【Android】Android中Alarm的机制

本文是博主看源码需要了解Alarm的机制而转载的,只是需要了解Alarm的机制的原理,了解Alarm的机制它是个什么东西,有何作用。

如果只了解Alarm的机制的作用的话,那么可以直接看下方的这个颜色字体,博主已标注出来,如有需要深入了解Alarm的机制的童鞋,可仔细看全文。

                         下方所有这个字体颜色了解Alarm的机制的大致作用                        

以下为转载原文:

本次给大家分析的是Android中Alarm的机制所用源码为最新的Android4.4.4。首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任务上的差别,最后分析Alarm机制的源码。


什么是Alarm

Alarm是android提供的用于完成闹钟式定时任务的类,系统通过AlarmManager来管理所有的Alarm,Alarm支持一次性定时任务和循环定时任务,它的使用方式很简单,这里不多做介绍,只给出一个简单的示例:

AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);  
Intent intent = new Intent(getApplicationContext(), TestActivity.class);  
PendingIntent pendIntent = PendingIntent.getActivity(getApplicationContext(),  
        0, intent, PendingIntent.FLAG_UPDATE_CURRENT);  
//5秒后发送广播,只发送一次  
int triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000;  
alarmMgr.set(AlarmManager.ELAPSED_REALTIME, triggerAtTime, pendIntent); 


Alarm和Timer以及Handler在定时任务上的区别

相同点

三者都可以完成定时任务,都支持一次性定时和循环定时(注:Handler可以间接支持循环定时任务)

不同点

Handler和Timer在定时上是类似的,二者在系统休眠的情况下无法正常工作,定时任务不会按时触发。Alarm在系统休眠的情况下可以正常工作,并且还可以决定是否唤醒系统,同时Alarm在自身不启动的情况下仍能正常收到定时任务提醒,但是当系统重启或者应用被杀死的情况下,Alarm定时任务会被取消。另外,从Android4.4开始,Alarm事件默认采用非精准方式,即定时任务可能会有小范围的提前或延后,当然我们可以强制采用精准方式,而在此之前,Alarm事件都是精准方式。



Alarm与Binder的交互

Alarm由AlarmManager来管理,从使用方式来看,AlarmManager很简单,我们只要得到了AlarmManager的对象,就可以调用set方法来设定定时任务了,而如何得到AlarmManager对象呢?也很简单,AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);下面我们去看看AlarmManager的set方法,当然AlarmManager还有setRepeating方法,但是二者是类似的。为了更好地理解下面的内容,需要你了解AIDL,如果你还不了解,请参看android跨进程通信(IPC):使用AIDL

code:AlarmManager#set

public void set(int type, long triggerAtMillis, PendingIntent operation) {  
    setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null);  
}  
  
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,  
        PendingIntent operation, WorkSource workSource) {  
    setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource);  
}  
  
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,  
        PendingIntent operation, WorkSource workSource) {  
    if (triggerAtMillis < 0) {  
        /* NOTYET 
        if (mAlwaysExact) { 
            // Fatal error for KLP+ apps to use negative trigger times 
            throw new IllegalArgumentException("Invalid alarm trigger time " 
                    + triggerAtMillis); 
        } 
        */  
        triggerAtMillis = 0;  
    }  
  
    try {  
        //定时任务实际上都有mService来完成,也就是说AlarmManager只是一个空壳  
        //从下面的构造方法可以看出,这个mService是IAlarmManager类型的,而IAlarmManager是一个接口  
        //如果大家了解AIDL就应该知道IAlarmManager应该是一个AIDL接口  
        mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,  
                workSource);  
    } catch (RemoteException ex) {  
    }  
}  
  
AlarmManager(IAlarmManager service, Context ctx) {  
    mService = service;  
  
    final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;  
    mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT);  
}  

说明:我对代码进行了注释,从注释可以看出,现在我们需要去找到这个mService,其实我已经帮大家找到了,它就是AlarmManagerService

Alarm机制分析

通过上面的一系列分析,我们知道AlarmManager的所有功能都是通过AlarmManagerService来完成的,在分析源码之前,我先来描述下Alarm的工作原理:从Android4.4开始,Alarm默认为非精准模式,除非显示指定采用精准模式。在非精准模式下,Alarm是批量提醒的,每个alarm根据其触发时间和最大触发时间的不同会被加入到不同的batch中,同一个batch的不同alarm是同时发生的,这样就无法实现精准闹钟,官方的解释是批量处理可以减少设备被唤醒次数以及节约电量,不过针对精准闹钟,官方预留的方法是setExact和setWindow,二者都是通过将时间窗口定义为0来实现精准闹钟的,因为时间窗口为0,意味着触发时间和最大触发时间是一样的,因为典型的情况下:最大触发时间= 触发时间 + 时间窗口。同时所有的batch是按开始时间升序排列的,在一个batch内部,不同的闹钟也是按触发时间升序排列的,所以闹钟的唤醒顺序是按照batch的排序依次触发的,而同一个batch中的alarm是同时触发的,可以用下面这个示意图来描述:

上图是示意图,系统中可以有多个batch,每个batch中可以有多个alarm。下面我们分析一下AlarmManagerService中的代码。其入口方法为set,set又调用了setImplLocked,所以我们直接看setImplLocked。

code:AlarmManagerService#setImplLocked

private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,  
        PendingIntent operation, boolean isStandalone, boolean doValidate,  
        WorkSource workSource) {  
    /**创建一个alarm,其中各参数的含义如下: 
     * type 闹钟类型 ELAPSED_REALTIME、RTC、RTC_WAKEUP等 
     * when 触发时间 UTC类型,绝对时间,通过System.currentTimeMillis()得到 
     * whenElapsed 相对触发时间,自开机算起,含休眠,通过SystemClock.elapsedRealtime()得到 
     * maxWhen 最大触发时间 
     * interval 触发间隔,针对循环闹钟有效 
     * operation 闹钟触发时的行为,PendingIntent类型 
     */  
    Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation, workSource);  
    //根据PendingIntent删除之前已有的同一个闹钟  
    removeLocked(operation);  
  
    boolean reschedule;  
    //尝试将alarm加入到合适的batch中,如果alarm是独立的或者无法找到合适的batch去容纳此alarm,返回-1  
    int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);  
    if (whichBatch < 0) {  
        //没有合适的batch去容纳alarm,则新建一个batch  
        Batch batch = new Batch(a);  
        batch.standalone = isStandalone;  
        //将batch加入mAlarmBatches中,并对mAlarmBatches进行排序:按开始时间升序排列  
        reschedule = addBatchLocked(mAlarmBatches, batch);  
    } else {  
        //如果找到合适了batch去容纳此alarm,则将其加入到batch中  
        Batch batch = mAlarmBatches.get(whichBatch);  
        //如果当前alarm的加入引起了batch开始时间和结束时间的改变,则reschedule为true  
        reschedule = batch.add(a);  
        if (reschedule) {  
            //由于batch的起始时间发生了改变,所以需要从列表中删除此batch并重新加入、重新对batch列表进行排序  
            mAlarmBatches.remove(whichBatch);  
            addBatchLocked(mAlarmBatches, batch);  
        }  
    }  
  
    if (DEBUG_VALIDATE) {  
        if (doValidate && !validateConsistencyLocked()) {  
            Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when  
                    + " when(hex)=" + Long.toHexString(when)  
                    + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen  
                    + " interval=" + interval + " op=" + operation  
                    + " standalone=" + isStandalone);  
            rebatchAllAlarmsLocked(false);  
            reschedule = true;  
        }  
    }  
  
    if (reschedule) {  
        rescheduleKernelAlarmsLocked();  
    }  
}  

说明:通过上述代码可以看出,当我们创建一个alarm的时候,仅仅是将这个alarm加入到某个batch中,系统中有一个batch列表,专门用于存储所有的alarm。可是仅仅把alarm加入到batch中还不行,系统还必须提供一个类似于Looper的东西一直去遍历这个列表,一旦它发现有些alarm的时间已经到达就要把它取出来去执行。事实上,AlarmManagerService中的确有一个类似于Looper的东西去干这个事情,只不过它是个线程,叫做AlarmThread。下面看它的代码:

code:AlarmManagerService#AlarmThread

private class AlarmThread extends Thread  
{  
    public AlarmThread()  
    {  
        super("AlarmManager");  
    }  
      
    public void run()  
    {  
        //当前时间触发的alarm列表  
        ArrayList<Alarm> triggerList = new ArrayList<Alarm>();  
  
        while (true)  
        {  
            //jni方法,顾名思义,阻塞式方法,当有alarm的时候会被唤醒  
            int result = waitForAlarm(mDescriptor);  
  
            triggerList.clear();  
  
            if ((result & TIME_CHANGED_MASK) != 0) {  
                if (DEBUG_BATCH) {  
                    Slog.v(TAG, "Time changed notification from kernel; rebatching");  
                }  
                remove(mTimeTickSender);  
                //将所有的alarm重新排序  
                rebatchAllAlarms();  
                mClockReceiver.scheduleTimeTickEvent();  
                Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);  
                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING  
                        | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);  
                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);  
            }  
              
            synchronized (mLock) {  
                final long nowRTC = System.currentTimeMillis();  
                final long nowELAPSED = SystemClock.elapsedRealtime();  
                if (localLOGV) Slog.v(  
                    TAG, "Checking for alarms... rtc=" + nowRTC  
                    + ", elapsed=" + nowELAPSED);  
  
                if (WAKEUP_STATS) {  
                    if ((result & IS_WAKEUP_MASK) != 0) {  
                        long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD;  
                        int n = 0;  
                        for (WakeupEvent event : mRecentWakeups) {  
                            if (event.when > newEarliest) break;  
                            n++; // number of now-stale entries at the list head  
                        }  
                        for (int i = 0; i < n; i++) {  
                            mRecentWakeups.remove();  
                        }  
  
                        recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);  
                    }  
                }  
                //这个方法会把batch列表中的第一个batch取出来然后加到触发列表中  
                //当然,前提是此batch的开始时间不大于当前时间  
                //同时,如果是循环闹钟,则会对下次任务进行再次定时  
                triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);  
                rescheduleKernelAlarmsLocked();  
  
                // 遍历触发列表,发送PendingIntent  
                for (int i=0; i<triggerList.size(); i++) {  
                    Alarm alarm = triggerList.get(i);  
                    try {  
                        if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);  
                        //这里PendingIntent会被send,结果就是我们的定时任务被执行了  
                        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, alarm.workSource);  
                            mWakeLock.acquire();  
                        }  
                        final InFlight inflight = new InFlight(AlarmManagerService.this,  
                                alarm.operation, alarm.workSource);  
                        mInFlight.add(inflight);  
                        mBroadcastRefCount++;  
  
                        final BroadcastStats bs = inflight.mBroadcastStats;  
                        bs.count++;  
                        if (bs.nesting == 0) {  
                            bs.nesting = 1;  
                            bs.startTime = nowELAPSED;  
                        } else {  
                            bs.nesting++;  
                        }  
                        final FilterStats fs = inflight.mFilterStats;  
                        fs.count++;  
                        if (fs.nesting == 0) {  
                            fs.nesting = 1;  
                            fs.startTime = nowELAPSED;  
                        } else {  
                            fs.nesting++;  
                        }  
                        if (alarm.type == ELAPSED_REALTIME_WAKEUP  
                                || alarm.type == RTC_WAKEUP) {  
                            bs.numWakeup++;  
                            fs.numWakeup++;  
                            //针对能唤醒设备的闹钟,这里会做一些唤醒设备的事情  
                            ActivityManagerNative.noteWakeupAlarm(  
                                    alarm.operation);  
                        }  
                    } catch (PendingIntent.CanceledException e) {  
                        if (alarm.repeatInterval > 0) {  
                            // This IntentSender is no longer valid, but this  
                            // is a repeating alarm, so toss the hoser.  
                            remove(alarm.operation);  
                        }  
                    } catch (RuntimeException e) {  
                        Slog.w(TAG, "Failure sending alarm.", e);  
                    }  
                }  
            }  
        }  
    }  
}  

说明:上述代码中,AlarmThread会一直循环的跑着,一旦有新的alarm触发,它就会取出一个batch然后逐个发送PendingIntent,具体alarm的触发是由底层来完成的,我没法再继续分析下去。还有就是Alarm中有一些细节,我没有进行很具体的分析,实际上很简单,大家一看就懂。到此为止,Alarm机制的主要流程也分析完了。



总结

本文没有详细介绍如何使用Alarm,因为很简单,看一下官方文档或者网上搜一下,到处都是。关于Alarm,有一点需要强调一下:当手机重启或者应用被杀死的时候,Alarm会被删除,因此,如果想通过Alarm来完成长久定时任务是不可靠的,如果非要完成长久定时任务,可以这样:将应用的所有Alarm信息存到数据库中,每次应用启动的时候都重新注册Alarm并更新Alarm的触发时间,通过这种方式就不存在Alarm丢失的情况了。本文很长,耗时8个小时才完成的,感谢大家阅读本文,希望本文能给大家带来一点帮助。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值