安卓电量优化之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
4 public boolean onStartJob(JobParameters params) {
5
6 return false;
7 }
8
9 @Override
10 public boolean onStopJob(JobParameters params) {
11
12 return 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
2 android:name=".TestJobService"
3 android: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 {
2 private static final String TAG = "SyncService";
3
4 @Override
5 public void onCreate() {
6 super.onCreate();
7 Log.i(TAG, "Service created");
8 }
9
10 @Override
11 public void onDestroy() {
12 super.onDestroy();
13 Log.i(TAG, "Service destroyed");
14 }
15
16
17 @Override
18 public int onStartCommand(Intent intent, int flags, int startId) {
19 Messenger callback = intent.getParcelableExtra("messenger");
20 Message m = Message.obtain();
21 m.what = MainActivity.MSG_SERVICE_OBJ;
22 m.obj = this;
23 try {
24 callback.send(m);
25 } catch (RemoteException e) {
26 Log.e(TAG, "Error passing service object back to activity.");
27 }
28 return START_NOT_STICKY;
29 }
30
31 @Override
32 public 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.
36 jobParamsMap.add(params);
37 if (mActivity != null) {
38 mActivity.onReceivedStartJob(params);
39 }
40 Log.i(TAG, "on start job: " + params.getJobId());
41 return true;
42 }
43
44 @Override
45 public boolean onStopJob(JobParameters params) {
46 // Stop tracking these job parameters, as we've 'finished' executing.
47 jobParamsMap.remove(params);
48 if (mActivity != null) {
49 mActivity.onReceivedStopJob();
50 }
51 Log.i(TAG, "on stop job: " + params.getJobId());
52 return true;
53 }
54
55 MainActivity mActivity;
56 private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>();
57
58 public void setUiCallback(MainActivity activity) {
59 mActivity = activity;
60 }
61
62 /** Send job to the JobScheduler. */
63 public void scheduleJob(JobInfo t) {
64 Log.d(TAG, "Scheduling job");
65 JobScheduler tm =
66 (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
67 if(tm.schedule(t)<=0){
68 Log.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 */
78 public boolean callJobFinished() {
79 JobParameters params = jobParamsMap.poll();
80 if (params == null) {
81 return false;
82 } else {
83 jobFinished(params, false);
84 return true;
85 }
86 }
87 }
上面提到过JobService其实也是Service,同样具有相应生命周期方法。TestJobService先不做详细讲解,但是要看到这个类中封装了一些JobScheduler方法供外界调用。
接下来我们结合MainActivity类一起看,MainActivity源码如下:
1 public class MainActivity extends Activity {
2
3 private static final String TAG = "MainActivity";
4
5 public static final int MSG_UNCOLOUR_START = 0;
6 public static final int MSG_UNCOLOUR_STOP = 1;
7 public static final int MSG_SERVICE_OBJ = 2;
8 // UI fields.
9 int defaultColor;
10 int startJobColor;
11 int stopJobColor;
12
13 private TextView mShowStartView;
14 private TextView mShowStopView;
15 private TextView mParamsTextView;
16 private EditText mDelayEditText;
17 private EditText mDeadlineEditText;
18 private RadioButton mWiFiConnectivityRadioButton;
19 private RadioButton mAnyConnectivityRadioButton;
20 private CheckBox mRequiresChargingCheckBox;
21 private CheckBox mRequiresIdleCheckbox;
22 ComponentName mServiceComponent;
23 TestJobService mTestService;
24 //
25 private static int kJobId = 0;
26
27 @Override
28 public void onCreate(Bundle savedInstanceState) {
29 super.onCreate(savedInstanceState);
30 setContentView(R.layout.sample_main);
31 Resources res = getResources();
32 defaultColor = res.getColor(R.color.none_received);
33 startJobColor = res.getColor(R.color.start_received);
34 stopJobColor = res.getColor(R.color.stop_received);
35 // Set up UI.
36 mShowStartView = (TextView) findViewById(R.id.onstart_textview);
37 mShowStopView = (TextView) findViewById(R.id.onstop_textview);
38 mParamsTextView = (TextView) findViewById(R.id.task_params);
39 mDelayEditText = (EditText) findViewById(R.id.delay_time);
40 mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
41 mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
42 mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
43 mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
44 mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
45 mServiceComponent = new ComponentName(this, TestJobService.class);
46 // Start service and provide it a way to communicate with us.
47 Intent startServiceIntent = new Intent(this, TestJobService.class);
48 startServiceIntent.putExtra("messenger", new Messenger(mHandler));
49 startService(startServiceIntent);
50 }
51
52 Handler mHandler = new Handler(/* default looper */) {
53 @Override
54 public void handleMessage(Message msg) {
55 switch (msg.what) {
56 case MSG_UNCOLOUR_START:
57 mShowStartView.setBackgroundColor(defaultColor);
58 break;
59 case MSG_UNCOLOUR_STOP:
60 mShowStopView.setBackgroundColor(defaultColor);
61 break;
62 case MSG_SERVICE_OBJ:
63 mTestService = (TestJobService) msg.obj;
64 mTestService.setUiCallback(MainActivity.this);
65 }
66 }
67 };
68
69 private boolean ensureTestService() {
70 if (mTestService == null) {
71 Toast.makeText(MainActivity.this, "Service null, never got callback?",
72 Toast.LENGTH_SHORT).show();
73 return false;
74 }
75 return 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")
83 public void scheduleJob(View v) {
84 if (!ensureTestService()) {
85 return;
86 }
87 JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);
88
89 String delay = mDelayEditText.getText().toString();
90 if (delay != null && !TextUtils.isEmpty(delay)) {
91 builder.setMinimumLatency(Long.valueOf(delay) * 1000);
92 }
93 String deadline = mDeadlineEditText.getText().toString();
94 if (deadline != null && !TextUtils.isEmpty(deadline)) {
95 builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
96 }
97 boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
98 boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
99 if (requiresUnmetered) {
100 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
101 } else if (requiresAnyConnectivity) {
102 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
103 }
104 builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
105 builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());
106 mTestService.scheduleJob(builder.build());
107 }
108 /**
109 * cancel All jobs
110 * @param v
111 */
112 @SuppressLint("NewApi")
113 public void cancelAllJobs(View v) {
114 JobScheduler tm =
115 (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
116 tm.cancelAll();
117 }
118
119 /**
120 * UI onclick listener to call jobFinished() in our service.
121 */
122 public void finishJob(View v) {
123 if (!ensureTestService()) {
124 return;
125 }
126 mTestService.callJobFinished();
127 mParamsTextView.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")
136 public void onReceivedStartJob(JobParameters params) {
137 mShowStartView.setBackgroundColor(startJobColor);
138 Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START);
139 mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second.
140 mParamsTextView.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 */
149 public void onReceivedStopJob() {
150 mShowStopView.setBackgroundColor(stopJobColor);
151 Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP);
152 mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second.
153 mParamsTextView.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"
2 android:layout_width="match_parent"
3 android:layout_height="match_parent"
4 android:orientation="vertical" >
5
6 <LinearLayout
7 android:layout_width="match_parent"
8 android:layout_height="match_parent"
9 android:layout_weight="1"
10 android:orientation="vertical">
11
12 <LinearLayout
13 android:layout_width="match_parent"
14 android:layout_height="100dp">
15 <TextView
16 android:id="@+id/onstart_textview"
17 android:layout_width="wrap_content"
18 android:layout_height="match_parent"
19 android:layout_weight="1"
20 android:background="#999999"
21 android:gravity="center"
22 android:text="@string/onstarttask"/>
23 <TextView
24 android:id="@+id/onstop_textview"
25 android:layout_width="wrap_content"
26 android:layout_height="match_parent"
27 android:layout_weight="1"
28 android:background="@color/none_received"
29 android:gravity="center"
30 android:text="@string/onstoptask"/>
31 </LinearLayout>
32 <Button
33 android:id="@+id/finished_button"
34 android:layout_width="match_parent"
35 android:layout_height="wrap_content"
36 android:padding="20dp"
37 android:layout_marginBottom="5dp"
38 android:onClick="finishJob"
39 android:text="@string/finish_job_button_text"/>
40
41 <TextView
42 android:id="@+id/task_params"
43 android:layout_width="match_parent"
44 android:layout_height="wrap_content"
45 android:text="@string/defaultparamtext"
46 android:gravity="center"
47 android:textSize="20dp"
48 android:padding="15dp"
49 android:layout_marginBottom="10dp" />
50
51 <TextView
52 android:layout_width="match_parent"
53 android:layout_height="wrap_content"
54 android:text="@string/constraints"
55 android:layout_margin="15dp"
56 android:textSize="18dp"/>
57 <LinearLayout
58 android:layout_width="match_parent"
59 android:layout_height="wrap_content"
60 android:orientation="vertical"
61 android:layout_marginLeft="10dp">
62 <LinearLayout
63 android:layout_width="match_parent"
64 android:layout_height="wrap_content">
65 <TextView
66 android:layout_width="wrap_content"
67 android:layout_height="wrap_content"
68 android:text="@string/connectivity"
69 android:layout_marginRight="10dp"/>
70 <RadioGroup
71 android:layout_width="wrap_content"
72 android:layout_height="wrap_content"
73 android:orientation="horizontal">
74 <RadioButton android:id="@+id/checkbox_any"
75 android:layout_width="wrap_content"
76 android:layout_height="wrap_content"
77 android:text="@string/any"
78 android:checked="false" />
79 <RadioButton android:id="@+id/checkbox_unmetered"
80 android:layout_width="wrap_content"
81 android:layout_height="wrap_content"
82 android:text="@string/unmetered"
83 android:checked="false" />
84 </RadioGroup>
85
86 </LinearLayout>
87 <LinearLayout
88 android:layout_width="match_parent"
89 android:layout_height="wrap_content">
90 <TextView
91 android:layout_width="wrap_content"
92 android:layout_height="wrap_content"
93 android:text="@string/timing"/>
94 <TextView
95 android:layout_width="wrap_content"
96 android:layout_height="wrap_content"
97 android:layout_marginLeft="15dp"
98 android:textSize="17dp"
99 android:text="@string/delay"/>
100 <EditText
101 android:id="@+id/delay_time"
102 android:layout_width="60dp"
103 android:layout_height="wrap_content"
104 android:inputType="number"/>
105 <TextView
106 android:layout_width="wrap_content"
107 android:layout_height="wrap_content"
108 android:text="@string/deadline"
109 android:textSize="17dp"/>
110 <EditText
111 android:id="@+id/deadline_time"
112 android:layout_width="60dp"
113 android:layout_height="wrap_content"
114 android:inputType="number"/>
115 </LinearLayout>
116 <LinearLayout
117 android:layout_width="match_parent"
118 android:layout_height="wrap_content">
119 <TextView
120 android:layout_width="wrap_content"
121 android:layout_height="wrap_content"
122 android:text="@string/charging_caption"
123 android:layout_marginRight="15dp"/>
124 <CheckBox
125 android:layout_width="wrap_content"
126 android:layout_height="wrap_content"
127 android:id="@+id/checkbox_charging"
128 android:text="@string/charging_text"/>
129 </LinearLayout>
130 <LinearLayout
131 android:layout_width="match_parent"
132 android:layout_height="wrap_content">
133 <TextView
134 android:layout_width="wrap_content"
135 android:layout_height="wrap_content"
136 android:text="@string/idle_caption"
137 android:layout_marginRight="15dp"/>
138 <CheckBox
139 android:layout_width="wrap_content"
140 android:layout_height="wrap_content"
141 android:id="@+id/checkbox_idle"
142 android:text="@string/idle_mode_text"/>
143 </LinearLayout>
144
145 </LinearLayout>
146 <Button
147 android:id="@+id/schedule_button"
148 android:layout_width="match_parent"
149 android:layout_height="wrap_content"
150 android:layout_marginTop="20dp"
151 android:layout_marginLeft="40dp"
152 android:layout_marginRight="40dp"
153 android:onClick="scheduleJob"
154 android:text="@string/schedule_job_button_text"/>
155 <Button
156 android:id="@+id/cancel_button"
157 android:layout_width="match_parent"
158 android:layout_height="wrap_content"
159 android:layout_marginLeft="40dp"
160 android:layout_marginRight="40dp"
161 android:onClick="cancelAllJobs"
162 android:text="@string/cancel_all_jobs_button_text"/>
163 </LinearLayout>
164 </ScrollView>
好了,本篇到此结束,希望通过本篇介绍你能正确使用JobScheduler这个强大的工具,有些任务放在适合的时机来做更加适宜。