Chinaunix首页 | 论坛 | 博客
  • 博客访问: 149328
  • 博文数量: 37
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 125
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-28 11:52
个人简介

回首向来萧瑟处,也无风雨也无晴

文章存档

2016年(1)

2015年(36)

我的朋友

分类: Android平台

2016-01-25 10:26:33

 1.简介

Android系统中,闹钟和定时唤醒功能都是由AlarmManagerService控制并管理的。我们所熟悉的RTC闹钟都和它有很大的关系。为了便于称呼,把这个service称为ALMS(为了和 ActivityManagerService区别)。

 

Alarm与系统中其他服务一样,都是客户端/服务端模型,AlarmManager向上层提供调用的接口,具体功能实现在AlarmManagerService中,Alarm实现代码主要是在管理逻辑闹钟上,它把闹钟分为几大类分别记录再不同的列表中,然后在ALMS中开启一个专门的线程AlarmThread来循环的等待闹钟的出发,一旦时间到了,就会回调逻辑闹钟的动作,并发送给应用处理。

 

2.AlarmManager

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,并由它进行实际的处理。

 

3.AlarmManagerService

ALMS主要工作是在AlarmManagerService中,这个类继承于IAlarmManager.Stub,所以它是一个Binder实体,,所以主要代码都是在AlarmManagerService重视先的,当Alarm被设置之后,会被存入mPendingNonWakeupAlarms的一个Alarm的列表中,

3.1.AlarmManagerService的启动

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服务端。

 

3.2逻辑闹钟

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。

 

 

3.2设置Alarm

外界应用设置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中包含的动作。

 

3.3重复性的Alarm

另一个设置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信息以及激发时间信息就可以了。

 

3.4 AlarmBatches分析

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,此流程在后文详细分析

 

 

4.AlarmThreadAlarm的激发

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)死循环

其流程如下:

 

 

4.1 waitforAlarm()

waitForAlarm()函数是一个native层的函数,它使用来等待底层alarm激发动作的。触发Alarm的重要值存在result中传给上层, 上层利用这个result来遍历Alarm列表。

 

4.2 triggerAlarmsLocked()

一旦等到底层驱动的激发动作,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中。

 

5.总结

AlarmManager字面意思是闹钟管理,一些与时间相关的应用,如日历,闹钟等需要使用Alarm Manager的服务,但是在整个Android中,周期性的唤醒,以及定时唤醒不仅仅只是闹钟会用到,第三方应用的推送服务以及和定时和服务器交互的心跳数据包,也会用到。但是流程都是在设置定时执行的任务时,将Intent封装在PendingIntent中,触发时间到了之后,从底层接受到触发动作之后,通过获取PendingIntent,来执行定时任务。

阅读(2675) | 评论(0) | 转发(0) |
0

上一篇:Android ADB中使用find命令

下一篇:没有了

给主人留下些什么吧!~~