Android5.0 alarm实现原理

转载请标明出处:http://www.jianshu.com/users/183339cdc7ae/latest_articles

概述

这片文章会分析alarm的实现原理(以驱动支持的情况为例),从而可以知道的是:为什么在5.0以上进程被杀死后没有办法在执行alarm的指定动作,
alarm的系统架构十分简单,


alarm_arch.png

AlarmManager

API的使用,大家肯定不会陌生,直接给出代码,以Broadcast为例(省略掉了AndroidManifest.xml里面的权限)

Intent intent = new Intent(); 
intent.setAction("xxxxx"); 
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,intent, 0); 
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);  
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 5 * 1000, pendingIntent);

5 * 1000:单位是毫秒,意思是5s后,以broadcast的方式发送intent
来看看setRepeating的实现

public void setRepeating(int type, long triggerAtMillis,
        long intervalMillis, PendingIntent operation) {
    setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null, null);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
            PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
        ...
        mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
                workSource, alarmClock);
        ....
}

其实是调用了mService的set方法,mService是IAlarmManager的对象,看名字就可以知道,这里是一个代理类,具体的实现实在AlarmManagerService里面

AlarmManagerService

private final IBinder mService = new IAlarmManager.Stub() {
    @Override
    public void set(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, WorkSource workSource,
            AlarmManager.AlarmClockInfo alarmClock) {
         ...
        setImpl(type, triggerAtTime, windowLength, interval, operation,
                windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
}

来看看setImpl的代码

void setImpl(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, boolean isStandalone, WorkSource workSource,
            AlarmManager.AlarmClockInfo alarmClock) {
      ....
      if (interval > 0 && interval < MIN_INTERVAL) {
             interval = MIN_INTERVAL;
      } 
      ....
      setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
                    interval, operation, isStandalone, true, workSource, alarmClock, userId,
private static final long MIN_INTERVAL = 60 * 1000;

5.0的android上面,如果app里面设置的触发时间<1min,系统会强制将触发时间设置成1min。
setImplLocked这个方法会更新AlarmManagerService中的list,该list保存的是整个系统中所有的alarm。
然后调用rescheduleKernelAlarmsLocked-->setLocked

private void setLocked(int type, long when) {
    if (mNativeData != 0) {...}
        ... 
        set(mNativeData, type, alarmSeconds, alarmNanoseconds);
     } else {
         Message msg = Message.obtain();
         msg.what = ALARM_EVENT;
            
         mHandler.removeMessages(ALARM_EVENT);
         mHandler.sendMessageAtTime(msg, when);
    }
}

这个mNativeData成员变量如果!=0,表示的是该系统支持驱动设置时间(笔者这里猜想的是可以通过设置时间到驱动中,通过硬件中断来完成时间到点后的回调)。如果=0,则使用handler的方式来执行,handler的具体实现其实是使用的Linux中的一个叫epoll的api来实现的,这里不做具体讲解。 我们先假设mNativeData !=0的情况,会调用一个native的set方法

com_android_server_AlarmManagerService.cpp
static JNINativeMethod sMethods[] = {
    ....
    {"set", "(JIJJ)V", (void*)android_server_AlarmManagerService_set},
    ....
};
static void android_server_AlarmManagerService_set(JNIEnv*, jobject, jlong nativeData, jint type, jlong seconds, jlong nanoseconds)
{
    AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);
    ...
    int result = impl->set(type, &ts);
    ...
}

AlarmImpl的set方法是一个虚方法,有2个类实现该方法

  1. AlarmImplAlarmDriver
  2. AlarmImplTimerFd

到底是使用的哪个对象来实现,先来看看初始化的时候使用的哪一个

static jlong android_server_AlarmManagerService_init(JNIEnv*, jobject) {
    jlong ret = init_alarm_driver();
    if (ret) {
        return ret;
    }
    return init_timerfd();
}

init_alarm_driver

static jlong init_alarm_driver() {
    int fd = open("/dev/alarm", O_RDWR);
    if (fd < 0) {
        ALOGV("opening alarm driver failed: %s", strerror(errno));
        return 0;
    }

    AlarmImpl *ret = new AlarmImplAlarmDriver(fd);
    return reinterpret_cast<jlong>(ret);
}

可以知道如果驱动支持,会在dev目录下面生产一个alarm的节点,在初始化alarm系统的时候fd就不会<0
本文最开始说了,这里以驱动支持为例.

当我们系统不支持驱动设置时间的时候,会调用init_timerfd方法,大家可以自行去看看该方法,使用的就是epoll的机制,前文也提到过

所以来看看AlarmImplAlarmDriver的set方法

int AlarmImplAlarmDriver::set(int type, struct timespec *ts)
{
    return ioctl(fds[0], ANDROID_ALARM_SET(type), ts);
}

通过ioctl的方式将时间设置给驱动,后面的实现需要去看驱动代码,因为各个厂商的实现可能会不同,这里不做详细讲解
但是最后的作用应该都是一样的,就是把时间设置给驱动,等到硬件中断返回后,再回调到native层,native再回调到framework

native回调

那硬件中断产生后是如何回调的呢?来看看AlarmManagerService里做的一个重要事情(假设驱动支持,即mNativeData !=0)

如果不支持的话会以epoll的方式回调

AlarmManagerService
@Override
public void onStart() {
  ...
  if (mNativeData != 0) {
      AlarmThread waitThread = new AlarmThread();
      waitThread.start();
   } else {
       Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
   }
   ...
}

对,你没有看错,这里启动一个thread

private class AlarmThread extends Thread  {
  ...        
  public void run() {
      while (true) {
            int result = waitForAlarm(mNativeData);
            ...
            deliverAlarmsLocked(triggerList, nowELAPSED);
      }
  }
}

一个死循环来等待驱动的返回,waitForAlarm是一个native方法具体的实现在驱动中。如果整个系统中没有alarm的时间回调,waitForAlarm则阻塞在这,直到有回调的时候才往后执行,这样会减少CPU的开销。

framework回调

deliverAlarmsLocked
void deliverAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED) {
     ...
     alarm.operation.send(getContext(), 0,
                        mBackgroundIntent.putExtra(
                                Intent.EXTRA_ALARM_COUNT, alarm.count),
                        mResultReceiver, mHandler);
   ...

这里的operation是PendingIntent对象,最后回调其实是PendingIntent的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);
        ...
    }

其中最重要的是mTarget,来看看mTarget是何方圣神

private final IIntentSender mTarget;

/*package*/ PendingIntent(IIntentSender target) {
      mTarget = target;
}

那又是合适调用的PendingIntent(IIntentSender target)方法呢,这里以getBroadcast为例

 public static PendingIntent getBroadcast(Context context, int requestCode,
            Intent intent, @Flags int flags) {
        return getBroadcastAsUser(context, requestCode, intent, flags,
                new UserHandle(UserHandle.myUserId()));
    }
    public static PendingIntent getBroadcastAsUser(Context context, int requestCode,
            Intent intent, int flags, UserHandle userHandle) {
        ...
        try {
            ...
            IIntentSender target =
                ActivityManagerNative.getDefault().getIntentSender(
                    ActivityManager.INTENT_SENDER_BROADCAST, packageName,
                    null, null, requestCode, new Intent[] { intent },
                    resolvedType != null ? new String[] { resolvedType } : null,
                    flags, null, userHandle.getIdentifier());
            return target != null ? new PendingIntent(target) : null;
        } catch (RemoteException e) {
        }
        return null;
    }

当我们再app中使用PendingIntent的getBroadcast方法的时候,返回的PendingIntent对象中包含了一个私有变量target,该taget就是驱动回调到framework层后正真做事情的发起点。
如果当我们app所在的进程被杀死后,进程中的变量会被回收,当然这个target也会被回收掉,所以没有办法进行回调
那taget的send方法是怎么实现的呢?

PendingIntentRecord.java
final class PendingIntentRecord extends IIntentSender.Stub {
  ...
  public int send(int code, Intent intent, String resolvedType,
            IIntentReceiver finishedReceiver, String requiredPermission) {
        return sendInner(code, intent, resolvedType, finishedReceiver,
                requiredPermission, null, null, 0, 0, 0, null, null);
    }
   ...
  int sendInner(int code, Intent intent, String resolvedType,
            IIntentReceiver finishedReceiver, String requiredPermission,
            IBinder resultTo, String resultWho, int requestCode,
            int flagsMask, int flagsValues, Bundle options, IActivityContainer container) {
              ...
              switch (key.type) {
                    case ActivityManager.INTENT_SENDER_ACTIVITY:
                     ...
                     owner.startActivitiesInPackage(uid, key.packageName, allIntents,
                                        allResolvedTypes, resultTo, options, userId);
                    case ActivityManager.INTENT_SENDER_BROADCAST:
                      ...
                     owner.broadcastIntentInPackage(key.packageName, uid,
                                    finalIntent, resolvedType,
                                    finishedReceiver, code, null, null,
                                requiredPermission, (finishedReceiver != null), false, userId);
                      ...
                   case ActivityManager.INTENT_SENDER_SERVICE:
                             ...
                            owner.startServiceInPackage(uid,
                                    finalIntent, resolvedType, userId);
                             ...
        }
           ...
  }
}

最后根据不同的类型调用ams的方法启动Broadcast / Activity / Service

总结

老规矩,上一张流程图

alarm_flow.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容