文档章节

AlarmManager研究

悠然红茶
 悠然红茶
发布于 2013/08/02 20:17
字数 4914
阅读 5015
收藏 20

AlarmManager研究

侯 亮

 

1.概述

        在Android系统中,闹钟和唤醒功能都是由Alarm Manager Service控制并管理的。我们所熟悉的RTC闹钟以及定时器都和它有莫大的关系。为了便于称呼,我常常也把这个service简称为ALMS。

        另外,ALMS还提供了一个AlarmManager辅助类。在实际的代码中,应用程序一般都是通过这个辅助类来和ALMS打交道的。就代码而言,辅助类只不过是把一些逻辑语义传递给ALMS服务端而已,具体怎么做则完全要看ALMS的实现代码了。

        ALMS的实现代码并不算太复杂,主要只是在管理“逻辑闹钟”。它把逻辑闹钟分成几个大类,分别记录在不同的列表中。然后ALMS会在一个专门的线程中循环等待闹钟的激发,一旦时机到了,就“回调”逻辑闹钟对应的动作。

        以上只是一些概要性的介绍,下面我们来看具体的技术细节。

 

2.AlarmManager

        前文我们已经说过,ALMS只是服务端的东西。它必须向外提供具体的接口,才能被外界使用。在Android平台中,ALMS的外部接口为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);
    void setTime(long millis);
    void setTimeZone(String zone);
    void remove(in PendingIntent operation);
}

        在一般情况下,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接口而已。

2.1  AlarmManager的成员函数

        AlarmManager的成员函数有:

AlarmManager(IAlarmManager service)
public void set(int type, long triggerAtTime, PendingIntent operation)
public void setRepeating(int type, long triggerAtTime, long interval, 
PendingIntent operation)
public void setInexactRepeating(int type, long triggerAtTime, long interval,
                                      PendingIntent operation)
public void cancel(PendingIntent operation)
public void setTime(long millis)
public void setTimeZone(String timeZone)

即1个构造函数,6个功能函数。基本上完全和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实体。它包含的重要成员如下:

其中,mRtcWakeupAlarms等4个ArrayList<Alarm>数组分别对应着前文所说的4种“逻辑闹钟”。为了便于理解,我们可以想象在底层有4个“实体闹钟”,注意,是4个,不是4类。上面每一类“逻辑闹钟”都会对应一个“实体闹钟”,而逻辑闹钟则可以有若干个,它们被存储在ArrayList中,示意图如下:

 ALMS_002

当然,这里所说的“实体闹钟”只是个概念而已,其具体实现和底层驱动有关,在frameworks层不必过多关心。

 

         Frameworks层应该关心的是那几个ArrayList<Alarm>。这里的Alarm对应着逻辑闹钟。

 

3.1  逻辑闹钟

        Alarm是AlarmManagerService的一个内嵌类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。

 

        总体来说还是比较简单的,我们先补充说明一下其中的count域。这个域是针对重复性闹钟的一个辅助域。重复性闹钟的实现机理是,如果当前时刻已经超过闹钟的激发时刻,那么ALMS会先从逻辑闹钟数组中摘取下Alarm节点,并执行闹钟对应的逻辑动作,然后进一步比较“当前时刻”和“Alarm理应激发的理想时刻”之间的时间跨度,从而计算出Alarm的“下一次理应激发的理想时刻”,并将这个激发时间记入Alarm节点,接着将该节点重新排入逻辑闹钟列表。这一点和普通Alarm不太一样,普通Alarm节点摘下后就不再还回逻辑闹钟列表了。

 

        “当前时刻”和“理应激发时刻”之间的时间跨度会随实际的运作情况而变动。我们分两步来说明“下一次理应激发时刻”的计算公式:
1)  count  =  (时间跨度 /  repeatInterval ) + 1 ;
2)  “下一次理应激发时刻” = “上一次理应激发时刻”+ count * repeatInterval ; 

 

        我们画一张示意图,其中绿色的可激发时刻表示“上一次理应激发时刻”,我们假定“当前时刻”分别为now_1处或now_2处,可以看到会计算出不同的“下一次理应激发时刻”,这里用桔红色表示。

 ALMS_003

 

        可以看到,如果当前时刻为now_1,那么它和“上一次理应激发时刻”之间的“时间跨度”是小于一个repeatInterval的,所以count数为1。而如果当前时刻为now_2,那么“时间跨度”与repeatInterval的商取整后为2,所以count数为3。另外,图中那两个虚线箭头对应的可激发时刻,只是用来做刻度的东西。

 

3.2  主要行为

        接下来我们来看ALMS中的主要行为,这些行为和AlarmManager辅助类提供的成员函数相对应。

 

3.2.1   设置alarm

        外界能接触的设置alarm的函数是set():

public void set(int type, long triggerAtTime, PendingIntent operation)
type:表示要设置的alarm类型。如前文所述,有4个alarm类型。
triggerAtTime:表示alarm“理应激发”的时间。
operation:指明了alarm闹铃激发时需要执行的动作,比如执行某种广播通告。

 

        设置alarm的动作会牵扯到一个发起者。简单地说,发起者会向Alarm Manager Service发出一个设置alarm的请求,而且在请求里注明了到时间后需要执行的动作。由于“待执行的动作”一般都不会马上执行,所以要表达成PendingIntent的形式。(PendingIntent的详情可参考其他文章)

 

         另外,triggerAtTime参数的意义也会随type参数的不同而不同。简单地说,如果type是和RTC相关的话,那么triggerAtTime的值应该是标准时间,即从1970 年 1 月 1 日午夜开始所经过的毫秒数。而如果type是其他类型的话,那么triggerAtTime的值应该是从本次开机开始算起的毫秒数。

 

3.2.2   重复性alarm

        另一个设置alarm的函数是setRepeating():

public void setRepeating(int type, long triggerAtTime, long interval,PendingIntent operation)

其参数基本上和set()函数差不多,只是多了一个“时间间隔”参数。事实上,在Alarm Manager Service一侧,set()函数内部也是在调用setRepeating()的,只不过会把interval设成了0。 

 

         setRepeating()的实现函数如下:

public void setRepeating(int type, long triggerAtTime, long interval,
                         PendingIntent operation) 
{
    if (operation == null) {
        Slog.w(TAG, "set/setRepeating ignored because there is no intent");
        return;
    }
    synchronized (mLock) {
        Alarm alarm = new Alarm();
        alarm.type = type;
        alarm.when = triggerAtTime;
        alarm.repeatInterval = interval;
        alarm.operation = operation;

        // Remove this alarm if already scheduled.
        removeLocked(operation);

        if (localLOGV) Slog.v(TAG, "set: " + alarm);

        int index = addAlarmLocked(alarm);
        if (index == 0) {
            setLocked(alarm);
        }
    }
}

 

        代码很简单,会创建一个逻辑闹钟Alarm,而后调用addAlarmLocked()将逻辑闹钟添加到内部逻辑闹钟数组的某个合适位置。

private int addAlarmLocked(Alarm alarm) {
    ArrayList<Alarm> alarmList = getAlarmList(alarm.type);

    int index = Collections.binarySearch(alarmList, alarm, mIncreasingTimeOrder);
    if (index < 0) {
        index = 0 - index - 1;
    }
    if (localLOGV) Slog.v(TAG, "Adding alarm " + alarm + " at " + index);
    alarmList.add(index, alarm);
    . . . . . .
    return index;
}

 

        逻辑闹钟列表是依据alarm的激发时间进行排序的,越早激发的alarm,越靠近第0位。所以,addAlarmLocked()在添加新逻辑闹钟时,需要先用二分查找法快速找到列表中合适的位置,然后再把Alarm对象插入此处。

 ALMS_004

如果所插入的位置正好是第0位,就说明此时新插入的这个逻辑闹钟将会是本类alarm中最先被激发的alarm,而正如我们前文所述,每一类逻辑闹钟会对应同一个“实体闹钟”,此处我们在第0位设置了新的激发时间,明确表示我们以前对“实体闹钟”设置的激发时间已经不准确了,所以setRepeating()中必须重新调整一下“实体闹钟”的激发时间,于是有了下面的句子:

if (index == 0) {
    setLocked(alarm);
}

 

        setLocked()内部会调用native函数set():

private native void set(int fd, int type, long seconds, long nanoseconds);

重新设置“实体闹钟”的激发时间。这个函数内部会调用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.2.3   取消alarm

        用户端是调用AlarmManager对象的cancel()函数来取消alarm的。这个函数内部其实是调用IAlarmManager的remove()函数。所以我们只来看AlarmManagerService的remove()就可以了。

public void remove(PendingIntent operation) 
{
    if (operation == null) {
        return;
    }
    synchronized (mLock) {
        removeLocked(operation);
    }
}

       

       注意,在取消alarm时,是以一个PendingIntent对象作为参数的。这个PendingIntent对象正是当初设置alarm时,所传入的那个operation参数。我们不能随便创建一个新的PendingIntent对象来调用remove()函数,否则remove()是不会起作用的。PendingIntent的运作细节不在本文论述范围之内,此处我们只需粗浅地知道,PendingIntent对象在AMS(Activity Manager Service)端会对应一个PendingIntentRecord实体,而ALMS在遍历逻辑闹钟列表时,是根据是否指代相同PendingIntentRecord实体来判断PendingIntent的相符情况的。如果我们随便创建一个PendingIntent对象并传入remove()函数的话,那么在ALMS端势必找不到相符的PendingIntent对象,所以remove()必然无效。

 

         remove()中调用的removeLocked()如下:

public void removeLocked(PendingIntent operation) 
{
    removeLocked(mRtcWakeupAlarms, operation);
    removeLocked(mRtcAlarms, operation);
    removeLocked(mElapsedRealtimeWakeupAlarms, operation);
    removeLocked(mElapsedRealtimeAlarms, operation);
}

简单地说就是,把4个逻辑闹钟数组都遍历一遍,删除其中所有和operation相符的Alarm节点。removeLocked()的实现代码如下:

private void removeLocked(ArrayList<Alarm> alarmList,
                               PendingIntent operation) 
{
    if (alarmList.size() <= 0) {
        return;
    }

    // iterator over the list removing any it where the intent match
    Iterator<Alarm> it = alarmList.iterator();

    while (it.hasNext()) {
        Alarm alarm = it.next();
        if (alarm.operation.equals(operation)) {
            it.remove();
        }
    }
}

 

         请注意,所谓的取消alarm,只是删除了对应的逻辑Alarm节点而已,并不会和底层驱动再打什么交道。也就是说,是不存在针对底层“实体闹钟”的删除动作的。所以,底层“实体闹钟”在到时之时,还是会被“激发”出来的,只不过此时在frameworks层,会因为找不到符合要求的“逻辑闹钟”而不做进一步的激发动作。

 

3.2.4   设置系统时间和时区

        AlarmManager还提供设置系统时间的功能,设置者需要具有android.permission.SET_TIME权限。

public void setTime(long millis) 
{
    mContext.enforceCallingOrSelfPermission("android.permission.SET_TIME", "setTime");
    SystemClock.setCurrentTimeMillis(millis);
}

 

         另外,还具有设置时区的功能:

public void setTimeZone(String tz)

相应地,设置者需要具有android.permission.SET_TIME_ZONE权限。

 

3.3  运作细节

3.3.1   AlarmThread和Alarm的激发

        AlarmManagerService内部是如何感知底层激发alarm的呢?首先,AlarmManagerService有一个表示线程的mWaitThread成员:

private final AlarmThread mWaitThread = new AlarmThread();

在AlarmManagerService构造之初,就会启动这个专门的“等待线程”。

public AlarmManagerService(Context context) 
{
    mContext = context;
    mDescriptor = init();
    . . . . . .
    . . . . . .
    if (mDescriptor != -1) 
    {
        mWaitThread.start();   // 启动线程!
    } 
    else 
    {
        Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
    }
}

AlarmManagerService的构造函数一开始就会调用一个init()函数,该函数是个native函数,它的内部会打开alarm驱动,并返回驱动文件句柄。只要能够顺利打开alarm驱动,ALMS就可以走到mWaitThread.start()一句,于是“等待线程”就启动了。

 

3.3.1.1  AlarmThread中的run()

         AlarmThread本身是AlarmManagerService中一个继承于Thread的内嵌类:

private class AlarmThread extends Thread
其最核心的run()函数的主要动作流程图如下:

 ALMS_005

我们分别来阐述上图中的关键步骤。

 

3.3.1.2  waitForAlarm()

        首先,从上文的流程图中可以看到,AlarmThread线程是在一个while(true)循环里不断调用waitForAlarm()函数来等待底层alarm激发动作的。waitForAlarm()是一个native函数:

private native int waitForAlarm(int fd);

其对应的C++层函数是android_server_AlarmManagerService_waitForAlarm():

【com_android_server_AlarmManagerService.cpp】
static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd)
{
    int result = 0;

    do
    {
        result = ioctl(fd, ANDROID_ALARM_WAIT);
    } while (result < 0 && errno == EINTR);

    if (result < 0)
    {
        ALOGE("Unable to wait on alarm: %s\n", strerror(errno));
        return 0;
    }

    return result;
}

 

       当AlarmThread调用到ioctl()一句时,线程会阻塞住,直到底层激发alarm。而且所激发的alarm的类型会记录到ioctl()的返回值中。这个返回值对外界来说非常重要,外界用它来判断该遍历哪个逻辑闹钟列表。

 

3.3.1.3  triggerAlarmsLocked()

        一旦等到底层驱动的激发动作,AlarmThread会开始遍历相应的逻辑闹钟列表:

ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
. . . . . .
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
. . . . . .
if ((result & RTC_WAKEUP_MASK) != 0)
    triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);
if ((result & RTC_MASK) != 0)
    triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);
if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0)
    triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED);
if ((result & ELAPSED_REALTIME_MASK) != 0)
    triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED);

 

         可以看到,AlarmThread先创建了一个临时的数组列表triggerList,然后根据result的值对相应的alarm数组列表调用triggerAlarmsLocked(),一旦发现alarm数组列表中有某个alarm符合激发条件,就把它移到triggerList中。这样,4条alarm数组列表中需要激发的alarm就汇总到triggerList数组列表中了。

 

         接下来,只需遍历一遍triggerList就可以了:

Iterator<Alarm> it = triggerList.iterator();
while (it.hasNext()) 
{
    Alarm alarm = it.next();
    . . . . . .
    alarm.operation.send(mContext, 0,
                         mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
                         mResultReceiver, mHandler);

    // we have an active broadcast so stay awake.
    if (mBroadcastRefCount == 0) {
        setWakelockWorkSource(alarm.operation);
        mWakeLock.acquire();
    }
    mInFlight.add(alarm.operation);
    mBroadcastRefCount++;
    mTriggeredUids.add(new Integer(alarm.uid));
    BroadcastStats bs = getStatsLocked(alarm.operation);
    if (bs.nesting == 0) {
        bs.startTime = nowELAPSED;
    } else {
        bs.nesting++;
    }
    if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP
        || alarm.type == AlarmManager.RTC_WAKEUP) {
        bs.numWakeup++;
        ActivityManagerNative.noteWakeupAlarm(alarm.operation);
    }
}

在上面的while循环中,每遍历到一个Alarm对象,就执行它的alarm.operation.send()函数。我们知道,alarm中记录的operation就是当初设置它时传来的那个PendingIntent对象,现在开始执行PendingIntent的send()操作啦。

 

         PendingIntent的send()函数代码是:

public void send(Context context, int code, Intent intent,
                    OnFinished onFinished, Handler handler) throws CanceledException
{
    send(context, code, intent, onFinished, handler, null);
}

调用了下面的send()函数:

public void send(Context context, int code, Intent intent,
                 OnFinished onFinished, Handler handler, String requiredPermission)
                 throws CanceledException 
{
    try 
    {
        String resolvedType = intent != null 
                              ? intent.resolveTypeIfNeeded(context.getContentResolver())
                              : null;
        int res = mTarget.send(code, intent, resolvedType,
                               onFinished != null
                               ? new FinishedDispatcher(this, onFinished, handler)
                               : null,
                               requiredPermission);
        if (res < 0) 
        {
            throw new CanceledException();
        }
    } 
    catch (RemoteException e) 
    {
        throw new CanceledException(e);
    }
}

mTarget是个IPendingIntent代理接口,它对应AMS(Activity Manager Service)中的某个PendingIntentRecord实体。需要说明的是,PendingIntent的重要信息都是在AMS的PendingIntentRecord以及PendingIntentRecord.Key对象中管理的。AMS中有一张哈希表专门用于记录所有可用的PendingIntentRecord对象。

 

        相较起来,在创建PendingIntent对象时传入的intent数组,其重要性并不太明显。这种intent数组主要用于一次性启动多个activity,如果你只是希望启动一个activity或一个service,那么这个intent的内容有可能在最终执行PendingIntent的send()动作时,被新传入的intent内容替换掉。

 

         AMS中关于PendingIntentRecord哈希表的示意图如下:

ALMS_006

AMS是整个Android平台中最复杂的一个核心service了,所以我们不在这里做过多的阐述,有兴趣的读者可以参考其他相关文档。

 

3.3.1.4  进一步处理“唤醒闹钟”

        在AlarmThread.run()函数中while循环的最后,会进一步判断,当前激发的alarm是不是“唤醒闹钟”。如果闹钟类型为RTC_WAKEUP或ELAPSED_REALTIME_WAKEUP,那它就属于“唤醒闹钟”,此时需要通知一下AMS:

if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP
    || alarm.type == AlarmManager.RTC_WAKEUP) 
{
    bs.numWakeup++;
    ActivityManagerNative.noteWakeupAlarm(alarm.operation);
}

这两种alarm就是我们常说的0型和2型闹钟,它们和我们手机的续航时间息息相关。 

 

         AMS里的noteWakeupAlarm()比较简单,只是在调用BatteryStatsService服务的相关动作,但是却会导致机器的唤醒:

public void noteWakeupAlarm(IIntentSender sender) 
{
    if (!(sender instanceof PendingIntentRecord)) 
    {
        return;
    }
    
    BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
    synchronized (stats) 
    {
        if (mBatteryStatsService.isOnBattery()) 
        {
            mBatteryStatsService.enforceCallingPermission();
            PendingIntentRecord rec = (PendingIntentRecord)sender;
            int MY_UID = Binder.getCallingUid();
            int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
            BatteryStatsImpl.Uid.Pkg pkg = stats.getPackageStatsLocked(uid, rec.key.packageName);
            pkg.incWakeupsLocked();
        }
    }
}

 

         好了,说了这么多,我们还是画一张AlarmThread示意图作为总结:

 ALMS_007

 

3.3.2   说说AlarmManagerService中的mBroadcastRefCount

        下面我们说说AlarmManagerService中的mBroadcastRefCount,之所以要说它,仅仅是因为我在修改AlarmManagerService代码的时候,吃过它的亏。

         我们先回顾一下处理triggerList列表的代码,如下:

Iterator<Alarm> it = triggerList.iterator();
while (it.hasNext()) 
{
    Alarm alarm = it.next();
    . . . . . .
    alarm.operation.send(mContext, 0,
                         mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
                         mResultReceiver, mHandler);

    // we have an active broadcast so stay awake.
    if (mBroadcastRefCount == 0) {
        setWakelockWorkSource(alarm.operation);
        mWakeLock.acquire();
    }
    mInFlight.add(alarm.operation);
    mBroadcastRefCount++;
    . . . . . .
    . . . . . .
}

可以看到,在AlarmThread.run()中,只要triggerList中含有可激发的alarm,mBroadcastRefCount就会执行加一操作。一开始mBroadcastRefCount的值为0,所以会进入上面那句if语句,进而调用mWakeLock.acquire()。

 

         后来我才知道,这个mBroadcastRefCount变量,是决定何时释放mWakeLock的计数器。AlarmThread的意思很明确,只要还有处于激发状态的逻辑闹钟,机器就不能完全睡眠。那么释放这个mWakeLock的地方又在哪里呢?答案就在alarm.operation.send()一句的mResultReceiver参数中。

 

         mResultReceiver是AlarmManagerService的私有成员变量:

private final ResultReceiver mResultReceiver = newResultReceiver();

类型为ResultReceiver,这个类实现了PendingIntent.OnFinished接口:

class ResultReceiver implements PendingIntent.OnFinished
当send()动作完成后,框架会间接回调这个对象的onSendFinished()成员函数。
public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
                                String resultData, Bundle resultExtras) 
{
    . . . . . .
    . . . . . .
    if (mBlockedUids.contains(new Integer(uid)))
    {
        mBlockedUids.remove(new Integer(uid));
    } 
    else 
    {
        if (mBroadcastRefCount > 0)
        {
            mInFlight.removeFirst();
            mBroadcastRefCount--;
            
            if (mBroadcastRefCount == 0) 
            {
                mWakeLock.release();
            } 
            . . . . . .
        } 
        . . . . . .
    }
    . . . . . .
}

也就是说每当处理完一个alarm的send()动作,mBroadcastRefCount就会减一,一旦减为0,就释放mWakeLock。

 

         我一开始没有足够重视这个mBroadcastRefCount,所以把alarm.operation.send()语句包在了一条if语句中,也就是说在某种情况下,程序会跳过alarm.operation.send()一句,直接执行下面的语句。然而此时的mBroadcastRefCount还在坚定不移地加一,这直接导致mBroadcastRefCount再也减不到0了,于是mWakeLock也永远不会释放了。令人头痛的是,这个mWakeLock虽然不让手机深睡眠下去,却也不会点亮屏幕,所以这个bug潜藏了好久才被找到。还真是应了我说的那句话:“魔鬼总藏在细节中。”

 

如需转载本文内容,请注明出处。
谢谢

© 著作权归作者所有

悠然红茶
粉丝 344
博文 20
码字总数 106144
作品 0
西安
高级程序员
私信 提问
加载中

评论(15)

xbm2014
xbm2014

引用来自“悠然红茶”的评论

引用来自“秦朝伟”的评论

引用来自“秦朝伟”的评论

引用来自“悠然红茶”的评论

从Android 4.0的代码看,AlarmManagerService里没有关于“结束一个应用”的善后工作,只有关于“删除package”或者“unmount卡”的善后动作(比如ACTION_PACKAGE_REMOVED,ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)。你可以查查谁在调用cancel()动作,或者是否应用包被删除了。

非常感谢你的回答。关于代码我没有找到,但是通过实验我发现:
当你手动杀死进程时(比如去任务管理器中结束应用),那么AlarmManager会清除之前的该应用注册的Alarm;
但如果程序是发生崩溃挂掉的(不知这种情况是否和“由于清理内存被系统关掉”一样),那么AlarmManager注册的Alarm会在将来继续执行。

查看代码发现是这样的:当手动停止应用时,ActivityManagerService会发送ACTION_PACKAGE_RESTARTED广播,AlarmManagerService会接收到该广播调用remove清楚该应用注册的Alarm.
总算了解了一些代码上的操作,谢谢你了~

呵呵,求真求是,吾辈当互勉。
求真求是 说的真好
悠然红茶
悠然红茶 博主

引用来自“nclove”的评论

图全挂了啊
好像没发现什么异常啊。
nclove
nclove
图全挂了啊
悠然红茶
悠然红茶 博主

引用来自“秦朝伟”的评论

引用来自“秦朝伟”的评论

引用来自“悠然红茶”的评论

从Android 4.0的代码看,AlarmManagerService里没有关于“结束一个应用”的善后工作,只有关于“删除package”或者“unmount卡”的善后动作(比如ACTION_PACKAGE_REMOVED,ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)。你可以查查谁在调用cancel()动作,或者是否应用包被删除了。

非常感谢你的回答。关于代码我没有找到,但是通过实验我发现:
当你手动杀死进程时(比如去任务管理器中结束应用),那么AlarmManager会清除之前的该应用注册的Alarm;
但如果程序是发生崩溃挂掉的(不知这种情况是否和“由于清理内存被系统关掉”一样),那么AlarmManager注册的Alarm会在将来继续执行。

查看代码发现是这样的:当手动停止应用时,ActivityManagerService会发送ACTION_PACKAGE_RESTARTED广播,AlarmManagerService会接收到该广播调用remove清楚该应用注册的Alarm.
总算了解了一些代码上的操作,谢谢你了~

呵呵,求真求是,吾辈当互勉。
秦朝伟

引用来自“秦朝伟”的评论

引用来自“悠然红茶”的评论

从Android 4.0的代码看,AlarmManagerService里没有关于“结束一个应用”的善后工作,只有关于“删除package”或者“unmount卡”的善后动作(比如ACTION_PACKAGE_REMOVED,ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)。你可以查查谁在调用cancel()动作,或者是否应用包被删除了。

非常感谢你的回答。关于代码我没有找到,但是通过实验我发现:
当你手动杀死进程时(比如去任务管理器中结束应用),那么AlarmManager会清除之前的该应用注册的Alarm;
但如果程序是发生崩溃挂掉的(不知这种情况是否和“由于清理内存被系统关掉”一样),那么AlarmManager注册的Alarm会在将来继续执行。

查看代码发现是这样的:当手动停止应用时,ActivityManagerService会发送ACTION_PACKAGE_RESTARTED广播,AlarmManagerService会接收到该广播调用remove清楚该应用注册的Alarm.
总算了解了一些代码上的操作,谢谢你了~
秦朝伟

引用来自“悠然红茶”的评论

从Android 4.0的代码看,AlarmManagerService里没有关于“结束一个应用”的善后工作,只有关于“删除package”或者“unmount卡”的善后动作(比如ACTION_PACKAGE_REMOVED,ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)。你可以查查谁在调用cancel()动作,或者是否应用包被删除了。

非常感谢你的回答。关于代码我没有找到,但是通过实验我发现:
当你手动杀死进程时(比如去任务管理器中结束应用),那么AlarmManager会清除之前的该应用注册的Alarm;
但如果程序是发生崩溃挂掉的(不知这种情况是否和“由于清理内存被系统关掉”一样),那么AlarmManager注册的Alarm会在将来继续执行。
悠然红茶
悠然红茶 博主
从Android 4.0的代码看,AlarmManagerService里没有关于“结束一个应用”的善后工作,只有关于“删除package”或者“unmount卡”的善后动作(比如ACTION_PACKAGE_REMOVED,ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)。你可以查查谁在调用cancel()动作,或者是否应用包被删除了。
秦朝伟

引用来自“悠然红茶”的评论

引用来自“秦朝伟”的评论

引用来自“悠然红茶”的评论

引用来自“秦朝伟”的评论

你好,非常感谢你的这篇深入浅出的文章,解了我很多疑惑。
我以前开发一个应用程序,本身向AlarmManager注册了一个闹钟,当这个程序被手动杀死以后,AlarmManager到唤醒时是否仍然会执行原来的PenddingIntent呢?
这个PenddingIntent是否存在于AlarmManagerService的内存空间中,还是存在于注册闹钟的应用程序的空间中呢?
如果存在于AlarmManagerService的内存空间中,那么在Alarm的激发时间时,该PenddingIntent是能够执行的吗?还是会检测该PenddingIntent对应的应用程序是否存在,如果不存在就不执行了?
如果不存在于AlarmManagerService的内存空间中,当应用程序被杀死时,AlarmManagerService会如何处理该应用程序注册的PenddingIntent呢?
另外,Alarm对象里面的应用程序的uid,pid是用来干什么的呢?和这个有关系吗?
不好意思,太多问题了。十分期待你的解答~

PendingIntent的最核心信息既不在应用进程里,也不在Alarm Manager Service里。而是记录在Activity Manager Service中,在其中会有个对应的PendingIntentRecord。详情可参考我写的《说说PendingIntent的内部机制》。当应用程序被杀死后,是不会影响AlarmManager在合适时机做激发PendingIntent的动作的。

你好,我之后做了个实验。
当我的应用程序正常运行时,我能够通过dumpsys alarm命令查看到AlarmManager现在管理的所有Alarm,包括我自己应用程序的。
当我杀死自己的应用程序时,再次使用dumpsys alarm命令时,就已经看不到自己应用程序里面注册的Alarm了。
这能说明杀死应用程序,AlarmManager会清空它的PenddingIntent吗

按理说如果不明确调用cancel()操作,Alarm Manager Service是不会删除相应的Alarm的。我们可以设想,如果一个应用真的需要在未来某个时刻被alarm激发起来的话,它也就是set一个alarm而已,而在激发之前,这个应用可能早就被干掉了。

通过观察,我发现当我手动结束掉一个应用时,控制台会输出: I/AlarmManager﹕ Remove com.tencent.mobileqq from mIntentHisRW I/AlarmManager﹕ record removed。
这能够说明应用被杀死时,AlarmManager会做一些善后工作吧。但是这个在代码层面是如何发生的,我找了很久也没找到。请问我该如何去寻找这种问题的原因?
悠然红茶
悠然红茶 博主

引用来自“秦朝伟”的评论

引用来自“悠然红茶”的评论

引用来自“秦朝伟”的评论

你好,非常感谢你的这篇深入浅出的文章,解了我很多疑惑。
我以前开发一个应用程序,本身向AlarmManager注册了一个闹钟,当这个程序被手动杀死以后,AlarmManager到唤醒时是否仍然会执行原来的PenddingIntent呢?
这个PenddingIntent是否存在于AlarmManagerService的内存空间中,还是存在于注册闹钟的应用程序的空间中呢?
如果存在于AlarmManagerService的内存空间中,那么在Alarm的激发时间时,该PenddingIntent是能够执行的吗?还是会检测该PenddingIntent对应的应用程序是否存在,如果不存在就不执行了?
如果不存在于AlarmManagerService的内存空间中,当应用程序被杀死时,AlarmManagerService会如何处理该应用程序注册的PenddingIntent呢?
另外,Alarm对象里面的应用程序的uid,pid是用来干什么的呢?和这个有关系吗?
不好意思,太多问题了。十分期待你的解答~

PendingIntent的最核心信息既不在应用进程里,也不在Alarm Manager Service里。而是记录在Activity Manager Service中,在其中会有个对应的PendingIntentRecord。详情可参考我写的《说说PendingIntent的内部机制》。当应用程序被杀死后,是不会影响AlarmManager在合适时机做激发PendingIntent的动作的。

你好,我之后做了个实验。
当我的应用程序正常运行时,我能够通过dumpsys alarm命令查看到AlarmManager现在管理的所有Alarm,包括我自己应用程序的。
当我杀死自己的应用程序时,再次使用dumpsys alarm命令时,就已经看不到自己应用程序里面注册的Alarm了。
这能说明杀死应用程序,AlarmManager会清空它的PenddingIntent吗

按理说如果不明确调用cancel()操作,Alarm Manager Service是不会删除相应的Alarm的。我们可以设想,如果一个应用真的需要在未来某个时刻被alarm激发起来的话,它也就是set一个alarm而已,而在激发之前,这个应用可能早就被干掉了。
秦朝伟

引用来自“悠然红茶”的评论

引用来自“秦朝伟”的评论

你好,非常感谢你的这篇深入浅出的文章,解了我很多疑惑。
我以前开发一个应用程序,本身向AlarmManager注册了一个闹钟,当这个程序被手动杀死以后,AlarmManager到唤醒时是否仍然会执行原来的PenddingIntent呢?
这个PenddingIntent是否存在于AlarmManagerService的内存空间中,还是存在于注册闹钟的应用程序的空间中呢?
如果存在于AlarmManagerService的内存空间中,那么在Alarm的激发时间时,该PenddingIntent是能够执行的吗?还是会检测该PenddingIntent对应的应用程序是否存在,如果不存在就不执行了?
如果不存在于AlarmManagerService的内存空间中,当应用程序被杀死时,AlarmManagerService会如何处理该应用程序注册的PenddingIntent呢?
另外,Alarm对象里面的应用程序的uid,pid是用来干什么的呢?和这个有关系吗?
不好意思,太多问题了。十分期待你的解答~

PendingIntent的最核心信息既不在应用进程里,也不在Alarm Manager Service里。而是记录在Activity Manager Service中,在其中会有个对应的PendingIntentRecord。详情可参考我写的《说说PendingIntent的内部机制》。当应用程序被杀死后,是不会影响AlarmManager在合适时机做激发PendingIntent的动作的。

你好,我之后做了个实验。
当我的应用程序正常运行时,我能够通过dumpsys alarm命令查看到AlarmManager现在管理的所有Alarm,包括我自己应用程序的。
当我杀死自己的应用程序时,再次使用dumpsys alarm命令时,就已经看不到自己应用程序里面注册的Alarm了。
这能说明杀死应用程序,AlarmManager会清空它的PenddingIntent吗
Android笔记:定时提醒、闹钟实现

android要实现定时的功能那肯定就要用到闹铃相关的技术, 那么android闹铃实现是基于 AlarmManager 这个类的,首先我们来看一下它的几个主要的方法。 两个核心的方法 : private final IAla...

glblong
2015/07/29
0
0
Android Service 自动启动

我们在使用某些Android应用的时候,可能会发现安装了某应用以后,会有一些服务也会随之运行。而且,这些服务每次都会随着手机开机而启动。有的服务做的更绝,当用户停止该服务器以后,过了一...

鉴客
2011/12/14
12.8K
9
论文写作的常用方法

  写论文时,经常要介绍自己所使用的研究方法。那么,常用的研究方法有哪些呢?以下是论文网站小编搜集整理的论文写作的常用研究方法,供大家阅读参考。   一、方法   系统科学方法   ...

qq58f7142469309
2017/04/28
0
0
中国AI科研产出全球第一 但引文影响力低

来源:科学网 日前,爱思唯尔发布了《人工智能:知识的创造、转移与应用》报告,分析了全球人工智能科研的发展趋势。报告显示,2017年中国在人工智能领域出版的文章数量位列全球第一,科研产...

人工智能学家
01/14
0
0
斯坦福与苹果基于Apple Watch检测心率异常,0.5%人群被检出,其中84%患有房颤

雷锋网消息 3月16日,斯坦福大学医学院研究人员在洛杉矶新奥尔良举行的美国心脏病学会第68届年度科学会议和博览会上公布了一项基于Apple Watch的心脏研究结果。 研究结果显示,超过40万名参与...

刘思思
03/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Podman 使用指南

> 原文链接:Podman 使用指南 Podman 原来是 CRI-O 项目的一部分,后来被分离成一个单独的项目叫 libpod。Podman 的使用体验和 Docker 类似,不同的是 Podman 没有 daemon。以前使用 Docker...

米开朗基杨
40分钟前
5
0
拯救 项目经理个人时间的5个技巧

优秀的项目经理都有一个共同点,那就是良好的时间管理能力。专业的项目经理会确保他们的时间投入富有成效,尽可能避免时间浪费。 时间管理叫做GTD,即Getting Things Done——“把事情做完”...

Airship
今天
6
0
LNMP环境介绍,Mariadb安装,服务管理,mariadb安装3

LNMP环境介绍 Nginx 处理的请求有两种,分为 静态与动态 图片,js,css,视频,音频,flash 等都是静态请求,这些数据都不是保存在数据库里面的 动态请求一般来说,需要的数据是在数据库里面...

doomcat
今天
1
0
前端技术之:Prisma Demo服务部署过程记录

安装前提条件: 1、已经安装了docker运行环境 2、以下命令执行记录发生在MackBook环境 3、已经安装了PostgreSQL(我使用的是11版本) 4、Node开发运行环境可以正常工作 首先需要通过Node包管...

popgis
今天
7
0
数组和链表

数组 链表 技巧一:掌握链表,想轻松写出正确的链表代码,需要理解指针获引用的含义: 对指针的理解,记住下面的这句话就可以了: 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指...

code-ortaerc
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部