ActivityMangerService can recover a process that has been killed by the background (based on 4.3). In the FragmentActivity and PhoneWindow background kill mechanism, ActivityMangerService can recover a process that has been killed by the background. This paper briefly describes some common problems caused by background killing, as well as the Android system controls to do some compatibility of background killing, and onSaveInstance and onRestoreInstance in the execution of the time, finally said how to deal with background killing, However, there is no explanation of how a process that has been killed by the background can be recovered. This article does not cover background killing, such as LowmemoryKiller, but only how a killed process can be recovered. Suppose, for example, that an App is killed in the background, and the App is recalled again from the most recent task list. How does the system handle this? There are several issues that may need to be addressed:
- How does the Android Framework layer (AMS) know that an App has been killed
- How was the scene before the App was killed saved
- How does the system (AMS) recover a killed App
- What is the difference between the startup process of the App killed by the background and that of ordinary startup
- Why are activities restored in reverse order
How does the system (AMS) know that the App has been killed
First, how does the system know that an Application has been killed? Android uses Linux’s oomKiller mechanism, which is simply a variant of LowmemoryKiller, but this is actually kernel level. LowmemoryKiller does not send notifications to user space when it kills a process, meaning that the framework layer’s ActivityMangerService does not know if the App was killed, but only if the App or Activity is killed. When does AMS know that an App or Activity has been killed by the background? Let’s take a look at what happens when we invoke the most recent task list.
Call up the App’s flow again from the nearest task list or Icon
In the systemUi source code package, there is a RecentActivity, which is actually the entry of the recent task list, and its display interface is displayed through the RecentsPanelView, click the recent App, its execution code is as follows:
public voidhandleOnClick(View view) { ViewHolder holder = (ViewHolder)view.getTag(); TaskDescription ad = holder.taskDescription; final Context context = view.getContext(); final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); Bitmap bm = holder.thumbnailViewImageBitmap; .If TaskDescription is not actively closed, ad.taskId is >=0
if (ad.taskId >= 0) {
// This is an active task; it should just go to the foreground.
am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
opts);
} else {
Intent intent = ad.intent;
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
| Intent.FLAG_ACTIVITY_TASK_ON_HOME
| Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivityAsUser(intent, opts,
new UserHandle(UserHandle.USER_CURRENT));
}...
}Copy the code
In the above code, there is a test that ad.taskId >= 0. If this test is met, then the APP is invoked by moveTaskToFront. RecentTasksLoader: RecentTasksLoader: RecentTasksLoader: RecentTasksLoader: RecentTasksLoader
@Override
protected Void doInBackground(Void... params) {
// We load in two stages: first, we update progress with just the first screenful
// of items. Then, we update with the rest of the itemsfinal int origPri = Process.getThreadPriority(Process.myTid()); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final PackageManager pm = mContext.getPackageManager(); final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); . TaskDescription item = createTaskDescription(recentInfo.id, recentInfo.persistentId, recentInfo.baseIntent, recentInfo.origActivity, recentInfo.description); . }Copy the code
ActivityManger getRecentTasks requests the latest task information from AMS, and createTaskDescription creates the TaskDescription. Recentinfo. id is the taskId of TaskDescription, so let’s see what it means:
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { ... IPackageManager pm = AppGlobals.getPackageManager(); final int N = mRecentTasks.size(); . for (int i=0; i<N && maxNum > 0; i++) {
TaskRecord tr = mRecentTasks.get(i);
if (i == 0|| ((flags&ActivityManager.RECENT_WITH_EXCLUDED) ! =0)
|| (tr.intent == null)
|| ((tr.intent.getFlags()
&Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) {
ActivityManager.RecentTaskInfo rti
= new ActivityManager.RecentTaskInfo();
rti.id = tr.numActivities > 0 ? tr.taskId : - 1;
rti.persistentId = tr.taskId;
rti.baseIntent = newIntent( tr.intent ! =null ? tr.intent : tr.affinityIntent);
if(! detailed) { rti.baseIntent.replaceExtras((Bundle)null);
}Copy the code
Select * from TaskRecord where numActivities > 0; select * from TaskRecord where numActivities > 0; ActivityRecord ActivityRecord ActivityRecord ActivityRecord ActivityRecord ActivityManagerService ActivityStack As long as the APP is alive or killed by LowmemoryKiller, its AMS ActivityRecord is intact, which is the basis for recovery. RecentActivity does not know if the APP to be invoked is alive or not, as long as the TaskRecord tells RecentActivity that it is in stock. This is done by invoking the App via ActivityManager’s moveTaskToFront, and AMS takes care of the rest of the work. Take a look at the flow chart so far:
When AMS invokes the App, AMS detects if the App or Activity is killed abnormally
Moving on to moveTaskToFrontLocked, this function is in ActivityStack, which is basically managing the ActivityRecord stack, All start activities keep an ActivityRecord in ActivityStack, which is how AMS manages activities. ActivityStack will eventually call resumeTopActivityLocked to invoke the Activity, The main way FOR AMS to retrieve information about an Activity that is about to resume is through an ActivityRecord. It does not know if the Activity itself is alive or not. Take a look at resumeTopActivityLocked.
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
// This activity is now becoming visible.
mService.mWindowManager.setAppVisibility(next.appToken, true); . Recovery logicif(next.app ! =null&& next.app.thread ! =null) {
// Return to normal
try {
// Deliver all pending results.
ArrayList a = next.results;
if(a ! =null) {
final int N = a.size();
if(! next.finishing && N >0) { next.app.thread.scheduleSendResult(next.appToken, a); }}... next.app.thread.scheduleResumeActivity(next.appToken, mService.isNextTransitionForward()); . }catch (Exception e) {
// Whoops, need to restart this activity!
// There is a need to restart the server ???? Abnormal kill
if (DEBUG_STATES) Slog.v(TAG, "Resume failed; resetting state to "
+ lastState + ":" + next);
next.state = lastState;
mResumedActivity = lastResumedActivity;
<! -- It is true that the process is dead -->Slog.i(TAG, "Restarting because process died: " + next); . startSpecificActivityLocked(next, true, false); return true; }... }Copy the code
Since there is no active call to Finish, AMS does not clear ActivityRecord and TaskRecord, so resumes go to the above branch. Can here will call next. App. Thread. ScheduleSendResult or next. App. Thread. ScheduleResumeActivity to arouse on an Activity, but if the app or Activity killed by abnormal, If the APP is killed, the Binder thread that communicates with AMS will throw a RemoteException, which will go through the catch section below. Through startSpecificActivityLocked APP again restored, and the final Activity reconstruction, in fact you can use AIDL local write a C/S communication, at one end will be closed, and then use the other end of the visit, will throw RemoteException, the diagram below:
Alternatively, if the APP is not killed, but the Activity is killed, what happens? First of all, the Activity must be managed by AMS, and the kill of the Activity must be recorded by AMS. Strictly speaking, this situation does not belong to the background killing, because it belongs to the normal management of AMS. In controllable scope, for example, when the “Do not retain Activity” in developer mode is turned on, at this time, Although it kills the Activity, it still keeps the ActivitRecord, so there is still a trace when it wakes up or falls back. Take a look at the Destroy callback code for ActivityStack.
final boolean destroyActivityLocked(ActivityRecord r,
boolean removeFromApp, boolean oomAdj, String reason) {
...
if (hadApp) {
...
boolean skipDestroy = false;
try{key code1r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing, r.configChangeFlags); . if (r.finishing && ! skipDestroy) {if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYING: " + r
+ " (destroy requested)");
r.state = ActivityState.DESTROYING;
Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG);
msg.obj = r;
mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
} else{key code2
r.state = ActivityState.DESTROYED;
if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r);
r.app = null; }}return removedFromHistory;
} Copy the code
AcvitityThread clears the Activity and sets the state of the ActivityRecord to ActivityState.DESTROYED if AMS closes the Activity abnormally. And empty its ProcessRecord reference: R.pp = null. They should set ActivityState.DESTROYED is set to avoid the subsequent clearing logic.
final void activityDestroyed(IBinder token) {
synchronized (mService) {
final long origId = Binder.clearCallingIdentity();
try {
ActivityRecord r = ActivityRecord.forToken(token);
if(r ! =null) {
mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
}
int index = indexOfActivityLocked(r);
if (index >= 0) {
1<! Remove ActivityRecord from history list -->if (r.state == ActivityState.DESTROYING) {
cleanUpActivityLocked(r, true.false);
removeActivityFromHistoryLocked(r);
}
}
resumeTopActivityLocked(null);
} finally{ Binder.restoreCallingIdentity(origId); }}}Copy the code
If r.state == ActivityState, the last message will remove the ActivityRecord. DESTROYED, the state of the DESTROYED message will not be set to ActivityState.DESTROYING the DESTROYED message will destroy the DESTROYED message. So not removeActivityFromHistoryLocked, namely retained ActivityRecord scene, as if also rely on exceptions to distinguish whether the end of the normal Activity. How does the Activity start in this case? Two key points emerge from the above analysis
- ActivityRecord has not been moved from the HistoryRecord list
- The ProcessRecord field for ActivityRecord is null, with R. pp = null
Thus ensure the when resumeTopActivityLocked startSpecificActivityLocked branch
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { ... if (next.app ! =null&& next.app.thread ! =null) {... }else {
// Whoops, need to restart this activity!. startSpecificActivityLocked(next,true.true);
}
return true;
}Copy the code
At this point, AMS knows if the APP or Activity has been killed abnormally and decides whether to resume or restore.
How is the scene before the App is killed saved: the new Activity is started and the old Activity is saved
The saving process of the App site is relatively simple. When the entry is basically startActivity, the jump of the interface involves switching the Activity and saving the current Activity scene: Here’s a look at how AMS manages these jumps and saves the scene: When Activity A starts Activity B, A is not visible at this time and may be destroyed. What is the process of saving the scene of Activity A
- ActivityA startActivity ActivityB
- ActivityA pause
- ActivityB create
- ActivityB start
- ActivityB resume
- ActivityA onSaveInstance
- ActivityA stop
The process might look like this:
Now let’s go through the source code step by step and see what AMS does when starting a new Activity and saving an old one: skip the simple startActivity and go straight to AMS
ActivityManagerService
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity"); . return mMainStack.startActivityMayWait(caller,- 1, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null.null, options, userId);
}Copy the code
ActivityStack
final int startActivityMayWait(IApplicationThread caller, int callingUid,
int res = startActivityLocked(caller, intent, resolvedType,
aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
callingPackage, startFlags, options, componentSpecified, null); . }Copy the code
StartActivityMayWait starts a new APP, or a new Activity, using startActivityMayWait. ResumeTopActivityLocked is a unified entry point for interface switching. The first time you enter, ActivityA will not be paused, so you will need to pause ActivityA.
ActivityStack
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { ... <! The Activity must be set to pause and then stop to Resume.// We need to start pausing the current activity so the top one
// can be resumed...
if(mResumedActivity ! =null) {
if(next.app ! =null&& next.app.thread ! =null) {
mService.updateLruProcessLocked(next.app, false);
}
startPausingLocked(userLeaving, false);
return true; }... }Copy the code
The Binder tells AMS to suspend the ActivityA thread. When the ActivityThread is finished, the Binder tells AMS to resume the ActivityB thread.
private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
if(prev.app ! =null&& prev.app.thread ! =null) {... try { prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, userLeaving, prev.configChangeFlags);Copy the code
ActivityThread
private void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges) {
ActivityClientRecord r = mActivities.get(token);
if(r ! =null) {... performPauseActivity(token, finished, r.isPreHoneycomb()); .// Tell the activity manager we have paused.
try {
ActivityManagerNative.getDefault().activityPaused(token);
} catch (RemoteException ex) {
}
}
}Copy the code
When AMS receives A pause message from ActivityA, it will invoke ActivityB, resumeTopActivityLocked, and wake UP ActivityB. After that, IT will further stop ACTIvitylocked.
ActivityStack
private final void completePauseLocked() {
if(! mService.isSleeping()) { resumeTopActivityLocked(prev); }else{... }Copy the code
After ActivityB is up, it will stop ActivityA through ActivityStack stopActivityLocked to stop ActivityA.
private final voidstopActivityLocked(ActivityRecord r) { ... if (mMainStack) { r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags); . }Copy the code
Back to the APP side, look at the call in ActivityThread: first by callActivityOnSaveInstanceState, to save the scene in the Bundle,
private void performStopActivityInner(ActivityClientRecord r,
StopInfo info, boolean keepShown, boolean saveState) {
...
// Next have the activity save its current state and managed dialogs...
if(! r.activity.mFinished && saveState) {if (r.state == null) {
state = new Bundle();
state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); r.state = state; . }Copy the code
Later, through ActivityManagerNative. GetDefault (.) activityStopped, notify the AMS, Stop action at the time of notification, will also save the field data of the past.
private static class StopInfo implements Runnable {
ActivityClientRecord activity;
Bundle state;
Bitmap thumbnail;
CharSequence description;
@Override public void run() {
// Tell activity manager we have been stopped.
try {
ActivityManagerNative.getDefault().activityStopped(
activity.token, state, thumbnail, description);
} catch (RemoteException ex) {
}
}
}Copy the code
Through the above process, AMS not only started a new Activity, but also saved the site of the previous Activity. Even if the previous Actiivity was killed due to various reasons, AMS can also know how to arouse Activiyt and restore it in the process of backtracking or re-awakening.
Now solve two problems: 1, how to save the scene, 2, how AMS can determine whether the APP or Activity is killed by an exception, then the last question remains: how AMS can recover the APP or Activity that is killed by an exception?
Recovery logic in case the entire Application is killed by the background
In fact, when explaining how AMS determines whether an APP or Activity is killed abnormally, we have already involved the logic of recovery. We also know that once AMS knows that the APP is killed by the background, it is not a normal resuem process, but a new laucher. ResumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { .... if (next.app ! =null&& next.app.thread ! =null) {
if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: "+ next); . try { ... }catch (Exception e) {
// Whoops, need to restart this activity!Here is the slog. I (TAG,"Restarting because process died: " + next);
next.state = lastState;
mResumedActivity = lastResumedActivity;
Slog.i(TAG, "Restarting because process died: " + next);
startSpecificActivityLocked(next, true.false);
return true;
}Copy the code
Can know from the above code, is actually walk startSpecificActivityLocked, there is no much difference in this first from the desktop to raise APP, just one thing to note, that is the time to launch the Activity is the last time the field data of decent, Since all of the Activity scenes were saved and passed to AMS when it was backlogged, this recovery startup returns this data to the ActivityThread. Take a closer look at the special handling code for recovery in performLaunchActivity:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if(r.state ! =null) { r.state.setClassLoader(cl); }}catch (Exception e) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation); . The key point1mInstrumentation.callActivityOnCreate(activity, r.state); . r.activity = activity; r.stopped =true;
if(! r.activity.mFinished) { activity.performStart(); r.stopped =false; } the key1
if(! r.activity.mFinished) {if(r.state ! =null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); }}if(! r.activity.mFinished) { activity.mCalled =false; mInstrumentation.callActivityOnPostCreate(activity, r.state); . }Copy the code
Look at the key points 1 and 2, 1, look at the key points. MInstrumentation callActivityOnCreate will callback Actiivyt onCreate, this function is mainly aimed at FragmentActivity do some fragments recovery work, The r.state in ActivityClientRecord is passed by AMS to the APP to restore the scene, and these are null on normal startup. Key point 2, at R. State! . = null is not empty when performing mInstrumentation callActivityOnRestoreInstanceState, this function default is mainly aimed at the Window with some recovery work, such as display position before ViewPager recovery, etc., It can also be used to recover user-saved data.
The Application is not killed by the background, and the Activity resumes after being killed
In the above analysis, we know that when AMS actively kills an Activity, the AcitivityRecord app field is null, so resumeTopActivityLocked is not the same as when the whole app is killed. It’s going to go down the branch
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { ... if (next.app ! =null&& next.app.thread ! =null) {... }else{point1Just restart the Activity, so you know that the process is not dead,// Whoops, need to restart this activity!
startSpecificActivityLocked(next, true.true);
}
return true;
}Copy the code
Although different, but also go startSpecificActivityLocked process, just don’t new APP process, the rest are all the same, no longer. I think we should get the idea at this point,
- How does Android save scenarios in a preventative way
- How does AMS know if an APP has been killed by the background
- How does AMS reconstruct the scene of an APP being killed from ActivityStack
This is the end of the logic for ActivityManagerService to restore the APP scenario. Mumble a few more questions, maybe some interview points.
- The difference between active clearing of recent tasks and abnormal killing: Whether ActivityStack clears normally
- Why restore in reverse order: this is the stack order of HistoryRecord in ActivityStack, strictly on the AMS side
- In a word, the principle of Android background Kill recovery is summarized: The Application process is killed, but the scene is saved by AMS, and AMS can restore the Application scene according to the save
Android background Kill (FragmentActivity, PhoneWindow) LowMemoryKiller (4.3-6.0) LowMemoryKiller (4.3-6.0) Binder Obituary Principle Android Background Kill series 5: Android process alive – “cut” or rogue
For reference only, welcome correction
Reference documentation
Android application startup process source code analysis Android Framework architecture analysis Android Low Memory Killer introduction to Android development InstanceState details Android — Memory management — Lowmemorykiller Mechanism Android operating system memory reclamation mechanism Android LowMemoryKiller Principle Analysis of the life cycle of Android processes and ADJ /proc directory in Linux startActivity Startup process Analysis of the Activity destruction process