zoukankan      html  css  js  c++  java
  • Android 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的子类,如:

    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);//任务执行完后记得调用jobFinsih通知系统释放相关资源
            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)) {
                //设置JobService执行的最小延时时间
                builder.setMinimumLatency(Long.valueOf(delay) * 1000);
            }
            String deadline = mDeadlineEditText.getText().toString();
            if (deadline != null && !TextUtils.isEmpty(deadline)) {
                //设置JobService执行的最晚时间
                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());//是否要求设备为idle状态
            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)就可以很容易实现了。看一下源码:

    /** @hide */
    public final IBinder onBind(Intent intent) {
        return mBinder.asBinder();
    }

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

    /** Binder for this service. */
    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();
        }
    };
    
    /** @hide */
    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吧。看一下源码就知道了:

    /** @hide */
    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() {
        // Allow jobs with no constraints - What am I, a database?
        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);  // Make our own copy.
        // Check that a deadline was not set on a periodic job.
        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来执行。

    源码下载

  • 相关阅读:
    日期类型存储方法
    Log4j2的一些记录
    【Maven】学习记录
    HTML 图片加载问题
    浏览器的组成
    javascript数组的实例属性(方法)
    javascript数组的内置对象Array
    javascript之this
    css的position,float属性的理解
    简单介绍帧动画
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/5972474.html
Copyright © 2011-2022 走看看