1. 程式人生 > >Android Broadcast原理分析之Android新版本限制(四)

Android Broadcast原理分析之Android新版本限制(四)

目錄

  • 概述
  • 官方文件介紹
  • 原始碼解析
  • 適配

1. 概述

隨著Android版本不斷迭代,Android對後臺的管控越來越嚴格,對於APP而言,通常來講後臺活躍的主要是廣播以及service,而Google對於後臺的管控也著重就在這兩個元件上,本篇文章主要介紹Android O上對於廣播的新增限制與管控。

升級到Android O之後,可能會發現有些廣播收不到了,檢視log發現會有如下一些資訊"Background execution not allowed.."打出來,原因正是由於Android對後臺越來越嚴格。

2. 官方文件介紹

如果應用註冊為接收廣播,則在每次傳送廣播時,應用的接收器都會消耗資源。 如果多個應用註冊為接收基於系統事件的廣播,這會引發問題;觸發廣播的系統事件會導致所有應用快速地連續消耗資源,從而降低使用者體驗。

可以看到,Google也認識到廣播這個機制的存在對於使用者的使用體驗包括效能以及功耗方面的影響比較大,所以才致力於不斷的增加後臺的限制。

3. 原始碼解析

3.1 ActivityManagerService.broadcastIntentLocked

if (getBackgroundLaunchBroadcasts().contains(action)) {
    intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
}

private ArraySet<String> getBackgroundLaunchBroadcasts() {
    if (mBackgroundLaunchBroadcasts == null) {
        mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
    }
    return mBackgroundLaunchBroadcasts;
}

可以看到,只有可以在後臺執行的廣播,才會增加FLAG_RECEIVER_INCLUDE_BACKGROUND,這個flag的意思是:如果帶有這個flag,則這個廣播的接收者可以處於後臺。反之,FLAG_RECEIVER_EXCLUDE_BACKGROUND這個flag的意思就是:不允許處於後臺狀態的應用接收這個廣播。 那麼此處後臺的定義是怎麼樣的呢?

Android O上App如果沒有fg-service或者activity,那麼當這個應用切換到‘後臺’一分鐘之後,自動會進入idel狀態(uidrecord.idle),此時就處於後臺,後續的Android程序管理章節會通過原始碼介紹。

上面可以看到,還是有特例的,那就是getBackgroundLaunchBroadcasts,但是這個裡面主要針對的是/etc目錄下特殊配置的一些配置檔案中的廣播,帶有allow-implicit-broadcast

標籤的,我們平時用的廣播大多不在這裡。 小結:預設情況下在廣播發送的時候,系統不會帶有FLAG_RECEIVER_INCLUDE_BACKGROUND這個的flag 那麼對於這個flag的處理是在哪呢?

3.2 BroadcastQueue.processNextBroadcast

final int allowed = mService.getAppStartModeLocked(
        info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
        info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
    if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
        Slog.w(TAG, "Background execution disabled: receiving "
                + r.intent + " to "
                + component.flattenToShortString());
        skip = true;
    } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
            || (r.intent.getComponent() == null
                && r.intent.getPackage() == null
                && ((r.intent.getFlags()
                        & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
                && !isSignaturePerm(r.requiredPermissions))) {
        mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                component.getPackageName());
        Slog.w(TAG, "Background execution not allowed: receiving "
                + r.intent + " to "
                + component.flattenToShortString());
        skip = true;
    }

這塊邏輯在前面講解廣播派發原理章節中也有提到,是處在其中的許可權校驗部分

  1. 如果有FLAG_RECEIVER_EXCLUDE_BACKGROUND,那就直接skip掉了
  2. 第二個條件是針對manifest靜態註冊的廣播,會skip

3.3 ActivityManagerService.getAppStartModeLocked

    int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
            int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
        // 根據uid拿到對應的uidrecord
        // 可以理解為每個package在手機中對應一個uid,每個uid對應一個uidrecord
        UidRecord uidRec = mActiveUids.get(uid);
        // 如果uid不存在,表示應用目前程序不在
        // idle表示應用處在後臺
        if (uidRec == null || alwaysRestrict || uidRec.idle) {
            boolean ephemeral;
            if (uidRec == null) {
                ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
                        UserHandle.getUserId(uid), packageName);
            } else {
                ephemeral = uidRec.ephemeral;
            }

            if (ephemeral) {
                return ActivityManager.APP_START_MODE_DISABLED;
            } else {
                if (disabledOnly) {
                    return ActivityManager.APP_START_MODE_NORMAL;
                }
                final int startMode = (alwaysRestrict)
                        ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
                        : appServicesRestrictedInBackgroundLocked(uid, packageName,
                                packageTargetSdk);
                if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
                    if (callingPid >= 0) {
                        ProcessRecord proc;
                        synchronized (mPidsSelfLocked) {
                            proc = mPidsSelfLocked.get(callingPid);
                        }
                        if (proc != null &&
                                !ActivityManager.isProcStateBackground(proc.curProcState)) {
                            return ActivityManager.APP_START_MODE_NORMAL;
                        }
                    }
                }
                return startMode;
            }
        }
        return ActivityManager.APP_START_MODE_NORMAL;
    }

從引數傳遞來看,這裡傳遞的alwaysRestrict是true

    int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        if (packageTargetSdk >= Build.VERSION_CODES.O) {
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
        }
        int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
                uid, packageName);
        switch (appop) {
            case AppOpsManager.MODE_ALLOWED:
                return ActivityManager.APP_START_MODE_NORMAL;
            case AppOpsManager.MODE_IGNORED:
                return ActivityManager.APP_START_MODE_DELAYED;
            default:
                return ActivityManager.APP_START_MODE_DELAYED_RIGID;
        }
    }

這裡邏輯非常明顯,當package的targetSdkVersion大於等於26之後,直接返回了APP_START_MODE_DELAYED_RIGID,返回BroadcastQueue.processNextBroadcast對照一下程式碼發現不等於APP_START_MODE_NORMAL,所以進入了flag的判斷部分。 所以:

  1. 對於通過manifets靜態註冊的廣播,傳送時需要新增FLAG_RECEIVER_INCLUDE_BACKGROUND
  2. 如果不希望被後臺應用監聽,可以在傳送時直接新增FLAG_RECEIVER_EXCLUDE_BACKGROUND

4. 適配

當APP的targetSdkVersion升級之後,可能會遇到很多類似靜態註冊的廣播接受不到的問題,一方面,可以通過在傳送者處增加FLAG_RECEIVER_INCLUDE_BACKGROUND來強制使其生效,因為這個是hide起來的,可以直接寫硬編碼(0x01000000)。另外也可以根據業務修改實現方式,比如修改註冊方式,或者通過jobscheduler實現。