版權聲明:本文出自汪磊的博客,轉載請務必注明出處。
一、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這個強大的工具,有些任務放在適合的時機來做更加適宜。