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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章