回首向来萧瑟处,也无风雨也无晴
全部博文(37)
分类: Android平台
2016-01-25 10:26:33
Android系统中,闹钟和定时唤醒功能都是由AlarmManagerService控制并管理的。我们所熟悉的RTC闹钟都和它有很大的关系。为了便于称呼,把这个service称为ALMS(为了和 ActivityManagerService区别)。
Alarm与系统中其他服务一样,都是客户端/服务端模型,AlarmManager向上层提供调用的接口,具体功能实现在AlarmManagerService中,Alarm实现代码主要是在管理逻辑闹钟上,它把闹钟分为几大类分别记录再不同的列表中,然后在ALMS中开启一个专门的线程AlarmThread来循环的等待闹钟的出发,一旦时间到了,就会回调逻辑闹钟的动作,并发送给应用处理。
AlarmManagerService是服务端的代码,向外提供具体接口代码,是AlarmManager,通过aidl机制,IAlarmManager来调用到AlarmManagerService,IAlarmManager。其定义位于frameworks\base\core\java\android\app\IAlarmManager.aidl脚本中,定义截选如下:
interface IAlarmManager {
void set(int type, long triggerAtTime, in PendingIntent operation);//在规定的时间精确的执行闹钟,这个函数应该是闹钟执行精度比较高
void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);//该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示触发这个闹钟要等待的时间,第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。
void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);//设置一个重复闹钟的不精确版本,它相对而言更节能一些,因为系统可能会将几个差不多的闹钟合并为一个来执行,减少设备的唤醒次数。由于不是精确版,所以这里的intervaMills
会略有不同
参数:intervalMillis: NTERVAL_FIFTEEN_MINUTES
INTERVAL_HALF_HOUR
INTERVAL_HOUR
INTERVAL_HALF_DAY
INTERVAL_DAY
void setTime(long millis);//设置系统时间
void setTimeZone(String zone);//设置系统的默认时区。需要android. Permission. SET_TIME_ZONE权限。
void remove(in PendingIntent operation);//删除一个Alarm
}
在应用需要使用Alarm服务时,会通过ServiceManager去获取Alarm服务,在一般情况下,service的使用者会通过Service Manager Service接口,先拿到它感兴趣的service对应的代理I接口,然后再调用I接口的成员函数向service发出请求。所以按理说,我们也应该先 拿到一个IAlarmManager接口,然后再使用它。可是,对Alarm Manager Service来说,情况略有不同,其最常见的调用方式如下:
manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
其中,getSystemService()返回的不再是IAlarmManager接口,而是AlarmManager对象。我们参考AlarmManager.java文件,可以看到AlarmManager类中聚合了一个IAlarmManager接口:
private final IAlarmManager mService;
也就是说在执行实际动作时,AlarmManager只不过是把外界的请求转发给内部聚合的IAlarmManager接口而已。
另外,AlarmManager类中会以不同的公共常量来表示多种不同的逻辑闹钟,在Android 4.0的原生代码中有4种逻辑闹钟:
1) RTC_WAKEUP
2) RTC
3) ELAPSED_REALTIME_WAKEUP
4) ELAPSED_REALTIME
应用通过调用AlarmManager对象的成员函数,可以传递到AlarmManagerService,并由它进行实际的处理。
ALMS主要工作是在AlarmManagerService中,这个类继承于IAlarmManager.Stub,所以它是一个Binder实体,,所以主要代码都是在AlarmManagerService重视先的,当Alarm被设置之后,会被存入mPendingNonWakeupAlarms的一个Alarm的列表中,
1.ALMS的是一个系统级服务,启动过程是在SystemServer.java中的StartOtherService中调用到SystemManagerService的startService方法启动服务。
2.startService方法中,先调用到服务类的构造函数完成初始化,然后调用到服务类的onstart方法启动。
3.onStart方法中申请PowerManager的PARTIAL_WAKE_LOCK锁,初始化mTimeTickSender,(一个PendingIntent,发送时间改变的广播,每到整分钟发送一次),初始化的mDateChangeSender(一个PendingIntent,发送日期改变的广播,每天发送一次),创建ClockReceiver对象,来处理时间和日期改变的广播事件。
4.最后在onStart中将ALMS注册到Binder服务端。
Alarm是AlarmManagerService的一个内部类Alarm,所有应用在设置Alarm的时候,都会在setImplLocked函数中将Alarm格式化为内部类Alarm的格式,定义截选如下:
private static class Alarm {
public int type;
public int count;
public long when;
public long repeatInterval;
public PendingIntent operation;
public int uid;
public int pid;
其中记录了逻辑闹钟的一些关键信息。
type域:记录着逻辑闹钟的闹钟类型,比如RTC_WAKEUP、ELAPSED_REALTIME_WAKEUP等;
count域:是个辅助域,它和repeatInterval域一起工作。当repeatInterval大于0时,这个域可被用于计算下一次重复激发alarm的时间。
when域:记录闹钟的激发时间。这个域和type域相关,详细情况见后文;
repeatInterval域:表示重复激发闹钟的时间间隔,如果闹钟只需激发一次,则此域为0,如果闹钟需要重复激发,此域为以毫秒为单位的时间间隔;
operation域:记录闹钟激发时应该执行的动作,详细情况见后文;
uid域:记录设置闹钟的进程的uid;
pid域:记录设置闹钟的进程的pid。
外界应用设置Alarm的函数是set():
public void set(int type, long triggerAtTime, PendingIntent operation)
type:表示要设置的alarm类型。如前文所述,有4个alarm类型。
triggerAtTime:表示alarm“理应激发”的时间。
operation:指明了alarm闹铃激发时需要执行的动作,比如执行某种广播通告。
一般是应用调用AlarmManager的set方法,然后通过AlarmManagerService中去设置一个Alarm请求,并且在请求中注明了到达的时间以及要执行的动作,由于等待执行的动作一般不会马上执行,所以用PendingIntent形式,去发送一个广播或者Activity,当到达时间时间之后,应用受到来自Alarm的PendingIntent的广播,会执行PendingIntent中包含的动作。
另一个设置alarm的函数是setRepeating():
public
void setRepeating(int type,
long triggerAtTime, long interval,PendingIntent operation)
其参数基本上和set()函数差不多,只是多了一个“时间间隔”参数。事实上,在Alarm Manager Service一侧,set()函数内部也是在调用setRepeating()的,只不过会把interval设成了0。同样调到setImpl中。在rescheduleKernelAlarmsLocked();会调用setLocked() ,setLocked会调用到native中的set方法,
重新设置“实体闹钟”的激发时间。这个函数内部会调用ioctl()和底层打交道。具体代码可参考:
frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp文件:
static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd,
jint type, jlong seconds, jlong nanoseconds)
{
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = nanoseconds;
int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
if (result < 0)
{
ALOGE("Unable to set alarm to %lld.%09lld: %s\n", seconds, nanoseconds, strerror(errno));
}
}
我们知道,PendingIntent只是frameworks一层的概念,和底层驱动是没有关系的。所以向底层设置alarm时只需要type信息以及激发时间信息就可以了。
Alarm默认为非精准模式,除非显示指定采用精准模式。在非精准模式下,Alarm是批量提醒的,每个alarm根据其触发时间和最大触发时间的不同会被加入到不同的batch中,每个Batch都是一个开始时间和结束时间,在开始和结束之间的Alarm都会被存在该batch中,所以同一个batch中有不同alarm,但是同一个batch中的Alarm是同时发生的,这样虽然无法实现精准闹钟,但是官方的解释是批量处理可以减少设备被唤醒次数以及,降低功耗;其关系示意图如下:
不过针对精准闹钟,官方预留的方法是setExact和setWindow,二者都是通过将时间窗口定义为0来实现精准闹钟的,因为时间窗口为0,意味着触发时间和最大触发时间是一样的,因为典型的情况下:最大触发时间= 触发时间 + 窗口时间。同时所有的batch是按开始时间升序排列的,在一个batch内部,不同的闹钟也是按触发时间升序排列的,所以闹钟的唤醒顺序是按照 batch的排序依次触发的,而同一个batch中的alarm是同时触发的。
在第三方应用通过setImpl来设置Alarm时候,仅仅是将这个alarm加入到某个batch中,
在AlarmManagerService中创建了一个Batch列表,专门存储batch,batch中再存储Alarm,系统中新创建一个线程AlarmThread去遍历该列表,如果发现出发时间到了,就将其取出来,执行该Alarm,此流程在后文详细分析
AlarmManagerService在启动的时候就创建了一个线程waitThread并直接启动它,在onStart()函数中:
mNativeData = init();
…...
AlarmThread waitThread = new AlarmThread();
waitThread.start();
在onStart函数之初就回调用一个init()函数,这个函数是一个native函数,内部会打开Alarm驱动,并返回驱动文件句柄,只要能够顺利打开Alarm驱动,ALMS就可以启动waitThread线程,
AlarmThread本身是AlarmManagerService中继承Thread的内部类:
其AlarmThread线程run方法中是一个while(true)死循环
其流程如下:
waitForAlarm()函数是一个native层的函数,它使用来等待底层alarm激发动作的。触发Alarm的重要值存在result中传给上层, 上层利用这个result来遍历Alarm列表。
一旦等到底层驱动的激发动作,AlarmThread会开始遍历相应的逻辑闹钟列表,AlarmThread先创建一个临时的数组列表triggerList,然后根据result的值对相应的alarm数组列表调triggerAlarmsLocked(),一旦发现alarm数组列表中有某个alarm符合激发条件,就把它移到triggerList中。这 样,4条alarm数组列表中需要激发的alarm就汇总到triggerList数组列表中了。
在deliverAlarmsLocked函数中遍历到Alarm的triggerList,每遍历到一个Alarm,就执行它的alarm.operation.send()函数。我们知道,alarm中记录的operation就是当初设置它时传来的那个PendingIntent对象,现在开始执行PendingIntent的send()操作。
在deliverAlarmsLocked中最后会判断Alarm是否是ELAPSED_REALTIME_WAKEUP或者是RTC_WAKEUP,就属于唤醒闹钟,就会调用到ActivityManagerNative.noteWakeupAlarm,去通过BatteryStatsImpl来统计唤醒的Alarm,放入一个mWakeupAlarm的list中。
AlarmManager字面意思是闹钟管理,一些与时间相关的应用,如日历,闹钟等需要使用Alarm Manager的服务,但是在整个Android中,周期性的唤醒,以及定时唤醒不仅仅只是闹钟会用到,第三方应用的推送服务以及和定时和服务器交互的心跳数据包,也会用到。但是流程都是在设置定时执行的任务时,将Intent封装在PendingIntent中,触发时间到了之后,从底层接受到触发动作之后,通过获取PendingIntent,来执行定时任务。