JobService的使用及源码分析

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
  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
  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
  1. /** @hide */  
  2. public final IBinder onBind(Intent intent) {  
  3.     return mBinder.asBinder();  
  4. }  

很明显,这里采用的是AIDL方式。在看一下mBinder的定义:

[java]  view plain  copy
  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
  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
  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
  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来执行。


源码下载



原文地址:http://blog.csdn.net/fishle123/article/details/50790894

猜你喜欢

转载自blog.csdn.net/f2006116/article/details/80026576