如果有人有比较好的关于alarmmanager用法的资料麻烦分享一下,网上的大多雷同,基本都是一个抄一个。
最近在用android studio做一个小东西,用到在后台定时任务,我的想法是每3小时请求一下网络数据,每天0点更新一下农历时间。并进行android各版本适配。由于是后台长期运行的服务,所以最佳方案就是用alarmmanager+handler。但在实践过程中发现各种问题,翻遍了网上的几乎所有帖子,都没有完全解决我的问题。
使用的代码:
alarmmanagerunits.java(alarmmanager的封装类)
myservices.java(服务)
我遇问题有以下几个:
1、4.4以上版本0点定时任务不启动。这个问题现在已解决,因为在4.4以上版本的android都是一次性任务,再加任务的时候由于代码的原因,pendingintent中的request code并没改变,所以只有一个最后的定时任务生效。代码中使用不同的requestcode就可以解决。我也不知道这是不是正统的解决办法,我看网上有人用回调的方法,但我不知道那是什么。
2、在4.4以下的版本中,使用setrepeating实现定时循环。这个也是最让我头痛的问题,在我找的代码里看起来实现很容易,实际上也是很容易就实现了每天定时循环任务和0点定时任务,同样代码实现了不同的功能,很好。但我把这些代码重新另外做一个例程使用的时候,0点任务无论如何也不执行。我把alarmmanagernuites.java和myservices.java代码完全拷贝过去,只是改了个UI,代码就不行了,上网逛了很久也没找到解决办法。
我现在知道的setrepeating()"可能"内在机制是这样的:
public void getUpAlarmManagerStartWork() {
calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY,0);
calendar.set(Calendar.MINUTE,0);
calendar.set(Calendar.SECOND,0);
am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyService.class);
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
am.setRepeating(AlarmManager.RTC_WAKEUP,calendar.getTimeInMillis(), TIME_INTERVAL, pendingIntent);
}
定个时,再写个动作,再延迟执行就可以了。这里面又有几个问题很久才搞明白:1、设置了时间后,getTimeInMillis()获取的就是设定的时间,不设定时间,getTimeInMillis()就是当前系统时间。2、如果设置的启动时间在当前时间之前,setrepeating会马上执行,并按照时间间隔重复执行任务,并不会再在设定时间执行。这个也是最让人头痛的地方,我觉得这是setrepeating正常的工作逻辑,但我做的有一个例程却可以在启动app当时执行,再按时间间隔执行,但0点的定时任务也可以执行,让我非常不理解,到现在也没找到原因。但只有这一个例程是这样,其他的全都不行,0点任务不会执行。3、setrepeating在app启动当时执行后的后一次执行并不是按照设定好的时间间隔,一般比设定时间短。我在网上找到一个帖子是这样说的:比如设定的时间间隔是5分钟,第二次循环任务是执行时间加上5的整数倍的最小值。比如启动时间为10点8分,第二次启动时间为10点10分,再后面的时间间隔正常,分别为10点15分,10点20分。。。真是让人无语。4、在版本大于4.4的时候,精确的定时任务不能再用setrepeating,6.0以上是setExactAndAllowWhileIdle,4.4以上是setExact,但另一个问题又来了,这两个都是一次性任务,要定时循环就在services里面回调。而且如果两个函数的pendingintent都用的一个requestcode(网上很多代码都是用的0),也会导致0点的任务不会执行。必须用不同的requestcode才可以,其实就是设置了两个定时任务,都是0的话,只有最后一个任务会执行,前面的都会被清掉。
下面是我的一个例程,当前状况是,无论哪个版本android,0点任务都不会执行。大家看看要怎么改才行:
alarmmanagerunits.java
package com.example.alarmmanagertest;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import java.util.Calendar;
public class AlarmManagerUtils {
private static final long TIME_INTERVAL = 61 * 1000;//闹钟执行任务的时间间隔
private Context context;
public static AlarmManager am;
public static PendingIntent pendingIntent;
private Calendar calendar;
//
private AlarmManagerUtils(Context aContext) {
this.context = aContext;
}
//singleton
private static AlarmManagerUtils instance = null;//instance 实例
public static AlarmManagerUtils getInstance(Context aContext) {
//getInstance跟new类似,在单例模式(保证一个类仅有一个实例,并提供一个访问它的全局访问点)的类中常见,
// 用来生成唯一的实例,getInstance往往是static的。
//详见https://www.csdn.net/tags/NtDakgwsNzAwMjYtYmxvZwO0O0OO0O0O.html
if (instance == null) {
synchronized (AlarmManagerUtils.class) {
//synchronized用于多线程访问,被synchronized修饰的部分不能同时被执行,是代码同步的一种方式。
if (instance == null) {
instance = new AlarmManagerUtils(aContext);
}
}
}
return instance;
}
public void createGetUpAlarmManager() {
am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyService.class);
//pendingIntent = PendingIntent.getService(context, 0, intent, 0);//每隔1小时启动一次服务
//在android8.0以后的版本,pendingIntent实现方法与之前的不同,需要版本适配
//Build.VERSION_CODES.O 为api level 26,即android8.0 见:build.class及avd manager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
pendingIntent = PendingIntent.getForegroundService(context, 0, intent, 0);
}else{
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
}
}
@SuppressLint("NewApi")//@SuppressLint忽略警告
public void getUpAlarmManagerStartWork() {
calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY,0);//HOUR_of_DAY of是小写,是12小时方法
calendar.set(Calendar.MINUTE,10);
calendar.set(Calendar.SECOND,50);
//版本适配 System.currentTimeMillis()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
am.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),pendingIntent);
System.out.println("4.4版本以上启动成功");
} else { //其实setExact和setExactAndAllowWhileIdle都是一次性闹钟,要想重复使用要重复注册。
am.setRepeating(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), TIME_INTERVAL, pendingIntent);
System.out.println("4.4版本以下启动成功");
//setRepeating 会先判断时间是否到设定时间,如果未到不执行,如果已过按间隔时间执行。
//疑问:如果设定时间已过,设定时间无效,但本例为什么还是会在设定时间执行?然后再间隔执行?2022.9.19
}
}
@SuppressLint("NewApi")
public void getUpAlarmManagerWorkOnOthers() {
//高版本重复设置闹钟达到低版本中setRepeating相同效果
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + TIME_INTERVAL, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ TIME_INTERVAL, pendingIntent);
}
}
}
myservices.java
package com.example.alarmmanagertest1;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import androidx.annotation.RequiresApi;
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "run: ");
Message msg =new Message();
msg.what=1;
msg.obj= String.valueOf(System.currentTimeMillis());
MainActivity.handler.sendMessage(msg);
}
}).start();
AlarmManagerUtils.getInstance(getApplicationContext()).getUpAlarmManagerWorkOnOthers();
return super.onStartCommand(intent, flags, startId);
}
}
myservice注册到androidmanifest.xml
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
MainActivity.java
package com.example.alarmmanagertest1;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
private AlarmManagerUtils alarmManagerUtils;
private TextView textView3;
private TextView textView5;
public int i=0;
public static Handler handler;
@SuppressLint("HandlerLeak")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView3=(TextView)findViewById(R.id.textView3);
textView5=(TextView)findViewById(R.id.textView5);
alarmManagerUtils = AlarmManagerUtils.getInstance(this);
alarmManagerUtils.createGetUpAlarmManager();
alarmManagerUtils.getUpAlarmManagerStartWork();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO 自动生成的方法存根
// super.handleMessage(msg);
long time=System.currentTimeMillis();
if (msg.what == 1) {
String text = (String) msg.obj;
i++;
String s = Integer.toString(i);
textView3.setText(s);
SimpleDateFormat f1= new SimpleDateFormat("MM-dd HH:mm:ss");
Date d1=new Date(time);
String t1=f1.format(d1);
textView5.setText(t1);
}
}
};// handler
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dsrw"
android:textColor="#EF6C00"
android:textSize="25sp"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintStart_toStartOf="@+id/guideline5"
app:layout_constraintTop_toTopOf="@+id/guideline3" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dsjishuqi"
android:textColor="?attr/colorPrimary"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/guideline6"
app:layout_constraintEnd_toStartOf="@+id/guideline8"
app:layout_constraintTop_toTopOf="@+id/guideline4" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/purple_200"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/guideline6"
app:layout_constraintStart_toStartOf="@+id/guideline8"
app:layout_constraintTop_toTopOf="@+id/guideline4" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dstime"
android:textColor="#009688"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/guideline7"
app:layout_constraintEnd_toStartOf="@+id/guideline8"
app:layout_constraintTop_toTopOf="@+id/guideline6" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#009688"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="@+id/guideline8"
app:layout_constraintTop_toTopOf="@+id/guideline6" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="122dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="173dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="61dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="220dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="272dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="162dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="222dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="20dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
运行界面:只做了一个计数器和任务执行时间