This article was originally published by Gityuan. It was first published on diycode.cc and Gityuan.com.

Summary of a.

1.1 the introduction

The Android open source system has countless APPS, all want to find a place for the survival of the system in this “world of great competition”. However, the system resources are limited, if they are divided into the king, no matter how strong the CPU will be busy, no matter how large the memory will eventually be used up, no matter how large the battery life will be short-lived.

ForceStop requires FORCE_STOP_PACKAGES privileges. Of course, this is not a third party app that can call it directly. Otherwise, apps can stop each other and there will be chaos in the world. This method is usually at the system’s behest. Here’s an example of how it works in the form of ADB directives:

Am force-stop pkgName am force-stop --user 2 pkgName // Only the information about user userId=2 is killedCopy the code

The force-stop command kills all information about the package name pkgName in the user space. You can also specify the user Id by using –user. When the above AM instruction is executed, it triggers a call to the main() method of am.java, starting with the main method.

1.2 Am. Main

[-> Am.java]

public static void main(String[] args) { (new Am()).run(args); // see section 1.3}Copy the code

1.3 Am. Run

[-> Am.java]

public void run(String[] args) { ... mArgs = args; mNextArg = 0; mCurArgData = null; onRun(); // [see section 1.4]... }Copy the code

1.4 Am. OnRun

[-> Am.java]

Public void onRun () throws the Exception {/ / get the Binder proxy objects AMP mAm. = ActivityManagerNative getDefault (); String op = nextArgRequired(); if (op.equals("start")) { ... } else if (op.equals("force-stop")) { runForceStop(); // [see section 1.5]}... }Copy the code

1.5 Am. RunForceStop

[-> Am.java]

private void runForceStop() throws Exception { int userId = UserHandle.USER_ALL; String opt; UserId while ((opt=nextOption())! = null) { if (opt.equals("--user")) { userId = parseUserArg(nextArgRequired()); }} // [see Section 1.6] MAM.forcestopPackage (nextArgRequired(), userId); }Copy the code

If userId is not specified, userhandle. USER_ALL is used by default.

1.6 AMP. ForceStopPackage

[-> ActivityManagerNative.java ::AMP]

public void forceStopPackage(String packageName, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(packageName); data.writeInt(userId); MRemote. Transact (FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); }Copy the code

1.7 AMN. OnTransact

[-> ActivityManagerNative.java]

public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case FORCE_STOP_PACKAGE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String packageName = data.readString(); int userId = data.readInt(); // [see section 2.1] forceStopPackage(packageName, userId); reply.writeNoException(); return true; }... }}Copy the code

Amn. forceStopPackage is used to run the process created when executing ADB. Binder Driver is used to run amn. forceStopPackage in a Binder thread of system_server. Operations from here on, including the current operation, are run in the system_server system process.

Section 1.8

In addition to the ADB approach, you can also call the forceStopPackage by getting the ActivityManager, but this is the @hide method and requires system permissions. This method is mainly used by the system, not the third party app. Next, go into the AMS object and delve into the inner workings of force-Stop.

2. Force-stop internal mechanism

2.1 AMS. ForceStopPackage

[-> ActivityManagerService.java]

public void forceStopPackage(final String packageName, int userId) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) ! = PackageManager. PERMISSION_GRANTED) {/ / need to access permission. FORCE_STOP_PACKAGES throw new SecurityException (); } final int callingPid = Binder.getCallingPid(); userId = handleIncomingUser(callingPid, Binder.getCallingUid(), userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); synchronized(this) { int[] users = userId == UserHandle.USER_ALL ? getUsersLocked() : new int[] { userId }; for (int user : users) { int pkgUid = -1; PkgUid = pm.getPackageUid(packageName, user); / / set the state of the package is stopped PM. SetPackageStoppedState (packageName, true, the user). If (isUserRunningLocked(user, false)) {// forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid); } } } } finally { Binder.restoreCallingIdentity(callingId); }}Copy the code

SetPackageStoppedState () sets the package status to stopped, so all broadcasts will not be received, except for broadcasts with FLAG_INCLUDE_STOPPED_PACKAGES, Almost all broadcasts by default do not carry this flag, which means that force-stopped applications cannot start a process by setting up a mobile network status or by turning on or off broadcasts.

When the force stop mode is used to end a process, the reason is usually “from pid “+ callingPid. . Of course, there are other, that is, AMS clearApplicationUserData method call forceStopPackageLocked reason for “clear data”.

2.2 AMS. ForceStopPackageLocked

private void forceStopPackageLocked(final String packageName, int uid, String reason) {//[see Flow 2.3] forceStopPackageLocked(packageName, userHandle.getAppId (uid), false, false, true, false, false, UserHandle.getUserId(uid), reason); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); // mProcessesReady=true if (! mProcessesReady) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); } intent.putExtra(Intent.EXTRA_UID, uid); intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid)); [see Section 8.1] broadcastIntentLocked(null, null, intent, NULL, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid)); }Copy the code

Running action_package_flow is used to clean up registered alarm and notification information.

2.3 AMS. ForceStopPackageLocked

private final boolean forceStopPackageLocked(String packageName, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, int userId, String reason) { int i; if (appId < 0 && packageName ! = null) {/ / retrieve the correct appId appId. = UserHandle getAppId (AppGlobals. GetPackageManager (). GetPackageUid (packageName, 0)); } / / doit = true can enter the branch the if (doit) {final ArrayMap < String, SparseArray < Long > > pmap = mProcessCrashTimes. The getMap (); for (int ip = pmap.size() - 1; ip >= 0; ip--) { SparseArray<Long> ba = pmap.valueAt(ip); for (i = ba.size() - 1; i >= 0; i--) { boolean remove = false; final int entUid = ba.keyAt(i); if (packageName ! = null) { if (userId == UserHandle.USER_ALL) { if (UserHandle.getAppId(entUid) == appId) { remove = true; } } else { if (entUid == UserHandle.getUid(userId, appId)) { remove = true; } } } else if (UserHandle.getUserId(entUid) == userId) { remove = true; } if (remove) { ba.removeAt(i); } } if (ba.size() == 0) { pmap.removeAt(ip); }}} / / cleaning Process [3.1] see Process Boolean didSomething = killPackageProcessesLocked (packageName, appId and userId, - 100, callerWillRestart, true, doit, evenPersistent, packageName == null ? ("stop user " + userId) : ("stop " + packageName)); / / clean up the Activity [4.1] see process if (mStackSupervisor. FinishDisabledPackageActivitiesLocked (packageName, null, doit. evenPersistent, userId)) { ... didSomething = true; } / / end of the package Service [5.1] see process the if (mServices. BringDownDisabledPackageServicesLocked (packageName, null, userId. evenPersistent, true, doit)) { ... didSomething = true; } if (packageName == null) {// if the packageName is empty, remove all sticky broadcasts mStickyBroadcasts. Remove (userId); ArrayList<ContentProviderRecord> providers = new ArrayList<>(); if (mProviderMap.collectPackageProvidersLocked(packageName, null, doit, evenPersistent, userId, providers)) { ... didSomething = true; } for (i = providers.size() - 1; i >= 0; I -) {/ / clean up will [see process 6.2] removeDyingProviderLocked (null, will get (I), true); } / / removing has obtained temporary permissions associated with the package/user removeUriPermissionsForPackageLocked (packageName, userId, false); If (doit) {// Clear Broadcast [see flow 7.1] for (I = mbroadcastkache.length - 1; i >= 0; i--) { didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked( packageName, null, userId, doit); } } if (packageName == null || uninstalling) { ... If (doit) {if (purgeCache && packageName! = null) { ... / / no access to the branch} the if (mBooted) {/ / recovery activity on the top of the stack mStackSupervisor. ResumeTopActivitiesLocked (); mStackSupervisor.scheduleIdleLocked(); } } return didSomething; }Copy the code

For didSomething only when all actions in the method are returned true. Such as killPackageProcessesLocked (), so long as has killed a process represents didSomething to true.

The main functions of this method are:

  1. Process: call AMS killPackageProcessesLocked () to clear the package Process involved;
  2. Activity: call along. FinishDisabledPackageActivitiesLocked Activity involved () to clear the package;
  3. Service: call AS bringDownDisabledPackageServicesLocked () to clear the package Service involved;
  4. The Provider: call AMS removeDyingProviderLocked () to clear the package Provider involved;
  5. BroadcastRecevier: call the BQ. CleanupDisabledPackageReceiversLocked () to clear the package of the radio

Next, the implementation process of force-stop is described from these five perspectives.

Three Process.

3.1 AMS. KillPackageProcessesLocked

private final boolean killPackageProcessesLocked(String packageName, int appId, int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit, boolean evenPersistent, String reason) { ArrayList<ProcessRecord> procs = new ArrayList<>(); Final int NP = mprocessNames.getMap ().size(); for (int ip=0; ip<NP; ip++) { SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip); final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { ProcessRecord app = apps.valueAt(ia); if (app.persistent && ! evenPersistent) { continue; Removed) {if (doit) {procs.add(app); if (doit) {procs.add(app); } continue; } if (app.setAdj < minOomAdj) { continue; If (packageName == null) {if (packageName == null) {... } else {//pkgDeps: package name the process depends on Final Boolean isDep = app.pkgdeps! = null && app.pkgDeps.contains(packageName); if (! isDep && UserHandle.getAppId(app.uid) ! = appId) { continue; } if (userId ! = UserHandle.USER_ALL && app.userId ! = userId) { continue; } //pkgList: the names of all packages running in the process; if (! app.pkgList.containsKey(packageName) && ! isDep) { continue; Removed = true; removed = true; removed = true; procs.add(app); } } int N = procs.size(); for (int i=0; i<N; I ++) {// [see Process 3.2] removeProcessLocked(procs.get(I), callerWillRestart, allowRestart, reason); } updateOomAdjLocked(); return N > 0; }Copy the code

Force-stop normally specifies the package name. This method iterates through all currently running processes (mProcessNames). Processes that do not meet any of the following criteria are the target processes that will be killed:

  1. Persistent process:
  2. Process setAdj < minOomAdj(default -100) :
  3. Non-userhandle. USER_ALL At the same time and the userids of processes are different: In the multi-user model, different users cannot kill each other.
  4. Processes do not depend on the packageName, and the appids of processes are different.
  5. The process does not depend on the packageName, and the packageName is not running in the process.

In layman’s terms:

  • ForceStop does not kill persistent processes;
  • When userId is specified, processes in other user Spaces are not killed.

In addition to this, the following situations are certain to be killed processes:

  • Process markedremove=true, the process will be killed;
  • The process ofpkgDepsIncluded in thepackageName, will be killed;
  • The process ofpkgListIncluded in thepackageName, and the process is equal to the AppId specified by the package name.

The pkgList of a process is added to the queue during the process of starting a component or creating a process, indicating that there are components running on the process under the application. So pkgDeps refers to the package name that the process depends on and calls the ClassLoader procedure to add.

3.2 AMS. RemoveProcessLocked

private final boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, boolean allowRestart, String reason) { final String name = app.processName; final int uid = app.uid; // Remove the process from mProcessNames removeProcessNameLocked(name, uid); if (mHeavyWeightProcess == app) { ... } boolean needRestart = false; if (app.pid > 0 && app.pid ! = MY_PID) { int pid = app.pid; synchronized (mPidsSelfLocked) { mPidsSelfLocked.remove(pid); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); } if (app.isolated) { mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } boolean willRestart = false; if (app.persistent && ! app.isolated) { if (! callerWillRestart) { willRestart = true; } else { needRestart = true; }} // Kill the process app.kill(reason, true); HandleAppDiedLocked (app, willRestart, allowRestart); // For persistent processes, restart the process if (willRestart) {removeLruProcessLocked(app); addAppLocked(app.info, false, null /* ABI override */); } } else { mRemovedProcesses.add(app); } return needRestart; }Copy the code

The main functions of this method are:

  • Remove the process from the mProcessNames, mPidsSelfLocked queues.
  • Remove the process startup timeout message PROC_START_TIMEOUT_MSG;
  • Call app. Kill () will call the Process at the same time to kill the Process. The kill and Process. The killProcessGroup, the Process can be found in the understanding Process is the implementation of the principle
  • Call handleAppDiedLocked() to clean up process-related information, as described in the binderDied() process analysis

Four Activity.

4.1 along. FinishDisabledPackageActivitiesLocked

[-> ActivityStackSupervisor.java]

boolean finishDisabledPackageActivitiesLocked(String packageName, Set<String> filterByClasses, boolean doit, boolean evenPersistent, int userId) { boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; final int numStacks = stacks.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { final ActivityStack stack = stacks.get(stackNdx); / / [4.2] see process the if (stack. FinishDisabledPackageActivitiesLocked (packageName, filterByClasses, doit, evenPersistent, userId)) { didSomething = true; } } } return didSomething; }Copy the code

4.2 the AS. FinishDisabledPackageActivitiesLocked

[-> ActivityStack.java]

// doit = true; boolean finishDisabledPackageActivitiesLocked(String packageName, Set<String> filterByClasses, boolean doit, boolean evenPersistent, int userId) { boolean didSomething = false; TaskRecord lastTask = null; ComponentName homeActivity = null; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; int numActivities = activities.size(); for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { ActivityRecord r = activities.get(activityNdx); final boolean sameComponent = (r.packageName.equals(packageName) && (filterByClasses == null || filterByClasses.contains(r.realActivity.getClassName()))) || (packageName == null && r.userId == userId); if ((userId == UserHandle.USER_ALL || r.userId == userId) && (sameComponent || r.task == lastTask) && (r.app == null || evenPersistent || ! r.app.persistent)) { ... if (r.isHomeActivity()) { if (homeActivity ! = null && homeActivity.equals(r.realActivity)) { continue; Home activity} else {homeActivity = r.realActivity; } } didSomething = true; if (sameComponent) { if (r.app ! = null) { r.app.removed = true; } r.app = null; } lastTask = r.task; If (finishActivityLocked(r, activity. RESULT_CANCELED, null, "force-stop", True)) {// r has been removed from mActivities --numActivities; --activityNdx; } } } } return didSomething; }Copy the code

4.3 the AS. FinishActivityLocked

[-> ActivityStack.java]

final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, String reason, boolean oomAdj) { if (r.finishing) { return false; } //[see Flow 4.3.1] r.akefinishinglocked (); final TaskRecord task = r.task; final ArrayList<ActivityRecord> activities = task.mActivities; final int index = activities.indexOf(r); If (index < (activities.size() -1)) {//[see flow 4.3.3] task.setFrontofTask (); if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) ! ActivityRecord Next = Activities.get (index+1); // When the activity is removed, the activity information is propagated to the next activity ActivityRecord. next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); }} / / pause button event distribution r.p auseKeyDispatchingLocked (); / / adjust the focus of the activity process of [see 4.3.4] adjustFocusedActivityLocked (r, "finishActivity"); / / reset the activity results callback information finishActivityResultsLocked (r, the resultCode, resultData); If (mResumedActivity == r) {Boolean endTask = index <= 0; / / ready to close the transition mWindowManager. PrepareAppTransition (endTask? AppTransition.TRANSIT_TASK_CLOSE : AppTransition.TRANSIT_ACTIVITY_CLOSE, false); / / advise the WMS to prepare to remove from the window of the activity. MWindowManager setAppVisibility (state Richard armitage ppToken, false); If (mPausingActivity == null) {// Pause the activity startPausingLocked(false, false, false, false); } if (endTask) { mStackSupervisor.removeLockedTaskLocked(task); } } else if (r.state ! = ActivityState. PAUSING) {/ / when r invisible 4.3.5] [see process return finishCurrentActivityLocked (r, FINISH_AFTER_PAUSE, oomAdj) == null; } return false; }Copy the code

4.3.1 AR. MakeFinishingLocked

[-> ActivityRecord.java]

void makeFinishingLocked() { if (! finishing) { if (task ! = null && task.stack ! . = null && this = = task stack. GetVisibleBehindActivity ()) {/ / in finishing the activity should not keep visibility in the background process [see 4.3.2] mStackSupervisor.requestVisibleBehindLocked(this, false); } finishing = true; if (stopped) { clearOptionsLocked(); }}}Copy the code

4.3.2 along. RequestVisibleBehindLocked

boolean requestVisibleBehindLocked(ActivityRecord r, boolean visible) { final ActivityStack stack = r.task.stack; if (stack == null) { return false; / / r in the stack is empty, the direct return} final Boolean isVisible = stack. HasVisibleBehindActivity (); final ActivityRecord top = topRunningActivityLocked(); if (top == null || top == r || (visible == isVisible)) { stack.setVisibleBehindActivity(visible ? r : null); return true; } if (visible && top.fullscreen) { ... } else if (! visible && stack.getVisibleBehindActivity() ! = r) {// The setting is not visible and the current top Activity is the same as the activity returns false; } stack.setVisibleBehindActivity(visible ? r : null); if (! The visible) {/ / put the activity above opaque r final ActivityRecord next = stack. FindNextTranslucentActivity (r); if (next ! = null) { mService.convertFromTranslucent(next.appToken); } } if (top.app ! = null && top.app.thread ! = null) {/ / will change notification to the top. The top application app. Thread. ScheduleBackgroundVisibleBehindChanged (top) appToken, visible). } return true; }Copy the code

4.3.3 TaskRecord.setFrontOfTask

[-> TaskRecord.java]

final void setFrontOfTask() { boolean foundFront = false; final int numActivities = mActivities.size(); for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); if (foundFront || r.finishing) { r.frontOfTask = false; } else { r.frontOfTask = true; foundFront = true; }} // All activities are in finishing state. If (! foundFront && numActivities > 0) { mActivities.get(0).frontOfTask = true; }}Copy the code
  • If the Task is queried from the bottom up, the first ActivityRecord that is in the non-finishing state is set to the root Activity(r.frontofTask = true) and all others to false.
  • When all the activities are in the finishing state, set the bottom activity to the following activity.

4.3.4 AS.adjustFocusedActivityLocked

[-> ActivityStack.java]

private void adjustFocusedActivityLocked(ActivityRecord r, String reason) { if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) { ActivityRecord next = topRunningActivityLocked(null); final String myReason = reason + " adjustFocus"; if (next ! = r) { final TaskRecord task = r.task; boolean adjust = false; / / the moment an activity is empty, or the next, unlike the current task, or the end of the r for the root activity. The if ((next = = null | | next. The task! = task) && r.frontOfTask) { if (task.isOverHomeStack() && task == topTask()) { adjust = true; } else { for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord tr = mTaskHistory.get(taskNdx); if (tr.getTopActivity() ! = null) { break; } else if (tr.isOverHomeStack()) { adjust = true; break; }}}} // Adjust the focus of the Activity if (adjust) {// For a non-full-screen stack, move the Angle to the next visible stack instead of moving directly to the home stack and shielding the other visible stacks. if (! mFullscreen && adjustFocusToNextVisibleStackLocked(null, myReason)) { return; } / / when the stack is full screen, or there is no other visible stack, stack is the home moved to the top if (mStackSupervisor. MoveHomeStackTaskToTop (task. GetTaskToReturnTo (), MyReason)) {// If focus is fully adjusted, return; } } } final ActivityRecord top = mStackSupervisor.topRunningActivityLocked(); if (top ! = null) { mService.setFocusedActivityLocked(top, myReason); }}}Copy the code

4.3.5 AS.finishCurrentActivityLocked

final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) { if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) { if (! mStackSupervisor.mStoppingActivities.contains(r)) { mStackSupervisor.mStoppingActivities.add(r); if (mStackSupervisor.mStoppingActivities.size() > 3 || r.frontOfTask && mTaskHistory.size() <= 1) { mStackSupervisor.scheduleIdleLocked(); } else { mStackSupervisor.checkReadyForSleepLocked(); } // Set the status to stopping r.state = activitystate.stopping; if (oomAdj) { mService.updateOomAdjLocked(); } return r; } / / clear information mStackSupervisor. MStoppingActivities. Remove (r); mStackSupervisor.mGoingToSleepActivities.remove(r); mStackSupervisor.mWaitingVisibleActivities.remove(r); if (mResumedActivity == r) { mResumedActivity = null; } final ActivityState prevState = r.state; // Set the status to Finishing r.state = ActivityState. Finishing; if (mode == FINISH_IMMEDIATELY || (mode == FINISH_AFTER_PAUSE && prevState == ActivityState.PAUSED) || prevState == ActivityState.STOPPED || prevState == ActivityState.INITIALIZING) { r.makeFinishingLocked(); boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm"); if (activityRemoved) { mStackSupervisor.resumeTopActivitiesLocked(); } return activityRemoved ? null : r; } / / need to wait until the activity performed pause, enter the stopped state, will finish mStackSupervisor. MFinishingActivities. Add (r); r.resumeKeyDispatchingLocked(); mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); return r; }Copy the code

If one of the following conditions is met, finish and destroy activities are executed.

  1. Mode to FINISH_IMMEDIATELY
  2. The mode is FINISH_AFTER_PAUSE and the Activity state is PAUSED;
  3. The status of the Activity is STOPPED or INITIALIZING.

Five Service.

5.1 bringDownDisabledPackageServicesLocked

[-> ActiveServices.java]

//killProcess = true; doit = true; boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses, int userId, boolean evenPersistent, boolean killProcess, boolean doit) { boolean didSomething = false; if (mTmpCollectionResults ! = null) { mTmpCollectionResults.clear(); } if (userId == UserHandle.USER_ALL) { for (int i = mServiceMap.size() - 1; i >= 0; I -) {/ / from mServiceMap query to all belong to the package of service [see process 5.2] didSomething | = collectPackageServicesLocked (packageName, filterByClasses, evenPersistent, doit, killProcess, mServiceMap.valueAt(i).mServicesByName); if (! doit && didSomething) { ... } } } else { ServiceMap smap = mServiceMap.get(userId); if (smap ! = null) { ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByName; / / query from mServiceMap to all belong to the package of service [see process 5.2] didSomething = collectPackageServicesLocked (packageName, filterByClasses. evenPersistent, doit, killProcess, items); } } if (mTmpCollectionResults ! = null) {/ / end off all the collected Service [5.3] see process for (int I = mTmpCollectionResults. The size () - 1; i >= 0; i--) { bringDownServiceLocked(mTmpCollectionResults.get(i)); } mTmpCollectionResults.clear(); } return didSomething; }Copy the code

5.2 collectPackageServicesLocked

[-> ActiveServices.java]

//killProcess = true; doit = true; private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses, boolean evenPersistent, boolean doit, boolean killProcess, ArrayMap<ComponentName, ServiceRecord> services) { boolean didSomething = false; for (int i = services.size() - 1; i >= 0; i--) { ServiceRecord service = services.valueAt(i); final boolean sameComponent = packageName == null || (service.packageName.equals(packageName) && (filterByClasses == null || filterByClasses.contains(service.name.getClassName()))); if (sameComponent && (service.app == null || evenPersistent || ! service.app.persistent)) { if (! doit) { ... } didSomething = true; if (service.app ! = null) { service.app.removed = killProcess; if (! service.app.persistent) { service.app.services.remove(service); } } service.app = null; service.isolatedProc = null; if (mTmpCollectionResults == null) { mTmpCollectionResults = new ArrayList<>(); } / / will meet the conditions of service in the mTmpCollectionResults mTmpCollectionResults. Add (service); } } return didSomething; }Copy the code

The main function of this method is to collect the qualified service into mTmpCollectionResults.

5.3 bringDownServiceLocked

[-> ActiveServices.java]

private final void bringDownServiceLocked(ServiceRecord r) { for (int conni=r.connections.size()-1; conni>=0; conni--) { ArrayList<ConnectionRecord> c = r.connections.valueAt(conni); for (int i=0; i<c.size(); i++) { ConnectionRecord cr = c.get(i); cr.serviceDead = true; Cr.conn. connected(r.name, null); } } if (r.app ! = null && r.app.thread ! = null) { for (int i=r.bindings.size()-1; i>=0; i--) { IntentBindRecord ibr = r.bindings.valueAt(i); if (ibr.hasBound) { bumpServiceExecutingLocked(r, false, "bring down unbind"); mAm.updateOomAdjLocked(r.app); ibr.hasBound = false; / / in the end to invoke the target Service. The onUnbind () method of state Richard armitage pp. Thread. ScheduleUnbindService (technique r, ibr. Intent. GetIntent ()); } } } r.destroyTime = SystemClock.uptimeMillis(); final ServiceMap smap = getServiceMap(r.userId); smap.mServicesByName.remove(r.name); smap.mServicesByIntent.remove(r.intent); r.totalRestartCount = 0; / / [see process 5.4] unscheduleServiceRestartLocked (r, 0, true); For (int I = mpendingservices.size ()-1; i>=0; i--) { if (mPendingServices.get(i) == r) { mPendingServices.remove(i); R.cancelnotification (); r.isForeground = false; r.foregroundId = 0; r.foregroundNoti = null; r.clearDeliveredStartsLocked(); r.pendingStarts.clear(); if (r.app ! = null) { synchronized (r.stats.getBatteryStats()) { r.stats.stopLaunchedLocked(); } r.app.services.remove(r); if (r.app.thread ! = null) { updateServiceForegroundLocked(r.app, false); bumpServiceExecutingLocked(r, false, "destroy"); mDestroyingServices.add(r); r.destroying = true; mAm.updateOomAdjLocked(r.app); / / in the end to invoke the target service onDestroy () of state Richard armitage pp. Thread. ScheduleStopService (r); } } if (r.bindings.size() > 0) { r.bindings.clear(); } if (r.restarter instanceof ServiceRestarter) { ((ServiceRestarter)r.restarter).setService(null); } int memFactor = mAm.mProcessStats.getMemFactorLocked(); long now = SystemClock.uptimeMillis(); if (r.tracker ! = null) { r.tracker.setStarted(false, memFactor, now); r.tracker.setBound(false, memFactor, now); if (r.executeNesting == 0) { r.tracker.clearCurrentOwner(r, false); r.tracker = null; } } smap.ensureNotStartingBackground(r); }Copy the code

5.4 unscheduleServiceRestartLocked

private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid, boolean force) { if (! force && r.restartDelay == 0) { return false; } / / will Services from the list to remove restart, and reset to restart counter Boolean removed = mRestartingServices. Remove (r); if (removed || callingUid ! = r.appInfo.uid) { r.resetRestartCounter(); } if (removed) { clearRestartingIfNeededLocked(r); } mAm.mHandler.removeCallbacks(r.restarter); return true; }Copy the code

6. The Provider

6.1 PM. CollectPackageProvidersLocked

[-> ProviderMap.java]

boolean collectPackageProvidersLocked(String packageName, Set<String> filterByClasses, boolean doit, boolean evenPersistent, int userId, ArrayList<ContentProviderRecord> result) { boolean didSomething = false; If (userId. = = UserHandle USER_ALL | | userId. = = UserHandle USER_OWNER) {/ / didSomething = [see process 6.2] collectPackageProvidersLocked(packageName, filterByClasses, doit, evenPersistent, mSingletonByClass, result); } if (! doit && didSomething) { ... } if (userId == userhandle. USER_ALL) {for (int I = 0; i < mProvidersByClassPerUser.size(); i++) { if (collectPackageProvidersLocked(packageName, filterByClasses, doit, evenPersistent, mProvidersByClassPerUser.valueAt(i), result)) { ... didSomething = true; } // Select HashMap<ComponentName from mProvidersByClassPerUser ContentProviderRecord> items = getProvidersByClass(userId); if (items ! = null) { didSomething |= collectPackageProvidersLocked(packageName, filterByClasses, doit, evenPersistent, items, result); } } return didSomething; }Copy the code
  • If userId = userhandle. USER_ALL, this parameter is specifiedmSingletonByClassandmProvidersByClassPerUserStructure to query all providers of the package.
  • If userId = userhandle. USER_OWNER, the value ofmSingletonByClassandmProvidersByClassPerUserTo query all providers of the package in the data structure with userId equal.
  • If the userId does not belong to either of the above, the userId will be frommProvidersByClassPerUserTo query all providers of the package whose userids are equal.

6.2 PM. CollectPackageProvidersLocked

[-> ProviderMap.java]

private boolean collectPackageProvidersLocked(String packageName, Set<String> filterByClasses, boolean doit, boolean evenPersistent, HashMap<ComponentName, ContentProviderRecord> providers, ArrayList<ContentProviderRecord> result) { boolean didSomething = false; for (ContentProviderRecord provider : providers.values()) { final boolean sameComponent = packageName == null || (provider.info.packageName.equals(packageName) && (filterByClasses == null || filterByClasses.contains(provider.name.getClassName()))); if (sameComponent && (provider.proc == null || evenPersistent || ! provider.proc.persistent)) { if (! doit) { ... } didSomething = true; result.add(provider); } } return didSomething; }Copy the code

6.3 AMS. RemoveDyingProviderLocked

private final boolean removeDyingProviderLocked(ProcessRecord proc, ContentProviderRecord cpr, boolean always) { final boolean inLaunching = mLaunchingProviders.contains(cpr); // Wake up CPR and remove provider-related information from mProviderMap if (! inLaunching || always) { synchronized (cpr) { cpr.launchingApp = null; cpr.notifyAll(); } mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid)); String names[] = cpr.info.authority.split(";" ); for (int j = 0; j < names.length; j++) { mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid)); } } for (int i = cpr.connections.size() - 1; i >= 0; i--) { ContentProviderConnection conn = cpr.connections.get(i); If (conn.waiting) {// Always = true, never enter the branch if (inLaunching &&! always) { continue; } } ProcessRecord capp = conn.client; conn.dead = true; if (conn.stableCount > 0) { if (! capp.persistent && capp.thread ! = null && capp.pid ! = 0 && capp.pid ! = MY_PID) {/ / kill depend on the provider's client process capp. Kill (" depends on the provider "+ CPR. Name. FlattenToShortString () +" in dying proc " + (proc ! = null ? proc.processName : "??" ), true); } } else if (capp.thread ! = null && conn.provider.provider ! = null) { capp.thread.unstableProviderDied(conn.provider.provider.asBinder()); cpr.connections.remove(i); if (conn.client.conProviders.remove(conn)) { stopAssociationLocked(capp.uid, capp.processName, cpr.uid, cpr.name); } } } if (inLaunching && always) { mLaunchingProviders.remove(cpr); } return inLaunching; }Copy the code

When other apps use this provider and establish stable connections, non-persistent processes will be killed because they rely on this provider.

Seven Broadcast.

7.1 BQ. CleanupDisabledPackageReceiversLocked

[-> BroadcastQueue.java]

boolean cleanupDisabledPackageReceiversLocked( String packageName, Set<String> filterByClasses, int userId, boolean doit) { boolean didSomething = false; for (int i = mParallelBroadcasts.size() - 1; i >= 0; I -) {/ / [see process 7.2] didSomething | = mParallelBroadcasts. Get (I). CleanupDisabledPackageReceiversLocked (packageName, filterByClasses, userId, doit); if (! doit && didSomething) { return true; } } for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) { didSomething |= mOrderedBroadcasts.get(i).cleanupDisabledPackageReceiversLocked( packageName, filterByClasses, userId, doit); if (! doit && didSomething) { return true; } } return didSomething; }Copy the code

The main functions of this method:

  • Clear parallel broadcast queue mParallelBroadcasts;
  • Clear ordered broadcast queue mOrderedBroadcasts

7.2 BR. CleanupDisabledPackageReceiversLocked

[-> BroadcastRecord.java]

boolean cleanupDisabledPackageReceiversLocked( String packageName, Set<String> filterByClasses, int userId, boolean doit) { if ((userId ! = UserHandle.USER_ALL && this.userId ! = userId) || receivers == null) { return false; } boolean didSomething = false; Object o; for (int i = receivers.size() - 1; i >= 0; i--) { o = receivers.get(i); if (! (o instanceof ResolveInfo)) { continue; } ActivityInfo info = ((ResolveInfo)o).activityInfo; final boolean sameComponent = packageName == null || (info.applicationInfo.packageName.equals(packageName) && (filterByClasses == null || filterByClasses.contains(info.name))); if (sameComponent) { ... didSomething = true; // Remove the broadcast receiver. Remove (I); if (i < nextReceiver) { nextReceiver--; } } } nextReceiver = Math.min(nextReceiver, receivers.size()); return didSomething; }Copy the code

Viii. Handling of broadcasting

Task_flow/task_flow/task_flow/task_flow/task_flow/task_flow/task_flow/task_flow

8.1 the Alarm to clean up

[-> AlarmManagerService.java]

class UninstallReceiver extends BroadcastReceiver { public UninstallReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); // monitoringaction_package_flow filter.addAction(intent.action_package_flow); filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); filter.addDataScheme("package"); getContext().registerReceiver(this, filter); . } @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { String action = intent.getAction(); String pkgList[] = null; if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { ... } else { ... Uri data = intent.getData(); if (data ! = null) { String pkg = data.getSchemeSpecificPart(); if (pkg ! = null) { pkgList = new String[]{pkg}; } } } if (pkgList ! = null && (pkgList. Length > 0)) {for (String PKG: pkgList) {// Remove alarm removeLocked(PKG); mPriorities.remove(pkg); for (int i=mBroadcastStats.size()-1; i>=0; i--) { ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(i); if (uidStats.remove(pkg) ! = null) { if (uidStats.size() <= 0) { mBroadcastStats.removeAt(i); } } } } } } } }Copy the code

Call the removeLocked() method in AlarmManagerService to remove the alarm associated with the package from the mAlarmBatches and mPendingWhileIdleAlarms queues.

8.2 Notification to clean up

[-> NotificationManagerService.java]

    private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            ...

            if (action.equals(Intent.ACTION_PACKAGE_ADDED)
                    || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
                    || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
                    || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
                    || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
                    || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
                int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                        UserHandle.USER_ALL);
                String pkgList[] = null;
                boolean queryReplace = queryRemove &&
                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
                    ...
                } else {
                    Uri uri = intent.getData();
                    ...
                    String pkgName = uri.getSchemeSpecificPart();
                    if (packageChanged) {
                        ...
                    }
                    pkgList = new String[]{pkgName};
                }

                if (pkgList != null && (pkgList.length > 0)) {
                    for (String pkgName : pkgList) {
                        if (cancelNotifications) {
                            //移除Notification
                            cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart,
                                    changeUserId, REASON_PACKAGE_CHANGED, null);
                        }
                    }
                }
                mListeners.onPackagesChanged(queryReplace, pkgList);
                mConditionProviders.onPackagesChanged(queryReplace, pkgList);
                mRankingHelper.onPackagesChanged(queryReplace, pkgList);
            }
        }
    };Copy the code

Call NotificationManagerService. In Java cancelAllNotificationsInt () method, as it relates to remove the bag from the mNotificationList queue Notification.

9. Cascading slaughter

Here to share with you a story of experience, remember before there was a BAT browser factory (specific name is hidden), the browser will be killed because another APP and lead to their innocent implicated by the killing, and suspected ROM customization caused by the bug, so sent an email to my factory for reasons.

Encountered this problem, first install the two apps to the Google native system, the result is still a cascade of slaughter, obviously can rule out the manufacturer ROM customization, according to the common sense of the bug should let the app to solve. Out of curiosity, I helped them further investigate this problem, and found that it was not innocent people who were killed, but the cascading killing of force-Stop.

App1 calls getClassLoader() to load App2, and the process running App1 adds App2’s package name to its pkgDeps queue. PkgDeps was mentioned earlier [section 3.2], and the queue is traversed during process killing. When App2 is killed by forceStop, it is a cascade killing of App1. Since App1 will call App2’s ClassLoader to load its methods, there is a certain connection. This is because Google intentionally gave forceStop this powerful feature.

The purpose of this story is to pay attention to this situation in the process of plugin or reflection to prevent unnecessary damage. Let’s talk more about how this process builds dependencies.

9.1 CI. GetClassLoader

[-> ContextImpl.java]

Public ClassLoader getClassLoader() {// return mPackageInfo! = null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader(); }Copy the code

9.2 LA. GetClassLoader

[-> LoadedApk.java]

public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader ! = null) { return mClassLoader; } if (mPackageName.equals("android")) { if (mBaseClassLoader == null) { mClassLoader = ClassLoader.getSystemClassLoader(); } else { mClassLoader = mBaseClassLoader; } return mClassLoader; } if (mRegisterPackage) {Binder, The final call to AMS. AddPackageDependency ActivityManagerNative. See section 9.3 】 【 getDefault (). AddPackageDependency (mPackageName); }... mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader); return mClassLoader; }}Copy the code

9.3 AMS. AddPackageDependency

public void addPackageDependency(String packageName) { synchronized (this) { int callingPid = Binder.getCallingPid(); if (callingPid == Process.myPid()) { return; } ProcessRecord proc; synchronized (mPidsSelfLocked) { proc = mPidsSelfLocked.get(Binder.getCallingPid()); } if (proc ! = null) { if (proc.pkgDeps == null) { proc.pkgDeps = new ArraySet<String>(1); } // add the packageName to pkgDeps proc.pkgdeps.add (packageName); }}}Copy the code

When ClassLoader is called to load the boot package name, the package name is added to the process’s pkgDeps.

Ten. Summary

ForceStop has the following functions:

  1. Process: call AMS killPackageProcessesLocked () to clear the package Process involved;
  2. Activity: call along. FinishDisabledPackageActivitiesLocked Activity involved () to clear the package;
  3. Service: call AS bringDownDisabledPackageServicesLocked () to clear the package Service involved;
  4. The Provider: call AMS removeDyingProviderLocked () to clear the package Provider involved;
  5. BroadcastRecevier: call the BQ. CleanupDisabledPackageReceiversLocked () to clear the package of the radio
  6. Running action_package_flow broadcasts used to stop registered alarms and notifications.

Function point summary:

  1. Force-stop does not kill persistent processes;
  2. When the APP is force-stopped, it cannot receive any ordinary broadcast, so it is certainly not feasible to monitor the changes of mobile phone network status or the broadcast when the screen is on and off to pull up the process.
  3. When the app is force-stop, the alarm alarm clock is cleaned up and the function of periodic ringing cannot be realized.
  4. After the APP is force-stopped, the four components and related processes are cut off and cleaned one by one. Even the app with multi-process architecture cannot pull itself up.
  5. Cascade killing: When app loads another app through ClassLoader, it will be cascade killing during force-stop.
  6. Life and death together: When the app and another app use the share UID, the other app will be killed in the force-stop process, establishing a strong relationship of life and death together.

Since force-stop has mentioned killing processes many times, let’s finally say a few words about keeping alive: The correct attitude of keeping alive should be to ensure that users do not be killed when they need it, and not to force users to keep alive when they do not need it. Everything is based on users.

  • Whether a process needs to survive, the upper layer of the system has AMS to manage the cache process and empty process, and the bottom layer has LowMemoryKiller to manage the process survival according to the available memory of the system. Such a strategy is from the perspective of system integrity, in order to provide users with better and smoother user experience.
  • Don’t get killed when you need it: Use plugins and shared Uids sparingly, unless you’re willing to accept cascading and life-and-death scenarios; In addition, it is the right way to improve the stability of app and reduce the frequency of crash and ANR.
  • Users do not need to strong don’t keep alive: in order to keep alive, multi-process architecture, use all sorts of tricks to enhance the priority is not desirable, such as a recruit force live – stop is enough to kill more than 90% of the insurance policy, of course, some other means and vulnerability to keep alive, the system level also tend to take some special methods to keep alive is prohibited. The blogger once worked on the performance and power consumption optimization at the bottom of the mobile phone, and was well aware of the rogue behavior of many apps and the serious system smoothness and battery life of the mobile phone.

In order to have a better user experience for Android, not to affect the performance of the mobile phone system, and not to reduce the battery life of the mobile phone, it is suggested that we spend more time on how to improve the robustness of the APP, how to optimize the app performance, and jointly build a good android ecosystem.

Gityuan