1. 程式人生 > >Android O(8.0)後臺service限制

Android O(8.0)後臺service限制

        Android的每次平臺更新,都一直在努力收緊應用許可權。8.0也不例外,這次是限制應用後臺啟動service的許可權。相關的問題現象,可參考http://blog.csdn.net/chenshengfa/article/details/71407704。

        不過本文除了吐槽下新的後臺限制對8.0平臺適配帶來的影響外,主要是說說本人在開發中發現的一些可規避的方法。先宣告一下,由於應用許可權問題,本文並不適合普通應用開發,僅做研究參考。

        雖然Android官方在限制了後臺service啟動的同時,也給出了替代方案。但畢竟有些功能,是其他方案無法替代或即便替代了也無法達到以往的效果的。比如監聽網路變化、監聽亮滅屏等這些需求。真是一去不復返的坑,對於一些習慣了耍流氓的我們,真真是不方便了啊。以下,是方案正文。

1.      對於所有應用可以使用的解決方法。

1). 平臺區分,8.0以下,愛咋咋地,以前怎樣還怎樣。

2). 8.0以上,統一一個常駐service,轉為前臺。方法為:將startService改成startForegroundService,並在對應的service建立的時候,使用startForeground註冊自己的notification。

3). 對,這個方法是帶notification的,也就是說,如果想用常駐service,就得讓使用者知道。

2.      對於系統應用,可以使用的方法(需要有system許可權)。

可能有人說,我都系統應用了,哪兒要這麼麻煩,我已經是特殊員工啦,Android禁止我幹嘛。對哦,你要是有7大姑8大姨的也想享受許可權怎麼搞?我所負責的應用,就有這麼一款,處在灰色地帶的,一部分有system許可權,另一部分小弟卻在局子外邊混。

首先,我們看一下原始碼中的啟動部分

ActiveServices.java -> startServiceLocked(),裡邊有這麼一段

if(!r.startRequested && !fgRequired) {
    // Before going further -- if this app isnot allowed to start services in the
    // background, then at this point we aren'tgoing to let it period.
    final int allowed =mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
            r.appInfo.targetSdkVersion,callingPid, false, false);
    if (allowed !=ActivityManager.APP_START_MODE_NORMAL) {
        Slog.w(TAG, "Background start notallowed: service "
                + service + " to " +r.name.flattenToShortString()
                + " from pid=" +callingPid + " uid=" + callingUid
                + " pkg=" +callingPackage);
        if (allowed ==ActivityManager.APP_START_MODE_DELAYED) {
            // In this case we are silentlydisabling the app, to disrupt as
            // little as possible existing apps.
            return null;
        }
        // This app knows it is in the newmodel where this operation is not
        // allowed, so tell it what hashappened.
        UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
        return new ComponentName("?","app is in background uid " + uidRec);
    }
}


不難發現,如果service不是fgRequired前臺喚醒,那就得走這段了,獲取service所持有的應用MODE是否為APP_START_MODE_NORMAL。也就是說,一旦應用為APP_START_MODE_NORMAL,就可以開開心心的啟動後臺service了。進入ActivityManagerService.java ->getAppStartModeLocked()檢視定義,核心基本上就是這個了

final intstartMode = (alwaysRestrict)
        ? appRestrictedInBackgroundLocked(uid,packageName, packageTargetSdk)
        :appServicesRestrictedInBackgroundLocked(uid, packageName,
               packageTargetSdk);


直接看appServicesRestrictedInBackgroundLocked()

intappServicesRestrictedInBackgroundLocked(int uid, String packageName, intpackageTargetSdk) {
    // Persistent app?
    if (mPackageManagerInt.isPackagePersistent(packageName)){
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid+ "/" + packageName
                    + " is persistent; notrestricted in background");
        }
        return ActivityManager.APP_START_MODE_NORMAL;
    }
 
    // Non-persistent but backgroundwhitelisted?
    if (uidOnBackgroundWhitelist(uid)) {
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid+ "/" + packageName
                    + " on backgroundwhitelist; not restricted in background");
        }
        returnActivityManager.APP_START_MODE_NORMAL;
    }
 
    // Is this app on the battery whitelist?
    if (isOnDeviceIdleWhitelistLocked(uid)) {
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid+ "/" + packageName
                    + " on idle whitelist;not restricted in background");
        }
        returnActivityManager.APP_START_MODE_NORMAL;
    }
 
    // None of the service-policy criteriaapply, so we apply the common criteria
    return appRestrictedInBackgroundLocked(uid,packageName, packageTargetSdk);
}


然後就是各種判斷了,作為有system許可權的應用,就可以給其他應用開後門啦。

其中的isOnDeviceIdleWhitelistLocked,使用者可用通過進入設定->應用詳情->管理電池應用,此處可以通過開關進行改變這個值,或者設定->電池->右上方的選單,電池優化中,修改應用列表即可。

如上,收尾。