JobService的使用及源碼分析

轉載請註明出處:http://blog.csdn.net/fishle123/article/details/50790894

Google在Android 5.0中引入JobScheduler來執行一些需要滿足特定條件但不緊急的後臺任務,APP利用JobScheduler來執行這些特殊的後臺任務時來減少電量的消耗。本文首先介紹JobSerice的使用方法,然後分析JobService的源碼實現。

JobService的使用

使用JobScheduler的時候需要把待執行的後臺任務封裝到JobService中提交。下面就來介紹JobService的使用,首先看一下JobService是什麼東東。

 

從上面的截圖,可以看出JobService繼承自Service,並且是一個抽象類。在JobService中有兩個抽象方法onStartJob(JobParameters)onStopJob(JobParameters)。onStartJob在JobService被調度到的時候會執行,我們只需要繼承JobService然後重寫onStartJob方法,並在裏面執行我們的後臺任務就可以了。

下面給出一個JobService的使用實例。

首先,定義一個JobService的子類,如:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class MyJobService extends JobService {  
  2.     public static final String TAG = MyJobService.class.getSimpleName();  
  3.   
  4.     @Override  
  5.     public boolean onStartJob(JobParameters params) {  
  6.         Log.i(TAG, "onStartJob:" + params.getJobId());  
  7.         Toast.makeText(MyJobService.this"start job:" + params.getJobId(), Toast.LENGTH_SHORT).show();  
  8.         jobFinished(params, false);//任務執行完後記得調用jobFinsih通知系統釋放相關資源  
  9.         return false;  
  10.     }  
  11.   
  12.     @Override  
  13.     public boolean onStopJob(JobParameters params) {  
  14.         Log.i(TAG, "onStopJob:" + params.getJobId());  
  15.         return false;  
  16.     }  
  17.   
  18.   
  19. }  

在MyJobService中,onStartJob裏面的邏輯非常簡單:彈出一個Toast。定義完JobService之後,剩下的工作就是提交Job了,這裏我們在Activity中實現,用戶點擊button來提交任務。Activity的代碼如下:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public class MainActivity extends Activity {  
  2.   
  3.     public static final String TAG = MainActivity.class.getSimpleName();  
  4.     private int mJobId = 0;  
  5.   
  6.     private EditText mDelayEditText;  
  7.     private EditText mDeadlineEditText;  
  8.     private RadioButton mWiFiConnectivityRadioButton;  
  9.     private RadioButton mAnyConnectivityRadioButton;  
  10.     private CheckBox mRequiresChargingCheckBox;  
  11.     private CheckBox mRequiresIdleCheckbox;  
  12.   
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.   
  18.         mDelayEditText = (EditText) findViewById(R.id.delay_time);  
  19.         mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);  
  20.         mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);  
  21.         mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);  
  22.         mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);  
  23.         mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);  
  24.     }  
  25.   
  26.     public void onBtnClick(View view) {  
  27.         JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);  
  28.         ComponentName componentName = new ComponentName(MainActivity.this, MyJobService.class);  
  29.         JobInfo.Builder builder = new JobInfo.Builder(++mJobId, componentName);  
  30.   
  31.   
  32.         String delay = mDelayEditText.getText().toString();  
  33.         if (delay != null && !TextUtils.isEmpty(delay)) {  
  34.             //設置JobService執行的最小延時時間  
  35.             builder.setMinimumLatency(Long.valueOf(delay) * 1000);  
  36.         }  
  37.         String deadline = mDeadlineEditText.getText().toString();  
  38.         if (deadline != null && !TextUtils.isEmpty(deadline)) {  
  39.             //設置JobService執行的最晚時間  
  40.             builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);  
  41.         }  
  42.         boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();  
  43.         boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();  
  44.         //設置執行的網絡條件  
  45.         if (requiresUnmetered) {  
  46.             builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);  
  47.         } else if (requiresAnyConnectivity) {  
  48.             builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);  
  49.         }  
  50.         builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());//是否要求設備爲idle狀態  
  51.         builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());//是否要設備爲充電狀態  
  52.   
  53.         scheduler.schedule(builder.build());  
  54.         Log.i(TAG, "schedule job:" + mJobId);  
  55.     }  
  56.     //......  
  57.     }  

這裏重點看一下26----55行,在button的單擊事件響應中,先通過getSystemService拿到系統的JobScheduler,然後使用JobInfo.Buidler來構造一個後臺任務,具體看28----55行。在設置後臺任務的參數時,需要特別注意的是:以下五個約束條件我們需要至少指定其中的一個,否則調用JobInfo.Buidler的build方法時會拋異常,導致後臺任務構造失敗。五個約束條件如下:

1)最小延時

2)最晚執行時間

3)需要充電

4)需要設備爲idle(空閒)狀態(一般很難達到這個條件吧)

5)聯網狀態(NETWORK_TYPE_NONE--不需要網絡,NETWORK_TYPE_ANY--任何可用網絡,NETWORK_TYPE_UNMETERED--不按用量計費的網絡)

其實仔細想一想也有道理,其實約束條件決定了JobService在什麼時候執行,如果都沒指定,系統就不知道在什麼來執行我們的JobService了。如果我們的後臺任務滿足以上的一個或多個條件,就可以考慮是不是應該用JobService來執行。

運行效果如下:


JobService源碼分析

JobService內部的運行機制究竟是怎樣的?既然繼承子Service,那麼它至少要重寫onStartCommand或者onBind。實際上JobService選擇的是重寫onBind。爲什麼使用bind方式呢?上面有提到,JobService是通過JobScheduler來調度,很明顯這裏會涉及到跨進程通信,如果使用AIDL(當然也可以使用Messenger)就可以很容易實現了。看一下源碼:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** @hide */  
  2. public final IBinder onBind(Intent intent) {  
  3.     return mBinder.asBinder();  
  4. }  

很明顯,這裏採用的是AIDL方式。在看一下mBinder的定義:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** Binder for this service. */  
  2. IJobService mBinder = new IJobService.Stub() {  
  3.     @Override  
  4.     public void startJob(JobParameters jobParams) {  
  5.         ensureHandler();  
  6.         Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);  
  7.         m.sendToTarget();  
  8.     }  
  9.     @Override  
  10.     public void stopJob(JobParameters jobParams) {  
  11.         ensureHandler();  
  12.         Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);  
  13.         m.sendToTarget();  
  14.     }  
  15. };  
  16.   
  17. /** @hide */  
  18. void ensureHandler() {  
  19.     synchronized (mHandlerLock) {  
  20.         if (mHandler == null) {  
  21.             mHandler = new JobHandler(getMainLooper());  
  22.         }  
  23.     }  
  24. }  

從這裏可以看到,JobService定義了一個IJobService接口,在這個接口裏面定義了startJob和stopJob兩個方法來讓JobScheduler調度我們的後臺任務的執行。這兩個方法的實現也很簡單,分別發送了MSG_EXECUTE_JOB和MSG_STOP_JOB兩個Message。ensureHandler從名字上看,應該就是用來初始化一個Handler吧。看一下源碼就知道了:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** @hide */  
  2. void ensureHandler() {  
  3.     synchronized (mHandlerLock) {  
  4.         if (mHandler == null) {  
  5.             mHandler = new JobHandler(getMainLooper());  
  6.         }  
  7.     }  
  8. }  

從這裏可以看到,在JobService裏面定義了一個JobHandler。注意下這裏使用的是getMainLooper(),因此,消息是在主線程中處理。繼續看JobHandler是怎麼處理這兩個消息的:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. class JobHandler extends Handler {  
  2.     JobHandler(Looper looper) {  
  3.         super(looper);  
  4.     }  
  5.   
  6.     @Override  
  7.     public void handleMessage(Message msg) {  
  8.         final JobParameters params = (JobParameters) msg.obj;  
  9.         switch (msg.what) {  
  10.             case MSG_EXECUTE_JOB:  
  11.                 try {  
  12.                     boolean workOngoing = JobService.this.onStartJob(params);  
  13.                     ackStartMessage(params, workOngoing);  
  14.                 } catch (Exception e) {  
  15.                     Log.e(TAG, "Error while executing job: " + params.getJobId());  
  16.                     throw new RuntimeException(e);  
  17.                 }  
  18.                 break;  
  19.             case MSG_STOP_JOB:  
  20.                 try {  
  21.                     boolean ret = JobService.this.onStopJob(params);  
  22.                     ackStopMessage(params, ret);  
  23.                 } catch (Exception e) {  
  24.                     Log.e(TAG, "Application unable to handle onStopJob.", e);  
  25.                     throw new RuntimeException(e);  
  26.                 }  
  27.                 break;  
  28.             case MSG_JOB_FINISHED:  
  29.                 final boolean needsReschedule = (msg.arg2 == 1);  
  30.                 IJobCallback callback = params.getCallback();  
  31.                 if (callback != null) {  
  32.                     try {  
  33.                         callback.jobFinished(params.getJobId(), needsReschedule);  
  34.                     } catch (RemoteException e) {  
  35.                         Log.e(TAG, "Error reporting job finish to system: binder has gone" +  
  36.                                 "away.");  
  37.                     }  
  38.                 } else {  
  39.                     Log.e(TAG, "finishJob() called for a nonexistent job id.");  
  40.                 }  
  41.                 break;  
  42.             default:  
  43.                 Log.e(TAG, "Unrecognised message received.");  
  44.                 break;  
  45.         }  
  46.     }  
  47.     ......//省略部分代碼  
  48.  }  

從源碼中,可以很清楚的看到,在第10----18行,處理在startJob中發出的消息,這裏會調用JobService.this.onStartJob(params)來執行任務,在第19----27調用JobService.this.onStopJob(params)來通知我們需要停止任務了。如果我們的後臺任務需要在wifi可用的時候才執行的話,如果在任務執行的過程中wifi斷開了,那麼系統就調用onStopService來通知我們停止運行。

再次強調一下,JobService中的後臺任務是在主線程中執行,這裏一定不能執行耗時的任務。雖然在JobService中使用了Binder,但是最後還是通過Handler將任務調度到主線程中來執行。

在上面的例子用,有提到在JobInfo.Builder中配置JobService的時候需要指定至少一個約束(觸發)條件,否則會拋出異常,這裏我們也看一下JobInfo.Builder的build方法:

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public JobInfo build() {  
  2.     // Allow jobs with no constraints - What am I, a database?  
  3.     if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&  
  4.             !mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE) {  
  5.         throw new IllegalArgumentException("You're trying to build a job with no " +  
  6.                 "constraints, this is not allowed.");  
  7.     }  
  8.     mExtras = new PersistableBundle(mExtras);  // Make our own copy.  
  9.     // Check that a deadline was not set on a periodic job.  
  10.     if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {  
  11.         throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +  
  12.                 "periodic job.");  
  13.     }  
  14.     if (mIsPeriodic && (mMinLatencyMillis != 0L)) {  
  15.         throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +  
  16.                 "periodic job");  
  17.     }  
  18.     if (mBackoffPolicySet && mRequiresDeviceIdle) {  
  19.         throw new IllegalArgumentException("An idle mode job will not respect any" +  
  20.                 " back-off policy, so calling setBackoffCriteria with" +  
  21.                 " setRequiresDeviceIdle is an error.");  
  22.     }  
  23.     return new JobInfo(this);  
  24. }  

從第3----7行,可知,如果5個約束條件都沒有指定的時候,會拋出IllegalArgumentException。其實仔細想一想也有道理,其實約束條件決定了JobService在什麼時候執行,如果都沒指定,系統就不知道在什麼來執行我們的JobService了。

總結

最後,總結一下JobService的使用:

1)先繼承JobService,並重寫startJob和stopJob

2)在manifest.xml中聲明JobService的時候,記得一定要加上

android:permission=”android.permission.BIND_JOB_SERVICE”

3)後臺任務不能執行耗時任務,如果一定要這麼做,一定要再起一個線程去做,使用 thread/handler/AsyncTask都可以。

4)JobService一定要設置至少一個執行條件,如有網絡連接、充電中、系統空閒...

5)任務執行完後記得調用jobFinish通知系統釋放相關資源

如果我們的後臺任務滿足JobService的一個或多個約束條件,就可以考慮是不是應該用JobService來執行。


源碼下載

發佈了28 篇原創文章 · 獲贊 36 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章