1.簡介
Android系統(tǒng)中,鬧鐘和定時喚醒功能都是由AlarmManagerService控制并管理的。我們所熟悉的RTC鬧鐘都和它有很大的關(guān)系。為了便于稱呼,把這個service稱為ALMS(為了和 ActivityManagerService區(qū)別)。
Alarm與系統(tǒng)中其他服務(wù)一樣,都是客戶端/服務(wù)端模型,AlarmManager向上層提供調(diào)用的接口,具體功能實現(xiàn)在AlarmManagerService中,Alarm實現(xiàn)代碼主要是在管理邏輯鬧鐘上,它把鬧鐘分為幾大類分別記錄再不同的列表中,然后在ALMS中開啟一個專門的線程AlarmThread來循環(huán)的等待鬧鐘的出發(fā),一旦時間到了,就會回調(diào)邏輯鬧鐘的動作,并發(fā)送給應(yīng)用處理。
2.AlarmManager
AlarmManagerService是服務(wù)端的代碼,向外提供具體接口代碼,是AlarmManager,通過aidl機制,IAlarmManager來調(diào)用到AlarmManagerService,IAlarmManager。其定義位于frameworks\base\core\java\android\app\IAlarmManager.aidl腳本中,定義截選如下:
interface IAlarmManager {
void set(int type, long triggerAtTime, in PendingIntent operation);//在規(guī)定的時間精確的執(zhí)行鬧鐘,這個函數(shù)應(yīng)該是鬧鐘執(zhí)行精度比較高
void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);//該方法用于設(shè)置重復鬧鐘,第一個參數(shù)表示鬧鐘類型,第二個參數(shù)表示觸發(fā)這個鬧鐘要等待的時間,第三個參數(shù)表示鬧鐘兩次執(zhí)行的間隔時間,第三個參數(shù)表示鬧鐘響應(yīng)動作。
void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);//設(shè)置一個重復鬧鐘的不精確版本,它相對而言更節(jié)能一些,因為系統(tǒng)可能會將幾個差不多的鬧鐘合并為一個來執(zhí)行,減少設(shè)備的喚醒次數(shù)。由于不是精確版,所以這里的intervaMills
會略有不同
參數(shù):intervalMillis: NTERVAL_FIFTEEN_MINUTES
INTERVAL_HALF_HOUR
INTERVAL_HOUR
INTERVAL_HALF_DAY
INTERVAL_DAY
void setTime(long millis);//設(shè)置系統(tǒng)時間
void setTimeZone(String zone);//設(shè)置系統(tǒng)的默認時區(qū)。需要android. Permission. SET_TIME_ZONE權(quán)限。
void remove(in PendingIntent operation);//刪除一個Alarm
}
在應(yīng)用需要使用Alarm服務(wù)時,會通過ServiceManager去獲取Alarm服務(wù),在一般情況下,service的使用者會通過Service Manager Service接口,先拿到它感興趣的service對應(yīng)的代理I接口,然后再調(diào)用I接口的成員函數(shù)向service發(fā)出請求。所以按理說,我們也應(yīng)該先 拿到一個IAlarmManager接口,然后再使用它??墒?,對Alarm Manager Service來說,情況略有不同,其最常見的調(diào)用方式如下:
manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
其中,getSystemService()返回的不再是IAlarmManager接口,而是AlarmManager對象。我們參考AlarmManager.java文件,可以看到AlarmManager類中聚合了一個IAlarmManager接口:
private final IAlarmManager mService;
也就是說在執(zhí)行實際動作時,AlarmManager只不過是把外界的請求轉(zhuǎn)發(fā)給內(nèi)部聚合的IAlarmManager接口而已。
另外,AlarmManager類中會以不同的公共常量來表示多種不同的邏輯鬧鐘,在Android 4.0的原生代碼中有4種邏輯鬧鐘:
1) RTC_WAKEUP
2) RTC
3) ELAPSED_REALTIME_WAKEUP
4) ELAPSED_REALTIME
應(yīng)用通過調(diào)用AlarmManager對象的成員函數(shù),可以傳遞到AlarmManagerService,并由它進行實際的處理。
3.AlarmManagerService
ALMS主要工作是在AlarmManagerService中,這個類繼承于IAlarmManager.Stub,所以它是一個Binder實體,,所以主要代碼都是在AlarmManagerService重視先的,當Alarm被設(shè)置之后,會被存入mPendingNonWakeupAlarms的一個Alarm的列表中,
3.1.AlarmManagerService的啟動
1.ALMS的是一個系統(tǒng)級服務(wù),啟動過程是在SystemServer.java中的StartOtherService中調(diào)用到SystemManagerService的startService方法啟動服務(wù)。
2.startService方法中,先調(diào)用到服務(wù)類的構(gòu)造函數(shù)完成初始化,然后調(diào)用到服務(wù)類的onstart方法啟動。
3.onStart方法中申請PowerManager的PARTIAL_WAKE_LOCK鎖,初始化mTimeTickSender,(一個PendingIntent,發(fā)送時間改變的廣播,每到整分鐘發(fā)送一次),初始化的mDateChangeSender(一個PendingIntent,發(fā)送日期改變的廣播,每天發(fā)送一次),創(chuàng)建ClockReceiver對象,來處理時間和日期改變的廣播事件。
4.最后在onStart中將ALMS注冊到Binder服務(wù)端。
3.2邏輯鬧鐘
Alarm是AlarmManagerService的一個內(nèi)部類Alarm,所有應(yīng)用在設(shè)置Alarm的時候,都會在setImplLocked函數(shù)中將Alarm格式化為內(nèi)部類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;
其中記錄了邏輯鬧鐘的一些關(guān)鍵信息。
type域:記錄著邏輯鬧鐘的鬧鐘類型,比如RTC_WAKEUP、ELAPSED_REALTIME_WAKEUP等;
count域:是個輔助域,它和repeatInterval域一起工作。當repeatInterval大于0時,這個域可被用于計算下一次重復激發(fā)alarm的時間。
when域:記錄鬧鐘的激發(fā)時間。這個域和type域相關(guān),詳細情況見后文;
repeatInterval域:表示重復激發(fā)鬧鐘的時間間隔,如果鬧鐘只需激發(fā)一次,則此域為0,如果鬧鐘需要重復激發(fā),此域為以毫秒為單位的時間間隔;
operation域:記錄鬧鐘激發(fā)時應(yīng)該執(zhí)行的動作,詳細情況見后文;
uid域:記錄設(shè)置鬧鐘的進程的uid;
pid域:記錄設(shè)置鬧鐘的進程的pid。
3.2設(shè)置Alarm
外界應(yīng)用設(shè)置Alarm的函數(shù)是set():
public void set(int type, long triggerAtTime, PendingIntent operation)
type:表示要設(shè)置的alarm類型。如前文所述,有4個alarm類型。
triggerAtTime:表示alarm“理應(yīng)激發(fā)”的時間。
operation:指明了alarm鬧鈴激發(fā)時需要執(zhí)行的動作,比如執(zhí)行某種廣播通告。
一般是應(yīng)用調(diào)用AlarmManager的set方法,然后通過AlarmManagerService中去設(shè)置一個Alarm請求,并且在請求中注明了到達的時間以及要執(zhí)行的動作,由于等待執(zhí)行的動作一般不會馬上執(zhí)行,所以用PendingIntent形式,去發(fā)送一個廣播或者Activity,當?shù)竭_時間時間之后,應(yīng)用受到來自Alarm的PendingIntent的廣播,會執(zhí)行PendingIntent中包含的動作。
3.3重復性的Alarm
另一個設(shè)置alarm的函數(shù)是setRepeating():
public
void setRepeating(int type,
long triggerAtTime, long interval,PendingIntent operation)
其參數(shù)基本上和set()函數(shù)差不多,只是多了一個“時間間隔”參數(shù)。事實上,在Alarm Manager Service一側(cè),set()函數(shù)內(nèi)部也是在調(diào)用setRepeating()的,只不過會把interval設(shè)成了0。同樣調(diào)到setImpl中。在rescheduleKernelAlarmsLocked();會調(diào)用setLocked() ,setLocked會調(diào)用到native中的set方法,
重新設(shè)置“實體鬧鐘”的激發(fā)時間。這個函數(shù)內(nèi)部會調(diào)用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一層的概念,和底層驅(qū)動是沒有關(guān)系的。所以向底層設(shè)置alarm時只需要type信息以及激發(fā)時間信息就可以了。
3.4 AlarmBatches分析
Alarm默認為非精準模式,除非顯示指定采用精準模式。在非精準模式下,Alarm是批量提醒的,每個alarm根據(jù)其觸發(fā)時間和最大觸發(fā)時間的不同會被加入到不同的batch中,每個Batch都是一個開始時間和結(jié)束時間,在開始和結(jié)束之間的Alarm都會被存在該batch中,所以同一個batch中有不同alarm,但是同一個batch中的Alarm是同時發(fā)生的,這樣雖然無法實現(xiàn)精準鬧鐘,但是官方的解釋是批量處理可以減少設(shè)備被喚醒次數(shù)以及,降低功耗;其關(guān)系示意圖如下:
不過針對精準鬧鐘,官方預留的方法是setExact和setWindow,二者都是通過將時間窗口定義為0來實現(xiàn)精準鬧鐘的,因為時間窗口為0,意味著觸發(fā)時間和最大觸發(fā)時間是一樣的,因為典型的情況下:最大觸發(fā)時間= 觸發(fā)時間 + 窗口時間。同時所有的batch是按開始時間升序排列的,在一個batch內(nèi)部,不同的鬧鐘也是按觸發(fā)時間升序排列的,所以鬧鐘的喚醒順序是按照 batch的排序依次觸發(fā)的,而同一個batch中的alarm是同時觸發(fā)的。
在第三方應(yīng)用通過setImpl來設(shè)置Alarm時候,僅僅是將這個alarm加入到某個batch中,
在AlarmManagerService中創(chuàng)建了一個Batch列表,專門存儲batch,batch中再存儲Alarm,系統(tǒng)中新創(chuàng)建一個線程AlarmThread去遍歷該列表,如果發(fā)現(xiàn)出發(fā)時間到了,就將其取出來,執(zhí)行該Alarm,此流程在后文詳細分析
4.AlarmThread和Alarm的激發(fā)
AlarmManagerService在啟動的時候就創(chuàng)建了一個線程waitThread并直接啟動它,在onStart()函數(shù)中:
mNativeData = init();
…...
AlarmThread waitThread = new AlarmThread();
waitThread.start();
在onStart函數(shù)之初就回調(diào)用一個init()函數(shù),這個函數(shù)是一個native函數(shù),內(nèi)部會打開Alarm驅(qū)動,并返回驅(qū)動文件句柄,只要能夠順利打開Alarm驅(qū)動,ALMS就可以啟動waitThread線程,
AlarmThread本身是AlarmManagerService中繼承Thread的內(nèi)部類:
其AlarmThread線程run方法中是一個while(true)死循環(huán)
其流程如下:
4.1 waitforAlarm()
waitForAlarm()函數(shù)是一個native層的函數(shù),它使用來等待底層alarm激發(fā)動作的。觸發(fā)Alarm的重要值存在result中傳給上層, 上層利用這個result來遍歷Alarm列表。
4.2 triggerAlarmsLocked()
一旦等到底層驅(qū)動的激發(fā)動作,AlarmThread會開始遍歷相應(yīng)的邏輯鬧鐘列表,AlarmThread先創(chuàng)建一個臨時的數(shù)組列表triggerList,然后根據(jù)result的值對相應(yīng)的alarm數(shù)組列表調(diào)triggerAlarmsLocked(),一旦發(fā)現(xiàn)alarm數(shù)組列表中有某個alarm符合激發(fā)條件,就把它移到triggerList中。這 樣,4條alarm數(shù)組列表中需要激發(fā)的alarm就匯總到triggerList數(shù)組列表中了。
在deliverAlarmsLocked函數(shù)中遍歷到Alarm的triggerList,每遍歷到一個Alarm,就執(zhí)行它的alarm.operation.send()函數(shù)。我們知道,alarm中記錄的operation就是當初設(shè)置它時傳來的那個PendingIntent對象,現(xiàn)在開始執(zhí)行PendingIntent的send()操作。
在deliverAlarmsLocked中最后會判斷Alarm是否是ELAPSED_REALTIME_WAKEUP或者是RTC_WAKEUP,就屬于喚醒鬧鐘,就會調(diào)用到ActivityManagerNative.noteWakeupAlarm,去通過BatteryStatsImpl來統(tǒng)計喚醒的Alarm,放入一個mWakeupAlarm的list中。
5.總結(jié)
AlarmManager字面意思是鬧鐘管理,一些與時間相關(guān)的應(yīng)用,如日歷,鬧鐘等需要使用Alarm Manager的服務(wù),但是在整個Android中,周期性的喚醒,以及定時喚醒不僅僅只是鬧鐘會用到,第三方應(yīng)用的推送服務(wù)以及和定時和服務(wù)器交互的心跳數(shù)據(jù)包,也會用到。但是流程都是在設(shè)置定時執(zhí)行的任務(wù)時,將Intent封裝在PendingIntent中,觸發(fā)時間到了之后,從底層接受到觸發(fā)動作之后,通過獲取PendingIntent,來執(zhí)行定時任務(wù)。
|