Android Fk:【JavaCrash】Android 26以後限制使用startService啟動後臺服務
阿新 • • 發佈:2018-12-22
Android Fk:【JavaCrash】Android 26以後限制使用startService啟動後臺服務
一. 問題概述
1.出錯呼叫棧
E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.unionpay.uppay.action.HCE pkg=com.sankuai.meituan }: app is in background uid null
07-23 19:06:29.734 15328 15377 E AndroidRuntime: FATAL EXCEPTION: Thread-9
07-23 19:06:29.734 15328 15377 E AndroidRuntime: Process: com.unionpay.uppay, PID: 15328
07-23 19:06:29.734 15328 15377 E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.unionpay.uppay.action.HCE pkg=com.sankuai.meituan }: app is in background uid null
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1515)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1471)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.content.ContextWrapper .startService(ContextWrapper.java:654)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at com.unionpay.mobile.android.hce.f.a(Unknown Source:33)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at com.unionpay.mobile.android.hce.h.run(Unknown Source:6)
2.主要原因:
Android O 8.0(API 26之後) 之後不再允許後臺service直接通過startService方式去啟動, 具體行為變更
如果針對 Android 8.0 的應用嘗試在不允許其建立後臺服務的情況下使用 startService() 函式,則該函式將引發一個 IllegalStateException。新的 Context.startForegroundService() 函式將啟動一個前臺服務。
現在,即使應用在後臺執行, 系統也允許其呼叫 Context.startForegroundService()。不過,應用必須在建立服務後的五秒內呼叫該服務的 startForeground() 函式。
3.解決辦法:
1. 修改啟動方式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
//並且在service裡再呼叫startForeground方法,不然就會出現ANR
context.startForeground(SERVICE_ID, builder.getNotification());
該種方式啟動使用者會有感知啊,有個通知額,==
2. 呼叫者作好保護,防止被炸
try {
Intent cameraIntent = new Intent("XXX.XXX");
cameraIntent.setPackage("com.XXX.XXX");
mContext.startServiceAsUser(cameraIntent, UserHandle.CURRENT);
} catch (Exception e) {
Slog.e(TAG, "IllegalAccessException", e);
}
3. 放棄使用起後臺服務喚醒程序
採用jobJobScheduler替換需要起後臺服務的喚醒操作方式。
二.原因分析
1. 先搜所log列印的地方
搜尋“Not allowed to start service”
//frameworks/base/core/java/android/app/ContextImpl.java
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), requireForeground,
getOpPackageName(), user.getIdentifier());
if (cn != null) {
...
} else if (cn.getPackageName().equals("?")) {
//看出是在這裡拋的異常
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
搜尋“app is in background”
//frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
throws TransactionTooLargeException {
// If this isn't a direct-to-foreground start, check our ability to kick off an
// arbitrary service
if (!r.startRequested && !fgRequired) {
...
return new ComponentName("?", "app is in background uid " + uidRec);
...
}
}
下面就是找這兩者之間的聯絡了,概括在圖中:
三.總結
- 從拋異常的地方可以看出,最終將是呼叫者會crash(如果不保護處理的話)
即 A應用程序以startservice的形式呼叫B應用程序的service,如果滿足以下條件:
a.O及O以上的手機平臺上
b.B應用程序的AndroidManifest裡聲明瞭targetSdk大於與等於26
c.B應用程序不是persistent應用
d.B應用程序當前進入後臺且處於idle狀態
e.B應用不在電源管理的白名單中
f.B應用程序不再執行後臺執行的白名單中
此時A應用程序就會crash(如果不做相關保護的話) - A應用程序可以是system_server
- O及O以上的app儘量不要通過起後臺service進行操作,需要用到後臺service的話可以通過JobScheduler進行處理,即如果你是個簡單的三方應用,不要再使用
呼叫後臺service的形式喚醒應用了,呼叫者會很危險!!!
4.具體流程圖如下:
(提供上面簡圖的draw.io的xml檔案,可以使用draw.io匯入修改
提供如下時序圖的uml檔案,可使用帶plantuml外掛的inteliJ AS開啟編輯
https://pan.baidu.com/s/17MO9CXdcSBLI_kuywvCuZA)