MTK 6.0 Alarm机制分析

(MTK Android6.0)

一、概述

Alarm模块处于Android Framework层,是Android中常用的一种系统服务级别的提示服务,在指定时间或周期性启动其他组件(Activity、Service、BroadcastReceiver),Alarm系统架构如下图所示:

MTK 6.0 Alarm机制分析

                                                                                    Alarm系统架构

二、AlarmManager介绍

 

Alarm对外通过AlarmManager提供接口给应用程序开发者使用,

主要提供如下接口:

(1)set(int type,long startTime,PendingIntent pi);

该方法用于设置一次性闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟执行时间,第三个参数表示闹钟响应动作。

(2)setRepeating(int type,long startTime,long intervalTime,PendingIntent pi);

该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。

(3)setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi);

该方法也用于设置重复闹钟,与第二个方法相似,不过其两个闹钟执行的间隔时间不是固定的而已。

(4) setExact(int type, long triggerAtMillis,PendingIntent operation)

    该方法用于设置一次性精确闹钟

(5)setAndAllowWhileIdle(int type, longtriggerAtMillis, PendingIntent operation)

    该方法用于设置一次性闹钟,但该闹钟运行在系统空闲状态下触发

(6)setExactAndAllowWhileIdle(int type, longtriggerAtMillis, PendingIntent operation)

该方法用于设置一次性精确闹钟,但该闹钟运行在系统空闲状态下触发

主要有四种类型的闹钟:

AlarmManager.ELAPSED_REALTIME表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;

AlarmManager.ELAPSED_REALTIME_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;

AlarmManager.RTC表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;

AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;

三、AlarmManagerService介绍

AlarmManagerService(后面简称ALMS)是Alarm模块的系统服务,主要逻辑也是在该服务中完成, ALMS的启动有系统进程完成。有SystemServer进程调用startOtherServices()

来启动:

[->SystemServer.java]

private void startOtherServices() {
  ...
  mSystemServiceManager.startService(AlarmManagerService.class);
  ...
}

下面以app调用setExactAndAllowWhileIdle()接口为例介绍ALMS的机制,调用流程的时序图如下:

MTK 6.0 Alarm机制分析

ALMS时序图

(1)  系统进程在启动ALMS服务时先实例化ALMS,同时初始化Handler和常量Constants类

   final AlarmHandler mHandler = new AlarmHandler(); 

   final Constants mConstants;  

   public AlarmManagerService(Context context) { 

       super(context); 

       // 初始化Handler和常量Constants类 

       mConstants = new Constants(mHandler); 

(2)  SystemService启动ALMS过程中调用ALMS 的onStart()方法,主要完成初始化alarm的驱动。添加时间变化闹钟和日志变化闹钟并启动Alarm的监听线程。

(3)  通过JNI对mNativeData进行初始化操作,打开设备驱动"/dev/alarm"返回一个long型的与fd文件描述符有关的值

(4)  初始化MTK 省电模块

(5)  初始化AlarmThread类,该类主要监听Alarm驱动的触发信息,完成定时触发的功能

(6)  启动Alarm监听线程

(7)  通过调用jni方法waitForAlarm(mNativeData)来监听触发信息,当驱动没有触发信息时,线程将进入等待状态直到有触发信息。到此ALMS的启动过程就完成了。

(8)  APP若需要创建一个Alarm则可以通过调用AlarmManager提供的接口来完成,此处我们创建一个精准并且在系统进入”休闲”状态也能触发的闹钟。代码如下:

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

Intent intent=new Intent(INTENT_ACTION);

PendingIntentpendingIntent=PendingIntent.getBroadcast(context, 0,intent,PendingIntent.FLAG_UPDATE_CURRENT);

long firstime= SystemClock.elapsedRealtime();//开始时间

aManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,firstime+(intervalMillis*1000) , pendingIntent);

(9)   通过Binder IPC 调用到ALMS服务端的set接口,在ALMS中进一步调用setImpl()接口,此接口可以修正传递过来的PendingIntent各相关参数:

MTK 6.0 Alarm机制分析

检查时间窗长度,如果超过半天就转换成一小时。

MTK 6.0 Alarm机制分析

检查alarm的执行周期,如果alarm的执行周期小于1分钟,则转换为1分钟。从这里可以得知周期性alarm的最小有效周期为1分钟,若要执行周期小于1分钟的事,不能使用alarm实现。

MTK 6.0 Alarm机制分析

检查触发时间,如果触发时间小于5秒,就将触发时间设置为5秒,为了防止alarm频繁滥发执行。 

(10)    调用mtk的省电模块,获取maxElapsed时间,判断流程如下:

MTK 6.0 Alarm机制分析

当满足如下条件时maxElapsed值设置为触发时间+5分钟:

background_power_saving_enable值为0

WIFI连接显示设备

Usb处于连接状态

亮屏

息屏且息屏时间小于1分钟

Type不为RTC_WAKEUP和ELAPSED_REALTIME_WAKEUP

白名单中配置的app

系统app且包名为com.android的app

(11)    创建Alarm对象,即一个闹钟。初始化类型、触发时间等信息

(12)    查找mAlarmBatches中是否有一样的Alarm,有则移除

(13)    判断该Alarm的触发时间范围是否满足现有的Batch中,判断依据如下:

MTK 6.0 Alarm机制分析

当Alarm满足以上四个条件时,Alarm就属于对应的Batch中,即需要添加对应Batch中作为批量触发中的一个。此时whichBatch为对应的Batch索引值

当Alarm不满足以上四个条件时,whichBatch的值为小于0,即需要重新创建一个Batch对象。

(14)    创建Batch对象,并将Alarm的whenElapsed值赋值给start maxWhenElapsed值赋值给end当是将Alarm插入现有的Batch对象中时,Batch的start和end时间需要根据插入的Alarm的whenElapsed和maxWhenElapsed值来做调整。即:

if (alarm.whenElapsed > start){

start = alarm.whenElapsed;

}

 if (alarm.maxWhenElapsed < end) {

       end= alarm.maxWhenElapsed;

     }

(15)    添加Batch对象到mAlarmBatches数组链表中,mAlarmBatches保存着系统中所有的Alarm。其保存机制如下图:

MTK 6.0 Alarm机制分析

保存在mAlarmBatches变量中的Batch对象先按照时间升序排序。在Batch类对象中维护了一个保存Alarm的alarms对象,alarms对象中的Alarm也是按照时间升序排序。

(16)    获取mAlarmBatches中第一个wakeup类型的Batch对象的start时间。并将触发时间设置到驱动中

(17)    调用jni方法 set()设置alarm,驱动的alarm只跟类型、触发时间相关,跟alarm的其他参数无关。

(18)    更新下一个Alarm的alarmClock 

(19)    当设置给驱动的Alarm的触发时间到了时,驱动将唤醒AlarmThread等待线程,并通过返回值result携带相应信息给ALMS。线程唤醒后通过调用triggerAlarmsLocked()方法获取满足触发条件的Alarm并保存在triggerList数组链表中。

(20)     执行triggerList中的alarm  并执行执行和PendingIntent关联的操作 ,即启动其他组件 (如:Activity、Service、BroadcastReceiver)。