1. 程式人生 > >Android Fk:【JavaCrash】Android 26以後限制使用startService啟動後臺服務

Android Fk:【JavaCrash】Android 26以後限制使用startService啟動後臺服務

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);
             ...
        }
    }

下面就是找這兩者之間的聯絡了,概括在圖中:
這裡寫圖片描述

三.總結

  1. 從拋異常的地方可以看出,最終將是呼叫者會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(如果不做相關保護的話)
  2. A應用程序可以是system_server
  3. O及O以上的app儘量不要通過起後臺service進行操作,需要用到後臺service的話可以通過JobScheduler進行處理,即如果你是個簡單的三方應用,不要再使用
    呼叫後臺service的形式喚醒應用了,呼叫者會很危險!!!
    4.具體流程圖如下:
    (提供上面簡圖的draw.io的xml檔案,可以使用draw.io匯入修改
    提供如下時序圖的uml檔案,可使用帶plantuml外掛的inteliJ AS開啟編輯
    https://pan.baidu.com/s/17MO9CXdcSBLI_kuywvCuZA)
    這裡寫圖片描述