轉載請註明出處: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的子類,如:
-
public class MyJobService extends JobService {
-
public static final String TAG = MyJobService.class.getSimpleName();
-
-
@Override
-
public boolean onStartJob(JobParameters params) {
-
Log.i(TAG, "onStartJob:" + params.getJobId());
-
Toast.makeText(MyJobService.this, "start job:" + params.getJobId(), Toast.LENGTH_SHORT).show();
-
jobFinished(params, false);
-
return false;
-
}
-
-
@Override
-
public boolean onStopJob(JobParameters params) {
-
Log.i(TAG, "onStopJob:" + params.getJobId());
-
return false;
-
}
-
-
-
}
在MyJobService中,onStartJob裏面的邏輯非常簡單:彈出一個Toast。定義完JobService之後,剩下的工作就是提交Job了,這裏我們在Activity中實現,用戶點擊button來提交任務。Activity的代碼如下:
-
public class MainActivity extends Activity {
-
-
public static final String TAG = MainActivity.class.getSimpleName();
-
private int mJobId = 0;
-
-
private EditText mDelayEditText;
-
private EditText mDeadlineEditText;
-
private RadioButton mWiFiConnectivityRadioButton;
-
private RadioButton mAnyConnectivityRadioButton;
-
private CheckBox mRequiresChargingCheckBox;
-
private CheckBox mRequiresIdleCheckbox;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
-
mDelayEditText = (EditText) findViewById(R.id.delay_time);
-
mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
-
mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
-
mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
-
mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
-
mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
-
}
-
-
public void onBtnClick(View view) {
-
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
-
ComponentName componentName = new ComponentName(MainActivity.this, MyJobService.class);
-
JobInfo.Builder builder = new JobInfo.Builder(++mJobId, componentName);
-
-
-
String delay = mDelayEditText.getText().toString();
-
if (delay != null && !TextUtils.isEmpty(delay)) {
-
-
builder.setMinimumLatency(Long.valueOf(delay) * 1000);
-
}
-
String deadline = mDeadlineEditText.getText().toString();
-
if (deadline != null && !TextUtils.isEmpty(deadline)) {
-
-
builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
-
}
-
boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
-
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
-
-
if (requiresUnmetered) {
-
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
-
} else if (requiresAnyConnectivity) {
-
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
-
}
-
builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
-
builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());
-
-
scheduler.schedule(builder.build());
-
Log.i(TAG, "schedule job:" + mJobId);
-
}
-
-
}
這裏重點看一下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)就可以很容易實現了。看一下源碼:
-
-
public final IBinder onBind(Intent intent) {
-
return mBinder.asBinder();
-
}
很明顯,這裏採用的是AIDL方式。在看一下mBinder的定義:
-
-
IJobService mBinder = new IJobService.Stub() {
-
@Override
-
public void startJob(JobParameters jobParams) {
-
ensureHandler();
-
Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);
-
m.sendToTarget();
-
}
-
@Override
-
public void stopJob(JobParameters jobParams) {
-
ensureHandler();
-
Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);
-
m.sendToTarget();
-
}
-
};
-
-
-
void ensureHandler() {
-
synchronized (mHandlerLock) {
-
if (mHandler == null) {
-
mHandler = new JobHandler(getMainLooper());
-
}
-
}
-
}
從這裏可以看到,JobService定義了一個IJobService接口,在這個接口裏面定義了startJob和stopJob兩個方法來讓JobScheduler調度我們的後臺任務的執行。這兩個方法的實現也很簡單,分別發送了MSG_EXECUTE_JOB和MSG_STOP_JOB兩個Message。ensureHandler從名字上看,應該就是用來初始化一個Handler吧。看一下源碼就知道了:
-
-
void ensureHandler() {
-
synchronized (mHandlerLock) {
-
if (mHandler == null) {
-
mHandler = new JobHandler(getMainLooper());
-
}
-
}
-
}
從這裏可以看到,在JobService裏面定義了一個JobHandler。注意下這裏使用的是getMainLooper(),因此,消息是在主線程中處理。繼續看JobHandler是怎麼處理這兩個消息的:
-
class JobHandler extends Handler {
-
JobHandler(Looper looper) {
-
super(looper);
-
}
-
-
@Override
-
public void handleMessage(Message msg) {
-
final JobParameters params = (JobParameters) msg.obj;
-
switch (msg.what) {
-
case MSG_EXECUTE_JOB:
-
try {
-
boolean workOngoing = JobService.this.onStartJob(params);
-
ackStartMessage(params, workOngoing);
-
} catch (Exception e) {
-
Log.e(TAG, "Error while executing job: " + params.getJobId());
-
throw new RuntimeException(e);
-
}
-
break;
-
case MSG_STOP_JOB:
-
try {
-
boolean ret = JobService.this.onStopJob(params);
-
ackStopMessage(params, ret);
-
} catch (Exception e) {
-
Log.e(TAG, "Application unable to handle onStopJob.", e);
-
throw new RuntimeException(e);
-
}
-
break;
-
case MSG_JOB_FINISHED:
-
final boolean needsReschedule = (msg.arg2 == 1);
-
IJobCallback callback = params.getCallback();
-
if (callback != null) {
-
try {
-
callback.jobFinished(params.getJobId(), needsReschedule);
-
} catch (RemoteException e) {
-
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
-
"away.");
-
}
-
} else {
-
Log.e(TAG, "finishJob() called for a nonexistent job id.");
-
}
-
break;
-
default:
-
Log.e(TAG, "Unrecognised message received.");
-
break;
-
}
-
}
-
......
-
}
從源碼中,可以很清楚的看到,在第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方法:
-
public JobInfo build() {
-
-
if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&
-
!mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE) {
-
throw new IllegalArgumentException("You're trying to build a job with no " +
-
"constraints, this is not allowed.");
-
}
-
mExtras = new PersistableBundle(mExtras);
-
-
if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
-
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
-
"periodic job.");
-
}
-
if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
-
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
-
"periodic job");
-
}
-
if (mBackoffPolicySet && mRequiresDeviceIdle) {
-
throw new IllegalArgumentException("An idle mode job will not respect any" +
-
" back-off policy, so calling setBackoffCriteria with" +
-
" setRequiresDeviceIdle is an error.");
-
}
-
return new JobInfo(this);
-
}
從第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來執行。
源碼下載