ROM定製:APP防自啟原理
國內APP各種保活、拉活,這也是導致安卓手機卡慢的主因,為此各大廠商都有自己防自啟 機制,規避APP的流氓行為,提升手機流暢性。
防自啟基本原理:
攔截四大元件(activity、service、receiver、provider)啟動流程,並對其它可能主動拉起APP的AccountManagerService、JobService、SyncManager進行攔截,切斷一切可能啟動APP的線索。
下面針對各元件的攔截點進行彙總(activity不攔截,使用者可感知):
service
啟動service有兩個介面startService、bindService,二者最終都會呼叫retrieveServiceLocked()檢索service資訊,我們選擇在此統一攔截service的啟動。
startService()-->AMS.startServiceLocked()-->retrieveServiceLocked()
bindService()-->AMS.bindServiceLocked()-->retrieveServiceLocked()
retrieveServiceLocked()先在ServiceMap快取中查詢service,查不到再呼叫PMS.resolveService()進一步查詢。我們在查詢到ServiceRecord之後攔截。
ActiveServices.java:retrieveServiceLocked():
在blockService()中我們可以根據callname和ServiceRecord資訊進行進一步處理,例如黑名單、isSystemAPP()等,根據實際需求定製。
boolean blockService(boolean createIfNeeded, String callname, int callingUid, ServiceRecord r, Intent service, int userId) {
if (r != null) { +/* service block begin */ +if (blockService(createIfNeeded, callingPackage, callingUid, r, service, userId)) { +Slog.w(TAG, "blocking service: " + service + " (" + callingUid + ", " + callingPid + ")"); +return null; +} +/* service block end */ if (mAm.checkComponentPermission(r.permission, callingPid, callingUid, r.appInfo.uid, r.exported) != PackageManager.PERMISSION_GRANTED) {
receiver
根據呼叫流程,我們選擇在processNextBroadcast()進行廣播攔截,有序佇列中的動態廣播無需攔截,只攔截靜態註冊,startProcess之前攔截。
sendBroadcast()-->AMS.broadcastIntent()-->broadcastIntentLocked()-->queue.scheduleBroadcastsLocked()-->mHandler.sendMessage(BROADCAST_INTENT_MSG)-->processNextBroadcast()
BroadcastQueue.java:processNextBroadcast():
這裡是粗粒度的skip整個廣播,後面會介紹針對receivers中某個接收者進行skip,對無原始碼的單個耗時廣播有效。
+/* receiver block begin */ +if (blockReceiver(info, r)) { +Slog.i(TAG, "Skipping delivery of static ["+ mQueueName + "] " + r); +r.receiver = null; +r.curFilter = null; +r.state = BroadcastRecord.IDLE; +scheduleBroadcastsLocked(); +return; +} +/* receiver block end */ if ((r.curApp=mService.startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, "broadcast", r.curComponent, (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false)) == null) {
provider
根據呼叫流程,我們選擇在AMS的getContentProviderImpl()進行攔截。
ContentResolver.query()-->acquireUnstableProvider()-->mMainThread.acquireProvider()-->ActivityManagerNative.getDefault().getContentProvider()-->getContentProviderImpl()
ActivityManagerService.java:getContentProviderImpl():
public static boolean blockProvider(ApplicationInfo callInfo, ApplicationInfo infos, int userId) {
+/* provider block begin */ +boolean stoped = +(cpi.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0; +if (stoped && r != null && +blockProvider(r.info, cpi.applicationInfo, userId)) { +return null; +} +/* provider block end */ proc = startProcessLocked(cpi.processName, cpr.appInfo, false, 0, "content provider", new ComponentName(cpi.applicationInfo.packageName, cpi.name), false, false, false);
AccountManagerService、JobService、SyncManager
這幾個service不再細述流程,直接貼攔截程式碼。
- AccountManagerService.java:addAccount()、addAccountAsUser()
long identityToken = clearCallingIdentity(); try { UserAccounts accounts = getUserAccounts(userId); +/* blocking Account begin */ +if(blockBindAccount(mContext, mAuthenticatorCache, +options, accountType, accounts.userId)) return; +/* blocking Account end */ logRecordWithUid( accounts, DebugDbHelper.ACTION_CALLED_ACCOUNT_ADD, TABLE_ACCOUNTS, userId); new Session(accounts, response, accountType, expectActivityLaunch, true /* stripAuthTokenFromResult */, null /* accountName */, false /* authDetailsRequired */, true /* updateLastAuthenticationTime */) {
- JobServiceContext.java:executeRunnableJob()
boolean executeRunnableJob(JobStatus job) { synchronized (mLock) { if (!mAvailable) { Slog.e(TAG, "Starting new runnable but context is unavailable > Error."); return false; } +/* blocking job begin */ +if (blockJob(job)) { +return false; +} +/* blocking job end */ mPreferredUid = NO_PREFERRED_UID;
- SyncManager.java:bindToSyncAdapter()
boolean bindToSyncAdapter(ComponentName serviceComponent, int userId) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "bindToSyncAdapter: " + serviceComponent + ", connection " + this); } Intent intent = new Intent(); intent.setAction("android.content.SyncAdapter"); intent.setComponent(serviceComponent); intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.sync_binding_label); intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser( mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0, null, new UserHandle(userId))); +/* blocking Sync begin */ +if (blockSync(serviceComponent.getPackageName(), userId)) { +return false; +} +/* blocking Sync end */ mBound = true; final boolean bindResult = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mSyncOperation.target.userId));
補充:receiver耗時與skip
廣播超時場景下,我們需要分析出receivers中具體哪個receiver耗時較長,進行優化處理。
對於無原始碼的APK,廣播耗時又比較久,我們也可以針對性的skip該receiver。
動態廣播:deliverToRegisteredReceiverLocked()處理;
靜態廣播:程序已建立,processCurBroadcastLocked()處理;
靜態廣播:程序未建立,startProcessLocked()建立程序。
耗時分析
原生程式碼對廣播耗時分析不太友好,需要手動比較日誌中"BroadcastQueue:Delivering to xxx"前後的時間間隔來判斷耗時,繁瑣而且不精確。我們採用自行新增log的方式協助日常分析。
無序廣播:binder非同步分發,需要自行新增log確認耗時時長。
有序廣播:每個BroadcastRecord全部處理完畢,會有finish日誌,單個receiver的耗時需要自行新增log。
無序廣播:
deliverToRegisteredReceiverLocked()-->performReceiveLocked()-->AT.scheduleRegisteredReceiver()-->InnerReceiver.performReceive()-->ReceiverDispatcher.performReceive()-->ReceiverDispatcher.Args.getRunnable()-->onReceive()
LoadedApk.java:ReceiverDispatcher.Args.getRunnable():
try { ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); intent.prepareToEnterProcess(); setExtrasClassLoader(cl); receiver.setPendingResult(this); +long beginTime = SystemClock.uptimeMillis(); receiver.onReceive(mContext, intent); +long endTime =SystemClock.uptimeMillis(); +long duration= endTime - beginTime; +if(duration > 100 ) { //自定義時長,例100ms +Slog.w(TAG, "long-running onReceive" ++ ": intent=" + intent ++ ", ordered=" + ordered ++ ", receiver=" + receiver ++ ", duration=" + duration + "ms"); +} }
有序廣播:
每個BroadcastRecord中所有receivers處理完,總時長。
BroadcastQueue.java:processNextBroadcast():
BroadcastQueue: Finished with ordered broadcast BroadcastRecord{9bec22c u13 android.intent.action.USER_INITIALIZE} receivers:(5) take 5783ms in [foreground], remains 5
+/* ordered time begin */ +long rightNow = SystemClock.uptimeMillis(); +long countTime = rightNow - r.dispatchTime; +Slog.d(TAG_BROADCAST, +"Finished with ordered broadcast " + r + " receivers:(" ++ numReceivers + ") take " + countTime + "ms in [" ++ mQueueName + "], remains " ++ (mOrderedBroadcasts.size() - 1)); +/* ordered time end */ addBroadcastToHistoryLocked(r); if (r.intent.getComponent() == null && r.intent.getPackage() == null && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage, r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime); } mOrderedBroadcasts.remove(0); r = null; looped = true; continue; } } while (r == null);
BroadcastRecord中單個receiver耗時。
processCurBroadcastLocked()-->AT.scheduleReceiver()-->handleReceiver()-->onReceive()
ActivityThread.java:handleReceiver():
try { sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); +long beginTime = SystemClock.uptimeMillis(); receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); +long endTime =SystemClock.uptimeMillis(); +long duration= endTime - beginTime; +if( duration > 100 ) { +Slog.w(TAG, "long-running onReceive, static," ++ ": intent=" + data.intent ++ ", receiver=" + receiver ++ ", duration=" + duration + "ms"); +} }
耗時廣播skip
例如發現GMS某個receiver耗時較長,對實際功能又無影響,可skip此receiver。
在deliverToRegisteredReceiverLocked()、processCurBroadcastLocked()、startProcessLocked()三處進行skip處理。
+static final String[] skipComponents = { +"com.google.android.gms/com.google.android.gms.chimera.GmsIntentOperationService$PersistentTrustedReceiver", +"com.google.android.setupwizard/com.google.android.setupwizard.util.SetupWizardUserInitReceiver", +};
deliverToRegisteredReceiverLocked():
+/* skip componet begin */ +if (skipComponents.contains(r.curComponent)) { +skip = true; +} +/* skip componet end */ if (skip) { r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; return; } ........ try { if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, "Delivering to " + filter + " : " + r); if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) { // Skip delivery if full backup in progress // If it's an ordered broadcast, we need to continue to the next receiver. if (ordered) { skipReceiverLocked(r); } } else { performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); } if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } }
processCurBroadcastLocked():
+/* skip componet begin */ +if (skipComponents.contains(r.curComponent)) { +skipReceiverLocked(r); +return; +} +/* skip componet end */ r.receiver = app.thread.asBinder(); r.curApp = app; app.curReceivers.add(r); app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); mService.updateLruProcessLocked(app, false, null); mService.updateOomAdjLocked(); // Tell the application to launch this receiver. r.intent.setComponent(r.curComponent); boolean started = false; try { if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Delivering to component " + r.curComponent + ": " + r); mService.notifyPackageUse(r.intent.getComponent().getPackageName(), PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, app.repProcState); if (DEBUG_BROADCAST)Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; }
startProcessLocked()之前:
+/* skip componet begin */ +if (skipComponents.contains(r.curComponent)) { +r.receiver = null; +r.curFilter = null; +r.state = BroadcastRecord.IDLE; +scheduleBroadcastsLocked(); +return; +} +/* skip componet end */ if ((r.curApp=mService.startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, "broadcast", r.curComponent, (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false)) == null) { return; }