Nothing makes an android developer more crazy than a new version of Android.
Android O 在 2017 年 8 月已經(jīng)正式發(fā)布了,Android P 的預覽版也已推出,今年下半年也將正式發(fā)布。
由于公司的產(chǎn)品面向海外用戶較多,大多都已經(jīng)是 Android 最新版本,且 Google 明確提出:從 2018 年 8 月起,所有向 Google Play 提交的新應用都必須使用 API level 26 (Android 8.0) 及以上版本開發(fā);2018 年 11 月起,所有 Google Play 的現(xiàn)有應用更新同樣必須使用 API level 26 及以上版本。因此必須對 Android O 作全面的適配。
1. Android O 新特性
官方文檔已經(jīng)詳細描述了 Android O 的新特性和 API,對開發(fā)者而言適配 Android O 需要關注以下行為變更:
- 后臺服務限制
- 隱式廣播限制
- 后臺位置限制
- 通知渠道
- 權限
2. 后臺服務限制
2.1 什么是前臺應用
滿足以下任意條件的應用被視為處于前臺:
- 具有可見的 Activity
- 具有前臺服務
- 另一個前臺應用關聯(lián)到該應用,如輸入法、壁紙服務、語音服務等
如果以上條件均不滿足,應用將被視為處于后臺。
2.2 為什么要限制后臺服務
后臺服務(如網(wǎng)絡下載、數(shù)據(jù)同步等)會消耗手機的內存和電量,影響性能。如果大量應用都開啟了后臺服務,會嚴重影響用戶體驗。
后臺服務限制和隱式廣播限制從根本上杜絕了后臺應用異常消耗系統(tǒng)資源,有助于大幅度降低應用后臺行為對設備體驗的影響。
2.3 什么是后臺服務限制
應用在后臺期間保留其后臺服務的能力將受到限制。如果應用處于后臺時調用了 startService() 將會拋出 IllegalStateException,除非:
-
應用已經(jīng)處于前臺,則可以調用
startService(),不會拋出IllegalStateException,但一旦進入后臺,后臺應用將被置于一個臨時白名單中,位于白名單中時,應用可以無限制地啟動服務,其后臺服務也可以運行。但這個時間窗一過,應用進入空閑狀態(tài),后臺服務就會被銷毀(Nexus 5X 8.0 系統(tǒng)上測試不到1分鐘) - 啟動前臺服務
- 綁定服務,即使應用處于后臺也不受影響
如果
targetSdkVersion < 26,是否可以繞過這些限制?
不可以,即使 targetSdkVersion < 26,用戶也可以在 Android O 的設備上選擇開啟這些限制。
2.4 解決方案
- Job Scheduler
- Foreground Service
- Firebase Cloud Messaging and Temporary Service Whitelist
2.5 Job Scheduler
JobScheduler is smarter about when jobs should be run and can batch them together so that devices stay asleep as much as possible.
Google 在 Android 5.0 中引入 JobScheduler 來執(zhí)行一些需要滿足特定條件但不緊急的后臺任務,利用 JobScheduler 來執(zhí)行這些特殊的后臺任務來減少電量的消耗。可以將其理解成定時任務,以替代 IntentService + AlarmManager。
開發(fā)者可以設定需要執(zhí)行的任務 JobService,以及任務執(zhí)行的條件 JobInfo,JobScheduler 會將任務加入到隊列。在特定的條件滿足時 Android 系統(tǒng)會去批量的執(zhí)行所有應用的這些任務,而非對每個應用的每個任務單獨處理。這樣可以減少設備被喚醒的次數(shù)。
2.5.1 JobService
先來看一下 JobService, 它繼承自 Service,除了 Service 的一些生命周期方法,又增加了 onStartJob 和 onStopJob 來處理自定義任務。
public class MyJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "onStartJob");
doJob(params);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "onStopJob");
// whether or not you would like JobScheduler to automatically retry your failed job.
return false;
}
private void doJob(JobParameters params) {
// I am on the main thread, so if you need to do background work,
// be sure to start up an AsyncTask, Thread, or IntentService!
}
}
使用 JobService 需要在清單里申請 android.permission.BIND_JOB_SERVICE 權限。
<service android:name=".MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
需要注意的是,在 com.android.server.job.JobServiceContext 類中聲明了 EXECUTING_TIMESLICE_MILLIS。
/** Amount of time a job is allowed to execute for before being considered timed-out. */
private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
經(jīng)過測試,不管應用是否處于前臺,JobService 都不能無限期運行,有 10 分鐘的超時時間,會自動銷毀,在 Android L 上這個時間是 1 分鐘。因此 Job Scheduler 適用于短耗時的后臺任務,不適用于連續(xù)的長時間的后臺服務。
2.5.2 使用 Job Scheduler
實施一個 Job 包含以下步驟:
-
JobInfo:采用 Builder 模式,設置 Job 執(zhí)行的條件和時機 -
JobService:Service的子類,Job 執(zhí)行時的具體行為 -
android.permission.BIND_JOB_SERVICE:JobService的子類需要授予該權限 -
JobScheduler:將一個 Job 添加到工作隊列中,調用JobScheduler.enqueue()或者JobScheduler.schedule。當工作隊列運行時,它可以將待定的工作從隊列中剝離并處理(替代IntentService)。
void scheduleJob() {
ComponentName componentName = new ComponentName(this, MyService.class);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, componentName)
.setMinimumLatency(2000)
.setOverrideDeadline(5000)
// ...
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
}
具體使用可以參考 Scheduling jobs like a pro with JobScheduler
2.5.3 在 API 低于 21 使用 Job Scheduler
JobScheduler 是Google 在 API 21 引入 的,那么對于 API 小于 21 該如何處理。
- minSdkVersion >= 21:JobScheduler
- minSdkVersion < 21:Firebase JobDispatcher,API 和
JobScheduler基本相似,但需要引入 Google Play Services,會增加 apk 的大小。 - 如果 minSdkVersion < 21,可以做一層封裝,業(yè)務方只調一套,也可以只用Firebase JobDispatcher 兼容,看業(yè)務方需求。
2.5.4 JobIntentService
JobIntentService 繼承自 Service,可以用來簡化處理任務。調用 JobIntentService.enqueueWork(),即可執(zhí)行任務。它會根據(jù) API 版本進一步執(zhí)行
- 在 Android O 及之后的設備上,調用
JobScheduler.enqueue() - 在 Android O 之前的設備上,調用
Context.startService()。
JobIntentService 需要 android.permission.WAKE_LOCK 的權限
<uses-permission android:name=”android.permission.WAKE_LOCK” />
2.6 Foreground Service
Job Scheduler 只適用于短耗時的后臺任務,如果需要在后臺執(zhí)行長期的任務,推薦使用 Foreground Service。這樣應用會在通知欄展示進行中的通知,以告知用戶你的應用正在運行后臺任務。
在 Android O 之前,創(chuàng)建前臺服務的方式通常是先創(chuàng)建一個后臺服務,然后將該服務推到前臺。
但對于 Android O,系統(tǒng)不允許后臺應用創(chuàng)建后臺服務。 因此,Android O 引入了一種全新的方法,即
ContextCompat.startForegroundService(),以在前臺啟動新服務。
調用
ContextCompat.startForegroundService()可以創(chuàng)建一個前臺服務,相當于創(chuàng)建一個后臺服務并將它推到前臺。創(chuàng)建一個用戶可見的
Notification。必須立即(在5秒內)調用該服務的
startForeground(id: Int, notification: Notification)方法,否則將停止服務并拋出android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()異常。
2.7 Firebase Cloud Messaging and Temporary Service Whitelist
使用 FCM 需要引入不低于 10.2.1 的 Google Play Services SDK。
當應用出現(xiàn)以下情況時,該應用可以被暫時加入到一個白名單里,應用可以像跑前臺服務一樣跑后臺服務:
- 處理高優(yōu)先級的 FCM 消息
- 接收廣播,如短信/彩信消息
- 點擊通知執(zhí)行 PendingIntent
這種情況常用的一個場景是,我們的應用需要通過請求后臺服務更新數(shù)據(jù),可以給我們的應用發(fā)送一個高優(yōu)先級的 FCM 消息,即使系統(tǒng)處于休眠狀態(tài),也能馬上收到 FCM 消息,被加到白名單后,就可以啟動一個后臺服務來更新數(shù)據(jù)了。
通過以上情況啟動的也必須是短耗時的任務,如
JobScheduler或者JobIntentService。
2.8 解決方案
- 對于短耗時的特定任務,采用
Job Scheduler。 - 對于需要長期執(zhí)行的服務,采用
Foreground Service。 - 對于一些三方的服務,無法修改,如果沒有適配 Android O,可以在啟動這些服務前啟動一個空的
Foreground Service,這樣應用處于前臺,就可以啟動這些后臺服務了。
如果在應用初始化后啟動一個空的
Foreground Service,保證應用處于前臺,則舊的Service都可以正常,修改量最小。但不推薦。
3. 隱式廣播限制
3.1 為什么要限制隱式廣播
如果在 AndroidManifest.xml 中隨意地聲明隱式廣播,那么任何時候收到響應系統(tǒng)事件都會喚醒應用,即使應用當前處于休眠狀態(tài),導致異常消耗系統(tǒng)資源。
3.2 什么是隱式廣播限制
應用無法在 AndroidManifest.xml 中聲明隱式廣播接收器,以獲得絕大部分響應系統(tǒng)事件的后臺能力。
顯式廣播依然能在
AndroidManifest.xml中注冊。
3.3 解決方案
- Broadcast Whitelist
- Scheduling Jobs
- Dynamic Broadcasts
3.3.1 白名單
以下事件依然能在 AndroidManifest.xml 中聲明隱式廣播接收器,可以正常使用。
- ACTION_LOCKED_BOOT_COMPLETED, ACTION_BOOT_COMPLETED
- ACTION_USER_INITIALIZE
- ACTION_LOCALE_CHANGED
- ACTION_USB_ACCESSORY_ATTACHED, ACTION_USB_ACCESSORY_DETACHED, ACTION_USB_DEVICE_ATTACHED, ACTION_USB_DEVICE_DETACHED
- ACTION_HEADSET_PLUG
- ACTION_CONNECTION_STATE_CHANGED, ACTION_CONNECTION_STATE_CHANGED, ACTION_ACL_CONNECTED, ACTION_ACL_DISCONNECTED
- ACTION_CARRIER_CONFIG_CHANGED
- LOGIN_ACCOUNTS_CHANGED_ACTION
- ACTION_PACKAGE_DATA_CLEARED
- ACTION_PACKAGE_FULLY_REMOVED
- ACTION_NEW_OUTGOING_CALL
- ACTION_DEVICE_OWNER_CHANGED
- ACTION_EVENT_REMINDER
- ACTION_MEDIA_MOUNTED, ACTION_MEDIA_CHECKING, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_EJECT, ACTION_MEDIA_UNMOUNTABLE
- SMS_RECEIVED_ACTION, WAP_PUSH_RECEIVED_ACTION
3.3.2 JobScheduler
JobScheduler 可以用來執(zhí)行一些需要滿足特定條件的后臺任務,如設備網(wǎng)絡狀態(tài)變化、設備充電狀態(tài)變化、低電量等。因此大多數(shù)情況下,之前注冊隱式廣播的應用使用 JobScheduler 可以獲得類似的功能。
3.3.3 動態(tài)注冊廣播
依然能用 Context.registerReceiver() 動態(tài)的注冊隱式廣播,不受影響。但務必在不需要的時候(如生命周期結束)調用 Context.unregisterReciever()。
4. 后臺位置限制
在 Android O 上,應用處于后臺時降低了后臺應用接收位置更新的頻率,具體的位置行為和受影響的 API 可以查看官方文檔。
5. 通知渠道
所有通知的實現(xiàn)都需要提供通知渠道(Notification ChannelId),否則通知在 Android O 系統(tǒng)上無法正常展示,會彈 Toast 提示 Developer warning for package XXX,F(xiàn)ailed to post notification on channel “null”.。
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannelGroup group = new NotificationChannelGroup(GROUP_ID, GROUP_NAME);
manager.createNotificationChannelGroup(group);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channel.setGroup(GROUP_ID);
// ...
manager.createNotificationChannel(channel);
notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
// ...
.build();
} else {
notification = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
// ...
.build();
}
6. 權限
Android O 之前,申請一個子權限(如寫外部存儲權限),會自動獲取權限組中其他子權限(讀外部存儲權限)。組內其他子權限可以直接使用,無需申請。
Android O 修復了這個錯誤。在 Android O 上,申請一個子權限,組內其他子權限不會自動獲取,需要再次申請才能使用。但不會彈出系統(tǒng)的權限申請框,將被自動批準。
7. Reference
- Android Oreo
- Keep those background Services working when targeting Android Oreo (26)
- Exploring Background Execution Limits on Android Oreo
- How to handle background services in ANDROID O?
- Preparing for Android 0: The Death of Background Services
- Meeting Google Play requirements for target API level
本文是 慌不要慌 原創(chuàng),發(fā)表于 https://danke77.github.io/,請閱讀原文支持原創(chuàng) https://danke77.github.io/2018/06/09/target-android-o/,版權歸作者所有,轉載請注明出處。