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