JobIntentService详解及使用

Android o新特性–后台限制

Android8.0对系统资源的管控更加严格,添加了后台限制规则。

如果满足以下任意条件,应用将被视为处于前台:

  1. 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  2. 具有前台服务。
  3. 另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:
  4. IME
  5. 壁纸服务
  6. 通知侦听器
  7. 语音或文本服务

如果以上条件均不满足,应用将被视为处于后台。

系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。

在系统创建服务后应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。

如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR

Android强制开发者使用 JobScheduler 作业替换后台服务的意思,使用jobIntentService就可以比较方便的使用JobScheduler这个工具。

特殊情况是可以创建后台服务的:

  1. 处理对用户可见的任务时,应用将被置于白名单中,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动服务,并且其后台服务也可以运行。
    1. 处理一条高优先级 Firebase 云消息传递 (FCM) 消息。
    2. 接收广播,例如短信/彩信消息。
    3. 从通知执行 PendingIntent。
  2. bindService不受后台限制

JobIntentService

JobIntentService实质为Service其继承关系如下所示。

 java.lang.Object
    ↳   android.content.Context
       ↳    android.content.ContextWrapper
           ↳    android.app.Service
               ↳    android.support.v4.app.JobIntentService

作用

Helper for processing work that has been enqueued for a job/service. When running on Android O or later, the work will be dispatched as a job via JobScheduler.enqueue. When running on older versions of the platform, it will use Context.startService.

官方文档解释为,用于处理被加入到job或service任务的一个辅助工具,8.0以下被当作普通的Intent使用startSerivce()启动service来执行。

8.0以上任务被作为job用jobScheduler.enqueue()方法来分发,
说到Jobscheduler,应该不陌生了,框架提供的用来APP调度任务的接口,根据APP要求构建JobInfo,系统会在适当的时间调用JobInfo指定的JobService来执行你的任务。

所以在Android8.0及以上JobIntentService和JobService做的事情是相同的,都是等着JobScheduler分配任务来执行。

不同点在于,JobService使用的handler使用的是主线程的Looper,因此需要在onStartJob()中手动创建AsyncTask去执行耗时任务,而JobIntentService则帮我们处理这一过程,使用它只需要写需要做的任务逻辑即可,不用关心卡住主线程的问题。另外,向jobScheduler传递任务操作也更简单了,不需要在指定JobInfo中的参数,直接enqueue(context,intent)就可以。

这有点像Service和IntentService的关系。

来看一段JobIntentService的源码
加入enqueue到onhandlework的过程。

//JobIntentService的入口方法
public static void enqueueWork(@NonNull Context context, @NonNull Class cls, int jobId,
            @NonNull Intent work) {
        if (work == null) {
            throw new IllegalArgumentException("work must not be null");
        }
        synchronized (sLock) {
            WorkEnqueuer we = getWorkEnqueuer(context, cls, true, jobId);//根据版本获取不同的WorkEnqueuer
            we.ensureJobId(jobId);//每个jobIntentService 唯一对应一个JobId,所有给这个service的work都必须相同
            we.enqueueWork(work);//调用WorkEnqueuer
        }
    }
static WorkEnqueuer getWorkEnqueuer(Context context, Class cls, boolean hasJobId, int jobId) {
        WorkEnqueuer we = sClassWorkEnqueuer.get(cls);
        if (we == null) {
            if (BuildCompat.isAtLeastO()) {
                if (!hasJobId) {
                    throw new IllegalArgumentException("Can't be here without a job id");
                }
                we = new JobWorkEnqueuer(context, cls, jobId);//8.0
            } else {
                we = new CompatWorkEnqueuer(context, cls);//8.0以前
            }
            sClassWorkEnqueuer.put(cls, we);
        }
        return we;
    }

    //8.0的WorkEnqueuer.enqueueWork()
    @Override
        void enqueueWork(Intent work) {
            if (DEBUG) Log.d(TAG, "Enqueueing work: " + work);
            mJobScheduler.enqueue(mJobInfo, new JobWorkItem(work));//调用JobScheduler.enqueue()
        }

看到最终调用了JobScheduler来调度任务,那么是给哪个service执行呢,看mJobInfo怎么build的

 JobWorkEnqueuer(Context context, Class cls, int jobId) {
            super(context, cls);
            ensureJobId(jobId);
            JobInfo.Builder b = new JobInfo.Builder(jobId, mComponentName);//使用mComponentName
            mJobInfo = b.setOverrideDeadline(0).build();
            mJobScheduler = (JobScheduler) context.getApplicationContext().getSystemService(
                    Context.JOB_SCHEDULER_SERVICE);
        }

使用mComponentName,那这个mComponentName在哪赋值

//JobWorkEnqueuer的父类
 WorkEnqueuer(Context context, Class cls) {
            mComponentName = new ComponentName(context, cls);

所以mComponentName就是在enqueueWork(@NonNull Context context, @NonNull Class cls, int jobId,@NonNull Intent work)中传入的service的类名,一般在调用JobIntentService时就会传入其子类的类名。

那么work在哪被处理的呢,按照jobService的逻辑找到onStartJob(),就是执行任务处理的地方

 public boolean onStartJob(JobParameters params) {
            if (DEBUG) Log.d(TAG, "onStartJob: " + params);
            mParams = params;
            // We can now start dequeuing work!
            mService.ensureProcessorRunningLocked();
            return true;
        }

        void ensureProcessorRunningLocked() {
            if (mCurProcessor == null) {
                mCurProcessor = new CommandProcessor();
                if (DEBUG) Log.d(TAG, "Starting processor: " + mCurProcessor);
                mCurProcessor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);//mCurProcessor是线程池的AsyncTask
        }
    }

    final class CommandProcessor extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            GenericWorkItem work;

            if (DEBUG) Log.d(TAG, "Starting to dequeue work...");

            while ((work = dequeueWork()) != null) {
                if (DEBUG) Log.d(TAG, "Processing next work: " + work);
                onHandleWork(work.getIntent());  //回调onHandleWork()
                if (DEBUG) Log.d(TAG, "Completing work: " + work);
                work.complete();
            }

            if (DEBUG) Log.d(TAG, "Done processing work!");

            return null;
        }

子类实现jobIntentService处理work就是实现onHandleWork,可以看到其使用线程池的AsyncTask来处理work的,所以不需要考虑主线程阻塞的问题。

案例演示

public class MainActivity extends Activity {

    Button btn ;
    static int num =0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent workIntent = new Intent();
                num++;
                Log.d("houson", "onClick: "+num);
                workIntent.putExtra("work","work num:"+num);
                MyJobIntentService.enqueueWork(getApplicationContext(),workIntent);
            }
        });
    }
}
public class MyJobIntentService extends JobIntentService {

    /**
     * 这个Service 唯一的id
     */
    static final int JOB_ID = 10111;

    /**
     * Convenience method for enqueuing work in to this service.
     */
    static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, MyJobIntentService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork( Intent intent) {
        Log.d("houson", "onHandleWork: "+intent.getStringExtra("work").toString());
    }
}

注意manifest!

<uses-permission android:name="android.permission.WAKE_LOCK">uses-permission>
<service android:name=".MyJobIntentService">service>

结果:

11-06 20:39:04.116 20653-20653/com.example.houson.jobintentservicedemo D/houson: onClick: 47
11-06 20:39:04.135 20653-20743/com.example.houson.jobintentservicedemo D/houson: onHandleWork: work num:47
11-06 20:39:04.216 20653-20653/com.example.houson.jobintentservicedemo D/houson: onClick: 48
11-06 20:39:04.234 20653-20745/com.example.houson.jobintentservicedemo D/houson: onHandleWork: work num:48

总结

由于Android O的后台限制,创建后台服务需要使用JobScheduler来由系统进行调度任务的执行,而使用JobService的方式比较繁琐,8.0及以上提供了JobIntentService帮助开发者更方便的将任务交给JobScheduler调度,其本质是Service后台任务在他的OnhandleWork()中进行,子类重写该方法即可。使用较简单。

注意
1.需要添加android.permission.WAKE_LOCK权限,JobIntentService处理了亮屏/锁屏,因此要此权限。
2.注册JobintentService也是service

你可能感兴趣的:(Android,多线程并发)