安卓电量优化之JobScheduler使用介绍

农村四月闲人少,勤学苦攻把名扬。这篇文章主要讲述安卓电量优化之JobScheduler使用介绍相关的知识,希望能为你提供帮助。
版权声明:本文出自汪磊的博客,转载请务必注明出处。
【安卓电量优化之JobScheduler使用介绍】一、JobScheduler概述
JobScheduler是安卓5.0版本推出的API,允许开发者在符合某些条件时创建执行在后台的任务。在android开发中,会存在这些场景:你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务,例如当设备接通电源适配器或者连接到WIFI,此时就可以使用JobScheduler了,当一系列预置的条件被满足时,JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。
JobSchedule的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:避免频繁的唤醒硬件模块,造成不必要的电量消耗以及避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。
JobSchedule适用版本为5.0及以上,目前还未发现兼容库支持。
二、JobScheduler使用
使用JobScheduler首先我们需要创建一个类继承自JobService且必须实现两个方法(JobService继承自Service),分别是onStartJob(JobParameters params)onStopJob(JobParameters params),如下:

1 public class TestJobService extends JobService { 2 3@Override 4public boolean onStartJob(JobParameters params) { 5 6return false; 7} 8 9@Override 10public boolean onStopJob(JobParameters params) { 11 12return false; 13} 14 }

当任务开始时会执行onStartJob(JobParameters params)方法,这是系统用来触发已经被执行的任务。这个方法返回一个boolean值。
如果返回值是false,系统假设这个方法返回时任务已经执行完毕。
如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了开发者的肩上,当任务执行完毕时开发者需要自己调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统。
当系统接收到一个取消请求时,会调用onStopJob(JobParameters params)方法取消正在等待执行的任务。很重要的一点是如果onStartJob(JobParameters params)返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。onStopJob(JobParameters params)在这种情况下不会被调用。
需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。我们可以在onStartJob中利用Handler发送消息,在Handler中处理相关任务。
jobFinished(JobParameters params, boolean needsRescheduled)的两个参数中的params参数是从JobService的onStartJob(JobParameters params)的params传递过来的,needsRescheduled参数是告诉系统这个任务如果由于某些原因导致执行失败是否需要重新调度执行,true需要重新调度执行,false不需要。
最后我们需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。
1 < service 2android:name=".TestJobService" 3android:permission="android.permission.BIND_JOB_SERVICE" > 4 < /service>

在创建完TestJobService后,我们就该创建JobScheduler对象了,创建JobScheduler对象需要通过getSystemService( Context.JOB_SCHEDULER_SERVICE )来初始化:
1 JobScheduler tm =(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

接下来我们使用JobInfo.Builder来构建一个JobInfo对象。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,简单理解为这个任务的ID就可以了,第二个是这个Service组件的类名。
1 JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);

mServiceComponent初始化如下:
1 mServiceComponent = new ComponentName(this, TestJobService.class);

builder允许你设置很多不同的选项来控制任务的执行,重要方法说明如下:
方法名 说明
setPeriodic(long intervalMillis) 设置任务每隔intervalMillis运行一次
setMinimumLatency(long minLatencyMillis) 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与setPeriodic(long time)方法不兼容,如果这两个方法同时调用了就会引起异常
setOverrideDeadline(long maxExecutionDelayMillis) 这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。与setMinimumLatency(long time)一样,这个方法也会与setPeriodic(long time),同时调用这两个方法会引发异常
setPersisted(boolean isPersisted) 这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行
setRequiredNetworkType(int networkType) 这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行
setRequiresCharging(boolean requiresCharging) 这个方法告诉你的应用,只有当设备在充电时这个任务才会被执行
setRequiresDeviceIdle(boolean requiresDeviceIdle) 这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务
需要注意的是setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging) and setRequiresDeviceIdle(boolean requireIdle)者几个方法可能会使得你的任务无法执行,除非调用setOverrideDeadline(long time)设置了最大延迟时间,使得你的任务在未满足条件的情况下也会被执行。一旦你预置的条件被设置,你就可以构建一个JobInfo对象,然后通过如下所示的代码将它发送到你的JobScheduler中
1 if( tm.schedule( builder.build() ) < = 0 ) { 2//something wrong 3 }

schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则返回我们在JobInfo.Builder中定义的标识id。
停止某个任务,可以调用JobScheduler对象的cancel(int jobId)来实现;如果想取消所有的任务,可以调用JobScheduler对象的cancelAll()来实现。
以上就是JobScheduler的介绍,是不是很简单,重要的是理解什么情况下使用JobScheduler。
三、JobScheduler实例举例
本Demo是安卓官方提供的一个例子,我们先看TestJobService类;
1 public class TestJobService extends JobService { 2private static final String TAG = "SyncService"; 3 4@Override 5public void onCreate() { 6super.onCreate(); 7Log.i(TAG, "Service created"); 8} 9 10@Override 11public void onDestroy() { 12super.onDestroy(); 13Log.i(TAG, "Service destroyed"); 14} 15 16 17@Override 18public int onStartCommand(Intent intent, int flags, int startId) { 19Messenger callback = intent.getParcelableExtra("messenger"); 20Message m = Message.obtain(); 21m.what = MainActivity.MSG_SERVICE_OBJ; 22m.obj = this; 23try { 24callback.send(m); 25} catch (RemoteException e) { 26Log.e(TAG, "Error passing service object back to activity."); 27} 28return START_NOT_STICKY; 29} 30 31@Override 32public boolean onStartJob(JobParameters params) { 33// We don‘t do any real ‘work‘ in this sample app. All we‘ll 34// do is track which jobs have landed on our service, and 35// update the UI accordingly. 36jobParamsMap.add(params); 37if (mActivity != null) { 38mActivity.onReceivedStartJob(params); 39} 40Log.i(TAG, "on start job: " + params.getJobId()); 41return true; 42} 43 44@Override 45public boolean onStopJob(JobParameters params) { 46// Stop tracking these job parameters, as we‘ve ‘finished‘ executing. 47jobParamsMap.remove(params); 48if (mActivity != null) { 49mActivity.onReceivedStopJob(); 50} 51Log.i(TAG, "on stop job: " + params.getJobId()); 52return true; 53} 54 55MainActivity mActivity; 56private final LinkedList< JobParameters> jobParamsMap = new LinkedList< JobParameters> (); 57 58public void setUiCallback(MainActivity activity) { 59mActivity = activity; 60} 61 62/** Send job to the JobScheduler. */ 63public void scheduleJob(JobInfo t) { 64Log.d(TAG, "Scheduling job"); 65JobScheduler tm = 66(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 67if(tm.schedule(t)< =0){ 68Log.i(TAG, "something wrong"); 69} 70} 71 72 73/** 74* Not currently used, but as an exercise you can hook this 75* up to a button in the UI to finish a job that has landed 76* in onStartJob(). 77*/ 78public boolean callJobFinished() { 79JobParameters params = jobParamsMap.poll(); 80if (params == null) { 81return false; 82} else { 83jobFinished(params, false); 84return true; 85} 86} 87 }

上面提到过JobService其实也是Service,同样具有相应生命周期方法。TestJobService先不做详细讲解,但是要看到这个类中封装了一些JobScheduler方法供外界调用。
接下来我们结合MainActivity类一起看,MainActivity源码如下:
1 public class MainActivity extends Activity { 2 3private static final String TAG = "MainActivity"; 4 5public static final int MSG_UNCOLOUR_START = 0; 6public static final int MSG_UNCOLOUR_STOP = 1; 7public static final int MSG_SERVICE_OBJ = 2; 8// UI fields. 9int defaultColor; 10int startJobColor; 11int stopJobColor; 12 13private TextView mShowStartView; 14private TextView mShowStopView; 15private TextView mParamsTextView; 16private EditText mDelayEditText; 17private EditText mDeadlineEditText; 18private RadioButton mWiFiConnectivityRadioButton; 19private RadioButton mAnyConnectivityRadioButton; 20private CheckBox mRequiresChargingCheckBox; 21private CheckBox mRequiresIdleCheckbox; 22ComponentName mServiceComponent; 23TestJobService mTestService; 24// 25private static int kJobId = 0; 26 27@Override 28public void onCreate(Bundle savedInstanceState) { 29super.onCreate(savedInstanceState); 30setContentView(R.layout.sample_main); 31Resources res = getResources(); 32defaultColor = res.getColor(R.color.none_received); 33startJobColor = res.getColor(R.color.start_received); 34stopJobColor = res.getColor(R.color.stop_received); 35// Set up UI. 36mShowStartView = (TextView) findViewById(R.id.onstart_textview); 37mShowStopView = (TextView) findViewById(R.id.onstop_textview); 38mParamsTextView = (TextView) findViewById(R.id.task_params); 39mDelayEditText = (EditText) findViewById(R.id.delay_time); 40mDeadlineEditText = (EditText) findViewById(R.id.deadline_time); 41mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered); 42mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any); 43mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging); 44mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle); 45mServiceComponent = new ComponentName(this, TestJobService.class); 46// Start service and provide it a way to communicate with us. 47Intent startServiceIntent = new Intent(this, TestJobService.class); 48startServiceIntent.putExtra("messenger", new Messenger(mHandler)); 49startService(startServiceIntent); 50} 51 52Handler mHandler = new Handler(/* default looper */) { 53@Override 54public void handleMessage(Message msg) { 55switch (msg.what) { 56case MSG_UNCOLOUR_START: 57mShowStartView.setBackgroundColor(defaultColor); 58break; 59case MSG_UNCOLOUR_STOP: 60mShowStopView.setBackgroundColor(defaultColor); 61break; 62case MSG_SERVICE_OBJ: 63mTestService = (TestJobService) msg.obj; 64mTestService.setUiCallback(MainActivity.this); 65} 66} 67}; 68 69private boolean ensureTestService() { 70if (mTestService == null) { 71Toast.makeText(MainActivity.this, "Service null, never got callback?", 72Toast.LENGTH_SHORT).show(); 73return false; 74} 75return true; 76} 77 78/** 79* UI onclick listener to schedule a job. What this job is is defined in 80* TestJobService#scheduleJob(). 81*/ 82@SuppressLint("NewApi") 83public void scheduleJob(View v) { 84if (!ensureTestService()) { 85return; 86} 87JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent); 88 89String delay = mDelayEditText.getText().toString(); 90if (delay != null & & !TextUtils.isEmpty(delay)) { 91builder.setMinimumLatency(Long.valueOf(delay) * 1000); 92} 93String deadline = mDeadlineEditText.getText().toString(); 94if (deadline != null & & !TextUtils.isEmpty(deadline)) { 95builder.setOverrideDeadline(Long.valueOf(deadline) * 1000); 96} 97boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked(); 98boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked(); 99if (requiresUnmetered) { 100builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 101} else if (requiresAnyConnectivity) { 102builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 103} 104builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked()); 105builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked()); 106mTestService.scheduleJob(builder.build()); 107} 108/** 109* cancel All jobs 110* @param v 111*/ 112@SuppressLint("NewApi") 113public void cancelAllJobs(View v) { 114JobScheduler tm = 115(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 116tm.cancelAll(); 117} 118 119/** 120* UI onclick listener to call jobFinished() in our service. 121*/ 122public void finishJob(View v) { 123if (!ensureTestService()) { 124return; 125} 126mTestService.callJobFinished(); 127mParamsTextView.setText(""); 128} 129 130/** 131* Receives callback from the service when a job has landed 132* on the app. Colours the UI and post a message to 133* uncolour it after a second. 134*/ 135@SuppressLint("NewApi") 136public void onReceivedStartJob(JobParameters params) { 137mShowStartView.setBackgroundColor(startJobColor); 138Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START); 139mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second. 140mParamsTextView.setText("Executing: " + params.getJobId() + " " + params.getExtras()); 141} 142 143/** 144* Receives callback from the service when a job that 145* previously landed on the app must stop executing. 146* Colours the UI and post a message to uncolour it after a 147* second. 148*/ 149public void onReceivedStopJob() { 150mShowStopView.setBackgroundColor(stopJobColor); 151Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP); 152mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second. 153mParamsTextView.setText(""); 154} 155 156 }

对于这个Demo我只想说一个点,MainActivity在启动的时候onCreate()方法中47-49行启动了TestJobService,并且传递了一个new Messenger(mHandler)匿名对象,接下来我们看下TestJobService中onStartCommand方法,此方法上来就获取Messenger对象,并且调用send方法发送一个消息(实际调用的就是Handler的send方法,可自行点进源码查看),此消息obj为TestJobService类实例对象。回到MainActivity中查看mHandler,what为MSG_SERVICE_OBJ的时候:将msg的obj赋值给MainActivity中mTestService变量,然后调用TestJobService中setUiCallback方法,这样MainActivity与TestJobService类就互相持有彼此实例对象了,也就可以调用其内部的方法互相通信了,比如TestJobService中onStartJob方法调用MainActivity中的onReceivedStartJob方法,onReceivedStartJob方法中其实就是发送了Handler消息,最后在Handler中执行相关业务逻辑。好了Demo就说这一点,其余请自行细细查看,很简单,最后附上Demo布局文件:
1 < ScrollView xmlns:android="http://schemas.android.com/apk/res/android" 2android:layout_width="match_parent" 3android:layout_height="match_parent" 4android:orientation="vertical" > 5 6< LinearLayout 7android:layout_width="match_parent" 8android:layout_height="match_parent" 9android:layout_weight="1" 10android:orientation="vertical"> 11 12< LinearLayout 13android:layout_width="match_parent" 14android:layout_height="100dp"> 15< TextView 16android:id="@+id/onstart_textview" 17android:layout_width="wrap_content" 18android:layout_height="match_parent" 19android:layout_weight="1" 20android:background="#999999" 21android:gravity="center" 22android:text="@string/onstarttask"/> 23< TextView 24android:id="@+id/onstop_textview" 25android:layout_width="wrap_content" 26android:layout_height="match_parent" 27android:layout_weight="1" 28android:background="@color/none_received" 29android:gravity="center" 30android:text="@string/onstoptask"/> 31< /LinearLayout> 32< Button 33android:id="@+id/finished_button" 34android:layout_width="match_parent" 35android:layout_height="wrap_content" 36android:padding="20dp" 37android:layout_marginBottom="5dp" 38android:onClick="finishJob" 39android:text="@string/finish_job_button_text"/> 40 41< TextView 42android:id="@+id/task_params" 43android:layout_width="match_parent" 44android:layout_height="wrap_content" 45android:text="@string/defaultparamtext" 46android:gravity="center" 47android:textSize="20dp" 48android:padding="15dp" 49android:layout_marginBottom="10dp" /> 50 51< TextView 52android:layout_width="match_parent" 53android:layout_height="wrap_content" 54android:text="@string/constraints" 55android:layout_margin="15dp" 56android:textSize="18dp"/> 57< LinearLayout 58android:layout_width="match_parent" 59android:layout_height="wrap_content" 60android:orientation="vertical" 61android:layout_marginLeft="10dp"> 62< LinearLayout 63android:layout_width="match_parent" 64android:layout_height="wrap_content"> 65< TextView 66android:layout_width="wrap_content" 67android:layout_height="wrap_content" 68android:text="@string/connectivity" 69android:layout_marginRight="10dp"/> 70< RadioGroup 71android:layout_width="wrap_content" 72android:layout_height="wrap_content" 73android:orientation="horizontal"> 74< RadioButton android:id="@+id/checkbox_any" 75android:layout_width="wrap_content" 76android:layout_height="wrap_content" 77android:text="@string/any" 78android:checked="false" /> 79< RadioButton android:id="@+id/checkbox_unmetered" 80android:layout_width="wrap_content" 81android:layout_height="wrap_content" 82android:text="@string/unmetered" 83android:checked="false" /> 84< /RadioGroup> 85 86< /LinearLayout> 87< LinearLayout 88android:layout_width="match_parent" 89android:layout_height="wrap_content"> 90< TextView 91android:layout_width="wrap_content" 92android:layout_height="wrap_content" 93android:text="@string/timing"/> 94< TextView 95android:layout_width="wrap_content" 96android:layout_height="wrap_content" 97android:layout_marginLeft="15dp" 98android:textSize="17dp" 99android:text="@string/delay"/> 100< EditText 101android:id="@+id/delay_time" 102android:layout_width="60dp" 103android:layout_height="wrap_content" 104android:inputType="number"/> 105< TextView 106android:layout_width="wrap_content" 107android:layout_height="wrap_content" 108android:text="@string/deadline" 109android:textSize="17dp"/> 110< EditText 111android:id="@+id/deadline_time" 112android:layout_width="60dp" 113android:layout_height="wrap_content" 114android:inputType="number"/> 115< /LinearLayout> 116< LinearLayout 117android:layout_width="match_parent" 118android:layout_height="wrap_content"> 119< TextView 120android:layout_width="wrap_content" 121android:layout_height="wrap_content" 122android:text="@string/charging_caption" 123android:layout_marginRight="15dp"/> 124< CheckBox 125android:layout_width="wrap_content" 126android:layout_height="wrap_content" 127android:id="@+id/checkbox_charging" 128android:text="@string/charging_text"/> 129< /LinearLayout> 130< LinearLayout 131android:layout_width="match_parent" 132android:layout_height="wrap_content"> 133< TextView 134android:layout_width="wrap_content" 135android:layout_height="wrap_content" 136android:text="@string/idle_caption" 137android:layout_marginRight="15dp"/> 138< CheckBox 139android:layout_width="wrap_content" 140android:layout_height="wrap_content" 141android:id="@+id/checkbox_idle" 142android:text="@string/idle_mode_text"/> 143< /LinearLayout> 144 145< /LinearLayout> 146< Button 147android:id="@+id/schedule_button" 148android:layout_width="match_parent" 149android:layout_height="wrap_content" 150android:layout_marginTop="20dp" 151android:layout_marginLeft="40dp" 152android:layout_marginRight="40dp" 153android:onClick="scheduleJob" 154android:text="@string/schedule_job_button_text"/> 155< Button 156android:id="@+id/cancel_button" 157android:layout_width="match_parent" 158android:layout_height="wrap_content" 159android:layout_marginLeft="40dp" 160android:layout_marginRight="40dp" 161android:onClick="cancelAllJobs" 162android:text="@string/cancel_all_jobs_button_text"/> 163< /LinearLayout> 164 < /ScrollView>

好了,本篇到此结束,希望通过本篇介绍你能正确使用JobScheduler这个强大的工具,有些任务放在适合的时机来做更加适宜。
 

    推荐阅读