Android开发之AlarmManager详解

一、前言

Android的系统架构是基于事件驱动机制的,例如系统启动、网络变化、接收短信等都是系统事件。为了响应这些事件,Android提供了一种机制——AlarmManager。

AlarmManager可以让我们在设定的时间周期性地执行某个任务,类似于定时器,但比定时器要更加强大。在Android中,我们使用AlarmManager来实现很多周期性任务,例如每隔一段时间发送一条广告、每天定时提醒用户做某事等等。

本文内容介绍了AlarmManager的使用方法,包括常见的场景以及如何使用AlarmManager来实现定时任务。

二、使用方法

1. 获取AlarmManager示例

在使用AlarmManager之前,我们需要先获取AlarmManager对象的实例,通过如下代码:

```

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

```

2. 设置定时任务

可以通过两种方式来实现定时任务:一种是通过PendingIntent设置定时任务;另一种是通过Service设置定时任务。下面我们分别介绍这两种方式。

2.1 通过PendingIntent设置定时任务

我们可以使用PendingIntent来调度在未来的某个时间执行的任务。使用PendingIntent的好处是不需要在程序中保持一个长时间运行的Service,只有在需要时才唤醒Service,这样可以减少耗电量。

下面是一个例子,我们通过AlarmManager和PendingIntent实现每隔30秒发送一条广告消息:

```

//获取系统AlarmManager服务

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

//定义要跳转至的Service

Intent intent = new Intent(this, MyService.class);

PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);

//兼容性处理,防止出现7.0以上版本不兼容的问题

if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){

//设置定时任务,每隔30秒执行一次

alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 30 * 1000, pendingIntent);

}else{

//7.0及以上版本使用setExactAndAllowWhileIdle方法

alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 30 * 1000, pendingIntent);

}

```

在上面的代码中,我们先获取了系统的AlarmManager服务,然后定义了要跳转至的Service,这里我们使用MyService,您也可以定义自己的Service。而设置PendingIntent时,我们使用了getService方法。

AlarmManager提供了setRepeating方法来设置定时任务,其中第一个参数表示闹钟类型,这里我们设置为RTC_WAKEUP,表示使用系统时间RTC来设置闹钟,唤醒CPU;第二个参数表示定时任务的开始时间,这里我们设置为当前系统时间,即立即执行;第三个参数表示定时任务的周期,这里我们设置为30秒,即每隔30秒执行一次。最后,我们将PendingIntent传递给AlarmManager,以便它知道哪个服务需要被执行。

在Android 7.0及以上版本中,如果需要低耗电量下设置定时任务,我们需要使用setExactAndAllowWhileIdle方法,而不能再使用setRepeating方法。这里需要注意的是,在低耗电模式下,不会立即启动定时器,而是等待设备退出该模式时才会执行任务。

2.2 通过Service设置定时任务

如果需要周期性地从网络获取数据、更新UI等任务,则需要使用Service来执行。下面是一个例子,我们通过AlarmManager和Service实现每隔1天提醒用户视力保护:

```

//获取系统AlarmManager服务

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

//定义要跳转至的Service

Intent intent = new Intent(this, ReminderService.class);

PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);

//当前时间

Calendar cal = Calendar.getInstance();

//设置提醒的时间(每天的11:00)

cal.set(Calendar.HOUR_OF_DAY, 11);

cal.set(Calendar.MINUTE, 0);

cal.set(Calendar.SECOND, 0);

//设置提醒间隔为一天

long interval = 24 * 60 * 60 * 1000;

//兼容性处理,防止出现7.0以上版本不兼容的问题

if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){

//设置定时任务,每隔一天执行一次

alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), interval, pendingIntent);

}else{

//7.0及以上版本使用setExactAndAllowWhileIdle方法

alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);

}

```

在上面的代码中,我们先获取了系统的AlarmManager服务,然后定义了要跳转至的Service,这里我们使用ReminderService,您也可以定义自己的Service。而设置PendingIntent时,我们使用了getService方法。

与前一种方式不同的是,这里使用了setRepeating方法的另一个版本,其中第一个参数表示闹钟类型,这里我们也设置为RTC_WAKEUP,表示使用系统时间RTC来设置闹钟,唤醒CPU;第二个参数表示定时任务的开始时间,这里我们设置为每天的11:00;第三个参数表示定时任务的周期,这里我们设置为一天。最后,我们将PendingIntent传递给AlarmManager,以便它知道哪个服务需要被执行。

注意:为了防止手机在深度睡眠中执行闹钟,这里我们设置闹钟类型为RTC_WAKEUP。这样系统将会在到达闹钟时间时唤醒设备。

三、常见问题

1. 如何取消定时任务?

可以使用AlarmManager的cancel方法来取消定时任务。具体方法如下:

```

//获取系统AlarmManager服务

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

//定义要取消的Intent

Intent intent = new Intent(this, MyService.class);

PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);

//取消定时任务

alarmManager.cancel(pendingIntent);

```

在上面的代码中,我们继续通过getService方法获取到的PendingIntent对象,然后通过AlarmManager的cancel方法来取消定时任务。

2. 如何保证AlarmManager的精确性?

对于Android 4.4及以上设备,我们可以通过setExact方法来保证AlarmManager的精确性。其使用方法与setRepeating方法类似,只是周期参数变为了单次执行的时间参数。

```

//获取系统AlarmManager服务

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

//定义要跳转至的Intent

Intent intent = new Intent(this, MyService.class);

PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);

//设置定时任务,每天的11:00执行

Calendar cal = Calendar.getInstance();

cal.set(Calendar.HOUR_OF_DAY, 11);

cal.set(Calendar.MINUTE, 0);

cal.set(Calendar.SECOND, 0);

//兼容性处理,防止出现7.0以上版本不兼容的问题

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){

alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);

}else{

alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);

}

```

3. 如何在锁屏状态下唤醒设备?

可以通过AlarmManager的另一个方法setAndAllowWhileIdle来解决该问题,具体使用方法与前面的方法类似。需要注意的是,该方法只能在应用处于较低的CPU活动状态时使用,否则设备可能会在锁屏状态下导致电量损耗较大。以下代码实现了在锁屏状态下唤醒设备执行任务的功能:

```

//获取系统AlarmManager服务

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

//定义要跳转至的Intent

Intent intent = new Intent(this, MyService.class);

PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);

//兼容性处理,防止出现7.0以上版本不兼容的问题

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

//设置定时任务,20秒后执行

alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 20 * 1000, pendingIntent);

}else{

//在锁屏状态下也会执行任务,但是会出现一些延迟

alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 20 * 1000, pendingIntent);

}

```

4. 什么是Doze模式?

Doze模式是Andoird 6.0系统中新增的一种优化策略。当设备处于静止状态、屏幕关闭、未充电并且未与任何已配对设备通信时,系统将会进入Doze模式。在Doze模式下,系统将会限制应用的网络访问、CPU和WIFI等系统服务。但是,应用仍然可以使用AlarmManager来触发定时任务。

5. 什么是App Standby?

App Standby是Andoird 6.0系统中新增的另一种优化策略。此模式下,系统会将未使用的应用置于待机状态以节省电量。当设备连接到电源并进入充电模式时,系统将恢复进入待机状态的应用的所有活动。只有在特定条件下,AlarmManager才能继续唤醒应用程序。

6. 如何考虑兼容性?

我们在设置定时任务时,需要根据Android系统版本的不同来区别使用setRepeating或setExact等方法。此外,在Android 6.0及以上版本中,由于Doze和App Standby等优化策略的加入,AlarmManager的实现机制也有所改变,因此需要谨慎地处理。

为了确保应用程序在不同的Android版本中都能够正常使用,我们可以使用以下方法来检测设备当前的Android版本。

```

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

//在6.0及以上版本使用setExactAndAllowWhileIdle方法

alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, pendingIntent);

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

//在4.4及以上版本使用setExact方法

alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, pendingIntent);

} else {

//在4.4以下版本使用setRepeating方法

alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, INTERVAL, pendingIntent);

}

```

通过这种方式,我们可以根据不同的版本来选择合适的方法调用。

四、案例说明

本节将介绍一个使用AlarmManager实现周期性任务的案例,该案例是一个翻译应用,它每隔5秒钟从服务器获取一条随机的英文单词,并且根据用户的操作执行相应的翻译任务。

1. 定义Service

为了能够在后台任务中执行网络请求,需要定义一个Service。在Service中添加网络请求逻辑。

```

public class TranslateService extends Service {

private static final String TAG = "TranslateService";

//网络请求地址

private static final String URL = "http://dict.youdao.com/dictvoice?audio=";

//单词列表

private List wordList;

//当前需要翻译的单词

private String currentWord;

@Override

public void onCreate() {

super.onCreate();

Log.i(TAG, "TranslateService onCreate");

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

Log.i(TAG, "TranslateService onStartCommand");

//从远程服务器获取单词列表

requestData();

return super.onStartCommand(intent, flags, startId);

}

/**

* 从服务器获取单词列表

*/

void requestData() {

//使用okhttp库发送GET请求

OkHttpClient client = new OkHttpClient();

HttpUrl.Builder urlBuilder = HttpUrl.parse(URL).newBuilder();

Request request = new Request.Builder()

.url(urlBuilder.build())

.build();

Call call = client.newCall(request);

call.enqueue(new Callback() {

@Override

public void onFailure(Call call, IOException e) {

Log.e(TAG, "requestData onFailure : " + e.getMessage());

}

@Override

public void onResponse(Call call, Response response) throws IOException {

if (response.isSuccessful()) {

String result = response.body().string();

parseResult(result);

} else {

Log.e(TAG, "requestData onResponse failed");

}

}

});

}

/**

* 解析从服务器端获取到的数据

*

* @param result

*/

void parseResult(String result) {

wordList = new ArrayList<>();

try {

JSONObject jsonObject = new JSONObject(result);

JSONArray jsonArray = jsonObject.getJSONArray("wordList");

for (int i = 0; i < jsonArray.length(); i++) {

String word = jsonArray.getString(i);

wordList.add(word);

}

} catch (JSONException e) {

Log.e(TAG, "parseResult JSONException : " + e.getMessage());

}

//开始执行翻译任务

startTranslate();

}

/**

* 开始执行翻译任务

*/

private void startTranslate() {

if (wordList != null && wordList.size() > 0) {

//获取随机的单词

int index = new Random().nextInt(wordList.size());

currentWord = wordList.get(index);

//执行翻译任务

Log.i(TAG, "currentWord : " + currentWord);

} else {

Log.e(TAG, "no words to translate");

}

}

@Nullable

@Override

public IBinder onBind(Intent intent) {

return null;

}

}

```

在上面的代码中,我们定义了一个TranslateService,用于实现后台任务。在该Service中,我们通过OkHttp库来发送GET请求,并从服务器端返回的数据中解析出单词列表,并随机获取一个单词,然后执行对应的翻译任务。

2. 设置AlarmManager并启动Service

在Activity中,我们可以根据需求来设置定时任务,并通过通过Service来执行对应的任务。

```

public class MainActivity extends AppCompatActivity {

private static final long INTERVAL = 5 * 1000;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

startAlarmManager();

}

/**

* 启动AlarmManager来开启定时任务

*/

private void startAlarmManager() {

//获取系统AlarmManager服务

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

//定义要跳转至的Intent

Intent intent = new Intent(this, TranslateService.class);

PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);

//设置定时任务

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

//在6.0及以上版本使用setExactAndAllowWhileIdle方法

alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, pendingIntent);

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

//在4.4及以上版本使用setExact方法

alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, pendingIntent);

} else {

//在4.4以下版本使用setRepeating方法

alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, INTERVAL, pendingIntent);

}

}

}

```

在MainActivity的onCreate方法中,我们调用了startAlarmManager方法来启动AlarmManager,这个方法中我们设置了获取系统AlarmManager服务,然后定义了要跳转至的Service,最后我们再根据不同的Android版本来选择合适的方法来设置定时任务。

三、总结

AlarmManager是Android中非常重要的机制之一,我们可以利用它来执行周期性任务。在这篇文章中,我们详 如果你喜欢我们三七知识分享网站的文章, 欢迎您分享或收藏知识分享网站文章 欢迎您到我们的网站逛逛喔!https://www.37seo.cn/

点赞(18) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部