本次给大家分析的是Android中Alarm的机制所用源码为最新的Android4.4.4。首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任务上的差别,最后分析Alarm机制的源码。

什么是Alarm

Alarm是android提供的用于完成闹钟式定时任务的类,系统通过AlarmManager来管理所有的Alarm,Alarm支持一次性定时任务和循环定时任务,它的使用方式很简单,这里不多做介绍,只给出一个简单的示例:

[java] view plain copy
  1. AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
  2. Intent intent = new Intent(getApplicationContext(), TestActivity.class);
  3. PendingIntent pendIntent = PendingIntent.getActivity(getApplicationContext(),
  4. 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  5. //5秒后发送广播,只发送一次
  6. int triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000;
  7. alarmMgr.set(AlarmManager.ELAPSED_REALTIME, triggerAtTime, pendIntent);

Alarm和Timer以及Handler在定时任务上的区别

相同点

三者都可以完成定时任务,都支持一次性定时和循环定时(注:Handler可以间接支持循环定时任务)

不同点

Handler和Timer在定时上是类似的,二者在系统休眠的情况下无法正常工作,定时任务不会按时触发。Alarm在系统休眠的情况下可以正常工作,并且还可以决定是否唤醒系统,同时Alarm在自身不启动的情况下仍能正常收到定时任务提醒,但是当系统重启或者应用被杀死的情况下,Alarm定时任务会被取消。另外,从Android4.4开始,Alarm事件默认采用非精准方式,即定时任务可能会有小范围的提前或延后,当然我们可以强制采用精准方式,而在此之前,Alarm事件都是精准方式。

Alarm与Binder的交互

Alarm由AlarmManager来管理,从使用方式来看,AlarmManager很简单,我们只要得到了AlarmManager的对象,就可以调用set方法来设定定时任务了,而如何得到AlarmManager对象呢?也很简单,AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);下面我们去看看AlarmManager的set方法,当然AlarmManager还有setRepeating方法,但是二者是类似的。为了更好地理解下面的内容,需要你了解AIDL,如果你还不了解,请参看android跨进程通信(IPC):使用AIDL。

code:AlarmManager#set

[java] view plain copy
  1. public void set(int type, long triggerAtMillis, PendingIntent operation) {
  2. setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null);
  3. }
  4. public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
  5. PendingIntent operation, WorkSource workSource) {
  6. setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource);
  7. }
  8. private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
  9. PendingIntent operation, WorkSource workSource) {
  10. if (triggerAtMillis < 0) {
  11. /* NOTYET
  12. if (mAlwaysExact) {
  13. // Fatal error for KLP+ apps to use negative trigger times
  14. throw new IllegalArgumentException("Invalid alarm trigger time "
  15. + triggerAtMillis);
  16. }
  17. */
  18. triggerAtMillis = 0;
  19. }
  20. try {
  21. //定时任务实际上都有mService来完成,也就是说AlarmManager只是一个空壳
  22. //从下面的构造方法可以看出,这个mService是IAlarmManager类型的,而IAlarmManager是一个接口
  23. //如果大家了解AIDL就应该知道IAlarmManager应该是一个AIDL接口
  24. mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
  25. workSource);
  26. } catch (RemoteException ex) {
  27. }
  28. }
  29. AlarmManager(IAlarmManager service, Context ctx) {
  30. mService = service;
  31. final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;
  32. mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT);
  33. }

说明:我对代码进行了注释,从注释可以看出,现在我们需要去找到这个mService,其实我已经帮大家找到了,它就是AlarmManagerService

Alarm机制分析

通过上面的一系列分析,我们知道AlarmManager的所有功能都是通过AlarmManagerService来完成的,在分析源码之前,我先来描述下Alarm的工作原理:从Android4.4开始,Alarm默认为非精准模式,除非显示指定采用精准模式。在非精准模式下,Alarm是批量提醒的,每个alarm根据其触发时间和最大触发时间的不同会被加入到不同的batch中,同一个batch的不同alarm是同时发生的,这样就无法实现精准闹钟,官方的解释是批量处理可以减少设备被唤醒次数以及节约电量,不过针对精准闹钟,官方预留的方法是setExact和setWindow,二者都是通过将时间窗口定义为0来实现精准闹钟的,因为时间窗口为0,意味着触发时间和最大触发时间是一样的,因为典型的情况下:最大触发时间= 触发时间 + 时间窗口。同时所有的batch是按开始时间升序排列的,在一个batch内部,不同的闹钟也是按触发时间升序排列的,所以闹钟的唤醒顺序是按照batch的排序依次触发的,而同一个batch中的alarm是同时触发的,可以用下面这个示意图来描述:

上图是示意图,系统中可以有多个batch,每个batch中可以有多个alarm。下面我们分析一下AlarmManagerService中的代码。其入口方法为set,set又调用了setImplLocked,所以我们直接看setImplLocked。

code:AlarmManagerService#setImplLocked

[java] view plain copy
  1. private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,
  2. PendingIntent operation, boolean isStandalone, boolean doValidate,
  3. WorkSource workSource) {
  4. /**创建一个alarm,其中各参数的含义如下:
  5. * type 闹钟类型 ELAPSED_REALTIME、RTC、RTC_WAKEUP等
  6. * when 触发时间 UTC类型,绝对时间,通过System.currentTimeMillis()得到
  7. * whenElapsed 相对触发时间,自开机算起,含休眠,通过SystemClock.elapsedRealtime()得到
  8. * maxWhen 最大触发时间
  9. * interval 触发间隔,针对循环闹钟有效
  10. * operation 闹钟触发时的行为,PendingIntent类型
  11. */
  12. Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation, workSource);
  13. //根据PendingIntent删除之前已有的同一个闹钟
  14. removeLocked(operation);
  15. boolean reschedule;
  16. //尝试将alarm加入到合适的batch中,如果alarm是独立的或者无法找到合适的batch去容纳此alarm,返回-1
  17. int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
  18. if (whichBatch < 0) {
  19. //没有合适的batch去容纳alarm,则新建一个batch
  20. Batch batch = new Batch(a);
  21. batch.standalone = isStandalone;
  22. //将batch加入mAlarmBatches中,并对mAlarmBatches进行排序:按开始时间升序排列
  23. reschedule = addBatchLocked(mAlarmBatches, batch);
  24. } else {
  25. //如果找到合适了batch去容纳此alarm,则将其加入到batch中
  26. Batch batch = mAlarmBatches.get(whichBatch);
  27. //如果当前alarm的加入引起了batch开始时间和结束时间的改变,则reschedule为true
  28. reschedule = batch.add(a);
  29. if (reschedule) {
  30. //由于batch的起始时间发生了改变,所以需要从列表中删除此batch并重新加入、重新对batch列表进行排序
  31. mAlarmBatches.remove(whichBatch);
  32. addBatchLocked(mAlarmBatches, batch);
  33. }
  34. }
  35. if (DEBUG_VALIDATE) {
  36. if (doValidate && !validateConsistencyLocked()) {
  37. Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
  38. + " when(hex)=" + Long.toHexString(when)
  39. + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
  40. + " interval=" + interval + " op=" + operation
  41. + " standalone=" + isStandalone);
  42. rebatchAllAlarmsLocked(false);
  43. reschedule = true;
  44. }
  45. }
  46. if (reschedule) {
  47. rescheduleKernelAlarmsLocked();
  48. }
  49. }

说明:通过上述代码可以看出,当我们创建一个alarm的时候,仅仅是将这个alarm加入到某个batch中,系统中有一个batch列表,专门用于存储所有的alarm。可是仅仅把alarm加入到batch中还不行,系统还必须提供一个类似于Looper的东西一直去遍历这个列表,一旦它发现有些alarm的时间已经到达就要把它取出来去执行。事实上,AlarmManagerService中的确有一个类似于Looper的东西去干这个事情,只不过它是个线程,叫做AlarmThread。下面看它的代码:

code:AlarmManagerService#AlarmThread

[java] view plain copy
  1. private class AlarmThread extends Thread
  2. {
  3. public AlarmThread()
  4. {
  5. super("AlarmManager");
  6. }
  7. public void run()
  8. {
  9. //当前时间触发的alarm列表
  10. ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
  11. while (true)
  12. {
  13. //jni方法,顾名思义,阻塞式方法,当有alarm的时候会被唤醒
  14. int result = waitForAlarm(mDescriptor);
  15. triggerList.clear();
  16. if ((result & TIME_CHANGED_MASK) != 0) {
  17. if (DEBUG_BATCH) {
  18. Slog.v(TAG, "Time changed notification from kernel; rebatching");
  19. }
  20. remove(mTimeTickSender);
  21. //将所有的alarm重新排序
  22. rebatchAllAlarms();
  23. mClockReceiver.scheduleTimeTickEvent();
  24. Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
  25. intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
  26. | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
  27. mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
  28. }
  29. synchronized (mLock) {
  30. final long nowRTC = System.currentTimeMillis();
  31. final long nowELAPSED = SystemClock.elapsedRealtime();
  32. if (localLOGV) Slog.v(
  33. TAG, "Checking for alarms... rtc=" + nowRTC
  34. + ", elapsed=" + nowELAPSED);
  35. if (WAKEUP_STATS) {
  36. if ((result & IS_WAKEUP_MASK) != 0) {
  37. long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD;
  38. int n = 0;
  39. for (WakeupEvent event : mRecentWakeups) {
  40. if (event.when > newEarliest) break;
  41. n++; // number of now-stale entries at the list head
  42. }
  43. for (int i = 0; i < n; i++) {
  44. mRecentWakeups.remove();
  45. }
  46. recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);
  47. }
  48. }
  49. //这个方法会把batch列表中的第一个batch取出来然后加到触发列表中
  50. //当然,前提是此batch的开始时间不大于当前时间
  51. //同时,如果是循环闹钟,则会对下次任务进行再次定时
  52. triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
  53. rescheduleKernelAlarmsLocked();
  54. // 遍历触发列表,发送PendingIntent
  55. for (int i=0; i<triggerList.size(); i++) {
  56. Alarm alarm = triggerList.get(i);
  57. try {
  58. if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
  59. //这里PendingIntent会被send,结果就是我们的定时任务被执行了
  60. alarm.operation.send(mContext, 0,
  61. mBackgroundIntent.putExtra(
  62. Intent.EXTRA_ALARM_COUNT, alarm.count),
  63. mResultReceiver, mHandler);
  64. // we have an active broadcast so stay awake.
  65. if (mBroadcastRefCount == 0) {
  66. setWakelockWorkSource(alarm.operation, alarm.workSource);
  67. mWakeLock.acquire();
  68. }
  69. final InFlight inflight = new InFlight(AlarmManagerService.this,
  70. alarm.operation, alarm.workSource);
  71. mInFlight.add(inflight);
  72. mBroadcastRefCount++;
  73. final BroadcastStats bs = inflight.mBroadcastStats;
  74. bs.count++;
  75. if (bs.nesting == 0) {
  76. bs.nesting = 1;
  77. bs.startTime = nowELAPSED;
  78. } else {
  79. bs.nesting++;
  80. }
  81. final FilterStats fs = inflight.mFilterStats;
  82. fs.count++;
  83. if (fs.nesting == 0) {
  84. fs.nesting = 1;
  85. fs.startTime = nowELAPSED;
  86. } else {
  87. fs.nesting++;
  88. }
  89. if (alarm.type == ELAPSED_REALTIME_WAKEUP
  90. || alarm.type == RTC_WAKEUP) {
  91. bs.numWakeup++;
  92. fs.numWakeup++;
  93. //针对能唤醒设备的闹钟,这里会做一些唤醒设备的事情
  94. ActivityManagerNative.noteWakeupAlarm(
  95. alarm.operation);
  96. }
  97. } catch (PendingIntent.CanceledException e) {
  98. if (alarm.repeatInterval > 0) {
  99. // This IntentSender is no longer valid, but this
  100. // is a repeating alarm, so toss the hoser.
  101. remove(alarm.operation);
  102. }
  103. } catch (RuntimeException e) {
  104. Slog.w(TAG, "Failure sending alarm.", e);
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }

说明:上述代码中,AlarmThread会一直循环的跑着,一旦有新的alarm触发,它就会取出一个batch然后逐个发送PendingIntent,具体alarm的触发是由底层来完成的,我没法再继续分析下去。还有就是Alarm中有一些细节,我没有进行很具体的分析,实际上很简单,大家一看就懂。到此为止,Alarm机制的主要流程也分析完了。

总结

本文没有详细介绍如何使用Alarm,因为很简单,看一下官方文档或者网上搜一下,到处都是。关于Alarm,有一点需要强调一下:当手机重启或者应用被杀死的时候,Alarm会被删除,因此,如果想通过Alarm来完成长久定时任务是不可靠的,如果非要完成长久定时任务,可以这样:将应用的所有Alarm信息存到数据库中,每次应用启动的时候都重新注册Alarm并更新Alarm的触发时间,通过这种方式就不存在Alarm丢失的情况了。本文很长,耗时8个小时才完成的,感谢大家阅读本文,希望本文能给大家带来一点帮助。

Android中Alarm的机制相关推荐

  1. android系统的alarm机制,Android中Alarm的机制

    本次给大家分析的是Android中Alarm的机制所用源码为最新的Android4.4.4.首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任 ...

  2. 浅析Android中的消息机制

    在分析Android消息机制之前,我们先来看一段代码: [java] view plaincopy public class MainActivity extends Activity impleme ...

  3. 探索Android中的Parcel机制(上)

    一.先从Serialize说起 我们都知道JAVA中的Serialize机制,译成串行化.序列化--,其作用是能将数据对象存入字节流其中,在须要时又一次生成对象.主要应用是利用外部存储设备保存对象状态 ...

  4. 探索Android中的Parcel机制(上) .

    一.先从Serialize说起 我们都知道JAVA中的Serialize机制,译成串行化.序列化--,其作用是能将数据对象存入字节流当中,在需要时重新生成对象.主要应用是利用外部存储设备保存对象状态, ...

  5. android classloader异常,Android中ClassLoader类加载机制

    Android中apk的构建过程 构建apk 如图 所示,典型 Android 应用模块的构建流程通常依循下列步骤: 编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中 ...

  6. Android中的消息机制

    Android 中的消息机制其实就是指的是 Handler 消息机制以及附带的 Looper 和 MessageQueue 的工作流程. 1.Android 为什么提供Handler? 解决子线程不能 ...

  7. 重温Android中的消息机制

    引入: 提到Android中的消息机制,大家应该都不陌生,我们在开发中不可避免的要和它打交道.从我们开发的角度来看,Handler是Android消息机制的上层接口.我们在平时的开发中只需要和Hand ...

  8. 探索Android中的Parcel机制(下)

    上一篇中我们透过源码看到了Parcel背后的机制,本质上把它当成一个Serialize就可以了,只是它是在内存中完成的序列化和反序列化,利用的是连续的内存空间,因此会更加高效. 我们接下来要说的是Pa ...

  9. Android 中的安全机制

    1 Android 安全机制概述 Android 是一个权限分离的系统 . 这是利用 Linux 已有的权限管理机制,通过为每一个 Application 分配不同的 uid 和 gid , 从而使得 ...

最新文章

  1. Error: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试
  2. android 音乐播放器的状态栏通知,Android仿虾米音乐播放器之通知栏notification解析...
  3. 远离ARP*** ARP防火墙新版发布
  4. Go 如何利用multipart/form-data实现文件的上传与下载
  5. SharePoint 2013网站管理-网站策略(关闭和删除策略)
  6. 统治世界的十大算法(转)
  7. 软件工程中需要学习和掌握的软件都有哪些_9个B端产品经理需要懂的技术
  8. 关于配置文件的节点内容加密(备忘)
  9. atitit.窗体静听esc退出本窗体java swing c# .net php
  10. wait放弃对象锁_Java线程:notify()和wait()示例
  11. Java程序性能优化——性能指标
  12. 计算机最早应用于( )领域,计算机最早被应用于()领域。
  13. 北京市小牛电动车选购指南
  14. 新买的电脑,为什么浏览器(谷歌)很卡,卡到爆,浏览器很卡怎么解决?
  15. 计算机类sci中接受综述么,sci综述类期刊有哪些
  16. 杀不死你的,终将使你更强大
  17. 天池竞赛 | 中医药领域的问题生成冠军方案
  18. 数据治理之主数据管理MDM
  19. 一些古今人物视频——至于励不励志,您自己评判
  20. SONY CR13 实战 windows7

热门文章

  1. Docker:(二)docker安装部署及优化详解
  2. 使用terminalizer工具录制终端生成GIF动画
  3. C语言实现归并排序——2路归并排序
  4. Docker——安装和启动
  5. Python数据可视化的3大步骤,你知道吗?
  6. 事务(Transaction)的简单理解
  7. 最优化理论·非线性最小二乘
  8. Android Studio快速集成讯飞SDK实现文字朗读功能
  9. BIND+Mysql实现DNS轮询泛解析和IP视图
  10. 关于对皮亚诺公理的理解