Android o新特性–後臺限制
Android8.0對系統資源的管控更加嚴格,添加了後臺限制規則。
如果滿足以下任意條件,應用
將被視爲處於前臺:
- 具有可見 Activity(不管該 Activity 已啓動還是已暫停)。
- 具有前臺服務。
- 另一個前臺應用已關聯到該應用(不管是通過綁定到其中一個服務,還是通過使用其中一個內容提供程序)。 例如,如果另一個應用綁定到該應用的服務,那麼該應用處於前臺:
- IME
- 壁紙服務
- 通知偵聽器
- 語音或文本服務
如果以上條件均不滿足,應用將被視爲處於後臺。
系統不允許後臺應用創建後臺服務。 因此,Android 8.0 引入了一種全新的方法,即 Context.startForegroundService(),以在前臺啓動新服務。
在系統創建服務後應用有五秒的時間來調用該服務的 startForeground() 方法以顯示新服務的用戶可見通知。
如果應用在此時間限制內未調用 startForeground(),則系統將停止服務並聲明此應用爲 ANR
。
Android強制開發者使用 JobScheduler 作業替換後臺服務的意思,使用jobIntentService就可以比較方便的使用JobScheduler這個工具。
特殊情況是可以創建後臺服務的:
- 處理對用戶可見的任務時,應用將被置於白名單中,後臺應用將被置於一個臨時白名單中並持續數分鐘。 位於白名單中時,應用可以無限制地啓動服務,並且其後臺服務也可以運行。
- 處理一條高優先級 Firebase 雲消息傳遞 (FCM) 消息。
- 接收廣播,例如短信/彩信消息。
- 從通知執行 PendingIntent。
- 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