前段时间我一个朋友在面试回来问我:那个公司要5天之内完成一个项目,功能包括每天早上6点开始执行定时任务,大批量图片上传,大批量数据库同步。我心想,后两个功能还好说,可就是每天早上6点开始执行的这种定时任务如何搞定?
有了问题,自然要琢磨怎么解决,如果接触的知识面不够,或者没有系统的学习Android API,例如不知道AlarmManager,自然是不知道如何启动定时任务的,当时我也不知道这个的存在,突然心头一闪,那手机上的闹钟可不就是定时任务吗?
多亏了这心头一闪,知道从系统闹钟看看一个闹钟这种标准的定时任务是如何完成的,正好手中刚刚下载完完整的安卓源码,也编译通过了,在源码的目录/packages/中找到了DeskClock文件夹,一看便知是闹钟了。
为了不破坏原生系统的完整性,我将这个工程拷了出来,导入了Studio进行分析,看看如何启动一个定时任务(我自己心里是觉得应该不会有一个服务在后台一直跑着用来监控时间),导入Studio之后进行简单的环境配置编译,跑了起来:
不得不说原生应用还是很漂亮的,为了达到我们的研究目的,我们只选择一个闹钟是如何被创建以及是如何被响应的。
首先我们需要找到一个闹钟任务是如何被创建及打开的,我没有直接去找闹钟是如何创建的,我去找了闹钟是如何被打开的,因为在item上有个开关,我找到了那个开关:
这个开关位于com.android.deskclock.AlarmClockFragment内,AlarmClockFragment内含有一个Adapter内部类,在Adapter的getView方法中找到了这个小开关的触发事件:
@Override public 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; } ... @Override public 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() { @Override public 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>() { @Override protected AlarmInstance doInBackground(Void... parameters) { ContentResolver cr = context.getContentResolver(); // Dismiss all old instances AlarmStateManager.deleteAllInstances(context, alarm.id); // Update alarm Alarm.updateAlarm(cr, alarm); if (alarm.enabled) { return setupAlarmInstance(context, alarm); } return null; } @Override protected 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 manager AlarmStateManager.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 optimization if (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的各种时钟任务应用。