Android系统原生应用解析之桌面闹钟及相关原理应用之时钟任务的应用(一)
前段時間我一個朋友在面試回來問我:那個公司要5天之內完成一個項目,功能包括每天早上6點開始執行定時任務,大批量圖片上傳,大批量數據庫同步。我心想,后兩個功能還好說,可就是每天早上6點開始執行的這種定時任務如何搞定?
有了問題,自然要琢磨怎么解決,如果接觸的知識面不夠,或者沒有系統的學習Android API,例如不知道AlarmManager,自然是不知道如何啟動定時任務的,當時我也不知道這個的存在,突然心頭一閃,那手機上的鬧鐘可不就是定時任務嗎?
多虧了這心頭一閃,知道從系統鬧鐘看看一個鬧鐘這種標準的定時任務是如何完成的,正好手中剛剛下載完完整的安卓源碼,也編譯通過了,在源碼的目錄/packages/中找到了DeskClock文件夾,一看便知是鬧鐘了。
為了不破壞原生系統的完整性,我將這個工程拷了出來,導入了Studio進行分析,看看如何啟動一個定時任務(我自己心里是覺得應該不會有一個服務在后臺一直跑著用來監控時間),導入Studio之后進行簡單的環境配置編譯,跑了起來:
不得不說原生應用還是很漂亮的,為了達到我們的研究目的,我們只選擇一個鬧鐘是如何被創建以及是如何被響應的。
首先我們需要找到一個鬧鐘任務是如何被創建及打開的,我沒有直接去找鬧鐘是如何創建的,我去找了鬧鐘是如何被打開的,因為在item上有個開關,我找到了那個開關:
這個開關位于com.android.deskclock.AlarmClockFragment內,AlarmClockFragment內含有一個Adapter內部類,在Adapter的getView方法中找到了這個小開關的觸發事件:
@Overridepublic View getView(int position, View convertView, ViewGroup parent) {...View v;if (convertView == null) {v = newView(mContext, getCursor(), parent);} else {v = convertView;}bindView(v, mContext, getCursor());return v;}...@Overridepublic void bindView(final View view, Context context, final Cursor cursor) {final Alarm alarm = new Alarm(cursor);Object tag = view.getTag();...final CompoundButton.OnCheckedChangeListener onOffListener = new CompoundButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton compoundButton, boolean checked) {if (checked != alarm.enabled) {setDigitalTimeAlpha(itemHolder, checked);alarm.enabled = checked;asyncUpdateAlarm(alarm, alarm.enabled);}}};...itemHolder.onoff.setOnCheckedChangeListener(onOffListener);...}onOffListener引用的對象便是鬧鐘開關的實現邏輯了,它調用了asyncUpdateAlarm方法:
private void asyncUpdateAlarm(final Alarm alarm, final boolean popToast) {final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();final AsyncTask<Void, Void, AlarmInstance> updateTask = new AsyncTask<Void, Void, AlarmInstance>() {@Overrideprotected AlarmInstance doInBackground(Void... parameters) {ContentResolver cr = context.getContentResolver();// Dismiss all old instancesAlarmStateManager.deleteAllInstances(context, alarm.id);// Update alarmAlarm.updateAlarm(cr, alarm);if (alarm.enabled) {return setupAlarmInstance(context, alarm);}return null;}@Overrideprotected void onPostExecute(AlarmInstance instance) {if (popToast && instance != null) {AlarmUtils.popAlarmSetToast(context, instance.getAlarmTime().getTimeInMillis());}}};updateTask.execute();}它內部執行了一個異步任務,任務的核心調用的是setupAlarmInstance:
private static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) {ContentResolver cr = context.getContentResolver();AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());newInstance = AlarmInstance.addInstance(cr, newInstance);// Register instance to state managerAlarmStateManager.registerInstance(context, newInstance, true);return newInstance;}這里的意思是,將鬧鐘數據添加到ContentProvider中,以便將數據共享給其它應用。接下來調用了AlarmStateManager.registerInstance:
public static void registerInstance(Context context, AlarmInstance instance,boolean updateNextAlarm) {...// The caller prefers to handle updateNextAlarm for optimizationif (updateNextAlarm) {updateNextAlarm(context);}}這段代碼中本來有很長的一段代碼,用來判斷鬧鐘的各個時間段的執行情況,為了避免干擾我們的主流程,對代碼進行了刪減處理,我們從上一段代碼可知,這里的updateNextAlarm值為true,進入到updateNextAlarm:
public static void updateNextAlarm(Context context) {...AlarmNotifications.registerNextAlarmWithAlarmManager(context, nextAlarm);}...public static void registerNextAlarmWithAlarmManager(Context context, AlarmInstance instance) {// Sets a surrogate alarm with alarm manager that provides the AlarmClockInfo for the// alarm that is going to fire next. The operation is constructed such that it is ignored// by AlarmStateManager.AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);int flags = instance == null ? PendingIntent.FLAG_NO_CREATE : 0;PendingIntent operation = PendingIntent.getBroadcast(context, 0 /* requestCode */,AlarmStateManager.createIndicatorIntent(context), flags);if (instance != null) {long alarmTime = instance.getAlarmTime().getTimeInMillis();// Create an intent that can be used to show or edit details of the next alarm.PendingIntent viewIntent = PendingIntent.getActivity(context, instance.hashCode(),createViewAlarmIntent(context, instance), PendingIntent.FLAG_UPDATE_CURRENT);AlarmManager.AlarmClockInfo info =new AlarmManager.AlarmClockInfo(alarmTime, viewIntent);alarmManager.setAlarmClock(info, operation);} else if (operation != null) {alarmManager.cancel(operation);}}updateNextAlarm方法中通過調用AlarmNotifications類中的registerNextAlarmWithAlarmManager方法將下一次的鬧鈴注冊到AlarmManager,歪果仁的命名清晰易懂啊!registerNextAlarmWithAlarmManager的方法內部則是我們真正需要看到的,首先是獲取到了系統中的AlarmManager對象,接著創建了一個PendingIntent對象operation,這個對象用來執行當鬧鐘時間到的時候,需要調用的廣播類,我們看看AlarmStateManager.createIndicatorIntent(context)方法內部是如何實現的:
/*** Creates an intent that can be used to set an AlarmManager alarm to set the next alarm* indicators.*/public static Intent createIndicatorIntent(Context context) {return new Intent(context, AlarmStateManager.class).setAction(INDICATOR_ACTION);}public final class AlarmStateManager extends BroadcastReceiver {...}內部則是簡單的new了一個Intent,這個顯式的意圖指定的是AlarmStateManager,而AlarmStateManager則繼承的是BroadcastReceiver,這時,我們很明白,當時鐘任務觸發的時候會調用我們這個AlarmStateManager的廣播,其實AlarmStateManager這個類的內部是有很多代碼了,這里被我刪減了,以便看代碼清晰。
回到上一段方法中繼續往下走,又看到在創建PendingIntent的對象viewIntent,這個對象則是用來當時鐘任務啟動時顯示的界面,我們在使用鬧鐘的時候會彈出一個界面讓我們關掉,那與我們交互的Activity就是這里被設定的,createViewAlarmIntent方法內部創建的是一個顯式的Activity,有興趣的可以進去看看。
一切設定好之后,再通過AlarmManager的方法setAlarmClock將我們的時鐘任務注冊到系統,系統會在我們設定的時間到達之后調用相關的Intent對象。
除了可以使用setAlarmClock方法注冊一個時鐘任務之外,我們還可以通過cancel方法將這個任務取消。
好,這就是鬧鐘的基本實現原理。接下里詳細描述一下AlarmManager的各種時鐘任務應用。
總結
以上是生活随笔為你收集整理的Android系统原生应用解析之桌面闹钟及相关原理应用之时钟任务的应用(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无主题
- 下一篇: Eureka 简介和使用