Android O上之後對AMS的廣播與服務作了嚴格的限制,靜態註冊的廣播再也不可以無所欲爲的接收隱式的廣播,後臺的應用進程也再也不可以調用startService了。Android 做出這樣的限制主要是爲了限制住不自覺的開發者,不讓他們隨意的浪費系統資源。java
這篇文章裏,我將從源碼的角度分析Android O是如何限制靜態廣播與後臺服務的,相關的廣播和服務源碼背景知識你們能夠看我之前的文章。分析的後面會列出Android O上廣播和服務的適配指南。谷歌推薦使用的JobScheduler原理你們能夠看個人上一篇文章。android
文章的最後是個人一些對這些限制的自問自答,也歡迎你們提出問題~shell
查看大圖緩存
[frameworks/base/services/core/java/com/android/server/am/BroadcastQueue#processNextBroadcast]app
if (!skip) { 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 (((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; } } }
[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService#getAppStartModeLocked]ide
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk, int callingPid, boolean alwaysRestrict, boolean disabledOnly) { UidRecord uidRec = mActiveUids.get(uid); if (uidRec == null || alwaysRestrict || uidRec.idle) { final int startMode = (alwaysRestrict) ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk) : appServicesRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk); return startMode; } return ActivityManager.APP_START_MODE_NORMAL; }
[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService#appRestrictedInBackgroundLocked]ui
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) { // 若是targetSdkVerision設置的爲26+的話,即便是在前臺的進程,也不可以收到廣播 // 2018年下半年Google將強制開發者將targetSdkVersion設置爲27以上 if (packageTargetSdk >= Build.VERSION_CODES.O) { return ActivityManager.APP_START_MODE_DELAYED_RIGID; } }
方案 | 建議 |
---|---|
發送端直接將intent加入FLAG_RECEIVER_INCLUDE_BACKGROUND的flag | 頻繁發送的重要廣播不推薦 |
將隱式廣播轉換爲顯式,調用setPackage、setComponent其中一個便可 | 肯定接收端組件或包名時可用,低擴展性 |
使用動態註冊廣播的方式替代靜態註冊 | 推薦 |
廣播發送端與接收端使用signature級別的權限 | 推薦 |
使用JobScheduler代替廣播接收事件 | 部分場景下,推薦 |
<permission android:name="top.navyblue.bugsfree.permission" android:protectionLevel="signature"/> <uses-permission android:name="top.navyblue.bugsfree.permission" />
Intent intent = new Intent(Utils.IMPLICT_INTENT_ACTION); // intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); // intent.setPackage("com.example.yongbiaoai.bugsfreeclient"); // intent.setComponent(new ComponentName("com.example.yongbiaoai.bugsfreeclient", "com.example.yongbiaoai.bugsfreeclient.ImplictReceiver")); // intent.addFlags(0x01000000); sendBroadcast(intent, "top.navyblue.bugsfree.permission");
若是須要自定義權限則須要聲明一個客戶端本身的權限,而後在調用sendBroadcast時加入這個權限this
<uses-permission android:name="top.navyblue.bugsfree.permission" /> <receiver android:name=".ImplictReceiver" android:permission="top.navyblue.bugsfree.permission"> <intent-filter> <action android:name="top.navyblue.top.implict.broadcast"/> </intent-filter> </receiver>
定義靜態接收者時,須要填入permission選項spa
idle狀態下,應用啓動服務將拋異常調試
調試方法:
場景 | 意義 |
---|---|
DeviceIdleController.addPowerSaveWhitelistApp | 使用系統Service 「deviceidle」,動態添加白名單 |
DeviceIdleController.removePowerSaveWhitelistApp | 使用系統Service 「deviceidle」,動態刪除白名單 |
DeviceIdleController.onStart | 從文件中讀取包名,初始化idle白名單 |
[frameworks/base/services/java/com/android/server/SystemServer]
public static void main(String[] args) { new SystemServer().run(); } private void run() { try {; startOtherServices(); } catch (Throwable ex) { throw ex; } finally { traceEnd(); } } private void startOtherServices() { mSystemServiceManager.startService(DeviceIdleController.class); }
在system_server啓動時,會將DeviceIdleController的系統服務啓動
[frameworks/base/services/core/java/com/android/server/DeviceIdleController]
public static final String DEVICE_IDLE_CONTROLLER = "deviceidle"; @Override public void onStart() { synchronized (this) { readConfigFileLocked(); updateWhitelistAppIdsLocked(); } mBinderService = new BinderService(); publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService); } private final class BinderService extends IDeviceIdleController.Stub { @Override public void addPowerSaveWhitelistApp(String name) { getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); long ident = Binder.clearCallingIdentity(); try { addPowerSaveWhitelistAppInternal(name); } finally { Binder.restoreCallingIdentity(ident); } } @Override public void removePowerSaveWhitelistApp(String name) { getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); long ident = Binder.clearCallingIdentity(); try { removePowerSaveWhitelistAppInternal(name); } finally { Binder.restoreCallingIdentity(ident); } } }
在調用onStart方法時會將系統服務在ServiceManager進行publish
startForegroundService ANR超時機制
[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService]
@Override public ComponentName startForegroundService(Intent service) { warnIfCallingFromSystemProcess(); return startServiceCommon(service, true, mUser); } private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
調用startForegroundService方法的區別在於requireForeground的FLAG被置爲true,和startService並無什麼區別
[frameworks/base/services/core/java/com/android/server/am/ActiveServices#sendServiceArgsLocked]
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, boolean oomAdjusted) throws TransactionTooLargeException { if (r.fgRequired && !r.fgWaiting) { if (!r.isForeground) { scheduleServiceForegroundTransitionTimeoutLocked(r); } else { r.fgRequired = false; } } } void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) { if (r.app.executingServices.size() == 0 || r.app.thread == null) { return; } Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG); msg.obj = r; r.fgWaiting = true; mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT); } void serviceForegroundTimeout(ServiceRecord r) { ProcessRecord app; synchronized (mAm) { app = r.app; r.fgWaiting = false; stopServiceLocked(r); } if (app != null) { mAm.mAppErrors.appNotResponding(app, null, null, false, "Context.startForegroundService() did not then call Service.startForeground()"); } }
調用Service.onStartCommand時會設置一個定時器,若是5s內沒有設置調用startForeground將服務置爲前臺,那麼就會出現ANR
[frameworks/base/core/java/android/app/Service#stopSelf]
public final void stopSelf(int startId) { if (mActivityManager == null) { return; } try { mActivityManager.stopServiceToken( new ComponentName(this, mClassName), mToken, startId); } catch (RemoteException ex) { } }
[frameworks/base/services/core/java/com/android/server/am/ActiveServices#stopServiceTokenLocked]
boolean stopServiceTokenLocked(ComponentName className, IBinder token, int startId) { bringDownServiceIfNeededLocked(r, false, false); } private final void bringDownServiceLocked(ServiceRecord r) { if (r.fgRequired) { r.fgRequired = false; r.fgWaiting = false; mAm.mHandler.removeMessages( ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); msg.obj = r.app; mAm.mHandler.sendMessage(msg); } } } void serviceForegroundCrash(ProcessRecord app) { mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId, "Context.startForegroundService() did not then call Service.startForeground()"); }
在調用startForegroundService拉起服務後,若是直接stop掉Service卻沒有將Service置爲前臺的話會出現FC
[frameworks/base/core/java/android/app/Service#startForeground]
public final void startForeground(int id, Notification notification) { try { mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0); } catch (RemoteException ex) { } }
[frameworks/base/services/core/java/com/android/server/am/ActiveServices#setServiceForegroundInnerLocked]
private void setServiceForegroundInnerLocked(ServiceRecord r, int id, Notification notification, int flags) { if (r.fgRequired) { r.fgRequired = false; r.fgWaiting = false; mAm.mHandler.removeMessages( ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); } }
當Service被啓動後,客戶端須要調用Service.startForeground解除ANR和FC
問:爲何Android O只限制靜態廣播而不限制動態廣播?
答:靜態註冊的廣播在沒有被限制的前提下,甚至能夠拉起應用進程。雖然在原生3.1以後,Intent若是沒有攜帶FLAG_INCLUDE_STOPPED_PACKAGES,是不能發送廣播給被force-stop的應用的靜態廣播,可是若是應用進程只是被殺死而沒有被force-stop,仍是能夠經過註冊隱式廣播來接收到與本身無關的廣播,從而拉起本身的進程或者提高自身進程的優先級,浪費用戶的資源。反觀動態廣播,一不能拉起進程,二生命週期每每很短,相比靜態廣播的權限威力小的。
問:爲何Android O只限制了後臺Service而不限制前臺和bounded Service? 答:一言以蔽之,就是大部分的應用開發者太不自覺了,後臺Service對比前臺Service,實現的成本低了不少,能夠在用戶絕不知情的前提下耗費手機的資源。不少Service被應用在後臺偷偷拉起,這樣就躲避了AMS的一些檢查機制,好比說,不會被看成空進程與緩存進程殺死。經過這類保活手段,雖然能夠延長應用的壽命,可是卻使手機電池壽命與內存佔用惡化。