This is the third day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

After executing the Activity’s Finish () method, I did some business logic in onDestroy(), which was blocked by a business in the previous Activity lifecycle. After a long time of searching, Finish () executes the Activity’s onDestroy() lifecycle, and then re-executes the onDestroy() method of the finished () Activity. I looked into why onDestroy() was not called back after Finish () and the underlying logic involved.

OnStop /onDestroy without a timely callback

OnDestroy () is called 10s after activity.Finish ().

Before looking at the source code, reproduce the scenario of 10s onDestroy(), write the simplest scenario where FirstActivity jumps to SecondActivity, and note the various life cycles and the interval between calls to Finish ().

class FirstActivity : BaseLifecycleActivity() { private val binding by lazy { ActivityFirstBinding.inflate(layoutInflater) } var startTime = 0L override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) setContentView(binding.root) binding.goToSecond.setOnClickListener { start<SecondActivity>() finish() startTime = System.currentTimeMillis() } } override fun onPause() { super.onPause() Log.e("finish","onPause() distance finish() : ${system.currentTimemillis () -startTime} ms")} Override fun onStop() {super.onstop () log.e ("finish","onStop() distance Finish () : ${System.currentTimeMillis() - startTime} ms") } override fun onDestroy() { super.onDestroy() Log.e(" Finish ","onDestroy() distance Finish () : ${system.currentTimemillis () -startTime} ms")}}Copy the code

SecondActivity is a normal blank Activity that does nothing. Click the button to jump to SecondActivity and print the log as follows:

1FirstActivity: onPause, onPause() Distance Finish () : 5 ms 2SecondActivity: onCreate 3SecondActivity: onStart 4SecondActivity: OnResume 5FirstActivity: onStop, onStop() Distance Finish () : 660 ms 6FirstActivity: onDestroy, onDestroy() distance Finish () : 663 ms

As you can see, under normal circumstances, after FirstActivity calls onPause, SecondActivity starts the normal lifecycle process until onResume is called back and made visible to the user, FirstActivity calls onPause and onDestroy. And the time intervals were within the normal range.

Let’s simulate a scenario where SecondActivity starts with a lot of animation, pouring messages into the main thread message queue. Modify the SecondActivity code.

1class SecondActivity : BaseLifecycleActivity() { 2 3 private val binding by lazy { ActivitySecondBinding.inflate(layoutInflater) } 4 5 override  fun onCreate(savedInstanceState: Bundle?) { 6 super.onCreate(savedInstanceState) 7 setContentView(binding.root) 8 9 postMessage() 10 } 11 12 private fun postMessage() { 13 binding.secondBt.post { 14 Thread.sleep(10) 15 postMessage() 16 } 17 } 18}Copy the code

1FirstActivity: onPause, onPause() Distance Finish () : 6 ms 2SecondActivity: onCreate 3SecondActivity: OnStart 4SecondActivity: onResume 5FirstActivity: onStop, onStop() Distance Finish () : 10033 ms 6FirstActivity: OnDestroy, onDestroy() Distance Finish () : 10037 ms

FirstActivity’s onPause() is not affected. Because during an Activity jump, the target Activity does not begin its normal life cycle until after the previous Activity onPause(). OnStop and onDestroy() took a full 10s to call back.

Comparing these two scenarios, we can guess that when the main thread of SecondActivity is too busy to pause for breath, FirstActivity cannot call onStop and onDestroy in time. Based on the above speculation, we can look to AOSP to find the answer.

What follows is a lot of boring source code analysis. Reading AOSP with questions in mind makes the process less “boring” and is sure to make a difference.

From the Activity. The finish ()

The following source code is based on Android version 9.0.

1> Activity.java
2
3  public void finish() {
4    finish(DONT_FINISH_TASK_WITH_ACTIVITY);
5}
Copy the code

Overrides the finish() method with arguments. The parameter is DONT_FINISH_TASK_WITH_ACTIVITY, which is also straightforward and does not destroy the task stack on which the Activity is located.

1> activity.java 2 3 private void finish(int finishTask) {4 // mParent = null, 5 if (mParent == null) {6...... is used in ActivityGroup 7 the try {8 / / Binder call AMS. FinishActivity (9) if (ActivityManager. GetService () 10. FinishActivity (mToken, the resultCode, resultData, finishTask)) { 11 mFinished = true; 12 } 13 } catch (RemoteException e) { 14 } 15 } else { 16 mParent.finishFromChild(this); 17} 18... 19}Copy the code

The mParent is null in most cases, not the else branch. Some older Android programmers may be aware of ActivityGroup, in which case mParent may not be null. (I haven’t used ActivityGroup since I’m young, so I won’t have to explain.) Binder calls the ams.finishActivity () method.

1> ActivityManagerService.java 2 3 public final boolean finishActivity(IBinder token, int resultCode, Intent resultData, 4 int finishTask) { 5 ...... 7 6 synchronized (this) {/ / token hold ActivityRecord weak references ActivityRecord r = ActivityRecord. 8 isInStackLocked (token); 9 if (r == null) { 10 return true; 11} 12... 13 try { 14 boolean res; 15 final boolean finishWithRootActivity = 16 finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY; 17 // finishTask is DONT_FINISH_TASK_WITH_ACTIVITY, Enter the else branch 18 if (finishTask = = Activity. FINISH_TASK_WITH_ACTIVITY 19 | | (finishWithRootActivity && r = = rootR)) {20 res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, 21 finishWithRootActivity, "finish-activity"); 22} else {23 / / call ActivityStack requestFinishActivityLocked 24 res = () tr.getStack().requestFinishActivityLocked(token, resultCode, 25 resultData, "app-request", true); 26 } 27 return res; 28 } finally { 29 Binder.restoreCallingIdentity(origId); 30} 31} 32}Copy the code

Note the token object in the method argument, which is a static inner class of ActivityRecord that holds a weak reference to the external ActivityRecord. Stub, inherited from iApplicationToken. Stub, is a Binder object. An ActivityRecord is a detailed description of the current Activity and contains all information about the Activity.

The parameters of the incoming finishTask () method is DONT_FINISH_TASK_WITH_ACTIVITY, so then will call ActivityStack. RequestFinishActivityLocked () method.

1> ActivityStack.java 2 3 final boolean requestFinishActivityLocked(IBinder token, int resultCode, 4 Intent resultData, String reason, boolean oomAdj) { 5 ActivityRecord r = isInStackLocked(token); 6 if (r == null) { 7 return false; 8 } 9 10 finishActivityLocked(r, resultCode, resultData, reason, oomAdj); 11 return true; 12} 13 14 final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, 15 String reason, Boolean oomAdj) {16 // PAUSE_IMMEDIATELY = true, 17 Return finishActivityLocked(R, resultCode, resultData, Reason, oomAdj,! PAUSE_IMMEDIATELY); 18}Copy the code

The last call is an overloaded finishActivityLocked() method.

3 // pauseImmediately is false 4 Final Boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, 5 String reason, boolean oomAdj, Boolean pauseImmediately) {6 if (r.ishing) {return false; 8 } 9 10 mWindowManager.deferSurfaceLayout(); 14 r.makeFinishinglocked (); 14 r.makefinishinglocked (); 15 final TaskRecord task = r.getTask(); 16... 17 / / pause event distribution of 18 r.p auseKeyDispatchingLocked (); 19 20 adjustFocusedActivityStack(r, "finishActivity"); 21 and 22 / / processing activity result 23 finishActivityResultsLocked (r, the resultCode, resultData); 26 if (mResumedActivity == r) {27...... 28 // Tell window manager to prepare for this one to be removed. 29 r.setVisibility(false); 30 31 If (mPausingActivity == null) {32 // Start Pause mResumedActivity 33 startPausingLocked(false, false, null, pauseImmediately); \ 34} 35...... 36 } else if (! R.isstate (PAUSING)) {37 // Does not enter this branch 38...... 39 } 40 return false; 41 } finally { 42 mWindowManager.continueSurfaceLayout(); 44 43}}Copy the code

Pause the current Activity after you call Finish. Next, look at the startPausingLocked() method.

1> ActivityStack.java 2 3 final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, 4 ActivityRecord resuming, boolean pauseImmediately) { 5 ...... 6 ActivityRecord prev = mResumedActivity; 7 8 if (prev == null) {9 // No onResume Activity, 10 if cannot perform pause (resuming = = null) {11 mStackSupervisor. ResumeFocusedStackTopActivityLocked (); 12 } 13 return false; 14} 15... 16 17 mPausingActivity = prev; SetState (PAUSING, "startPausingLocked"); 18 // Set the current Activity state to PAUSING. 20... 21 22 if (prev.app ! = null && prev.app.thread ! = null) { 23 try { 24 ...... 25 // 1. Distribute the lifecycle event 26 through ClientLifecycleManager // Finally send the EXECUTE_TRANSACTION event 27 to H mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken, 28 PauseActivityItem.obtain(prev.finishing, userLeaving, 29 prev.configChangeFlags, pauseImmediately)); 30 } catch (Exception e) { 31 mPausingActivity = null; 32 } 33 } else { 34 mPausingActivity = null; 35} 36... 38 if (mPausingActivity! = null) { 39 ...... 40 If (pauseImmediately) {// here is false, go to else branch 41 completePauseLocked(false, scalar); 42 return false; 43} else {44 // 2. Send a 500ms delay message, 45 // activityPausedLocked() is finally called back 46 schedulePauseTimeout(prev); 47 return true; 48} 49} else {50 // will not enter the branch 51} 52}Copy the code

There are two key steps. The first step is to distribute the lifecycle process through ClientLifecycleManager at comment 1. The second step is to send a 500ms delay message and wait for the onPause process. However, if the process has been completed within 500ms in the first step, the message is cancelled. So the final logic of these two steps is actually the same. So let’s go straight to step one.

1 mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,
2                        PauseActivityItem.obtain(prev.finishing, userLeaving,
3                                prev.configChangeFlags, pauseImmediately));
Copy the code

ClientLifecycleManager It sends the EXECUTE_TRANSACTION event to Handler H of the main thread, calling the execute() and postExecute() methods of XXXActivityItem. Binder calls the corresponding handleXXXActivity() method in ActivityThread. Here is handlePauseActivity () method, which will pass Instrumentation. CallActivityOnPause (state Richard armitage ctivity) method callback Activity. The onPause ().

1> Instrumentation.java
2
3public void callActivityOnPause(Activity activity) {
4    activity.performPause();
5}
Copy the code

At this point, the onPause() method is executed. But the process does not end, and it is time to display the next Activity. As mentioned earlier, the execute() and postExecute() methods of PauseActivityItem are called. The execute() method calls back the current activity.onpause (), while the postExecute() method finds the Activity to display.

1> PauseActivityItem.java 2 3public void postExecute(ClientTransactionHandler client, IBinder token, 4 PendingTransactionActions pendingActions) { 5 try { 6 ActivityManager.getService().activityPaused(token); 7 } catch (RemoteException ex) { 8 throw ex.rethrowFromSystemServer(); 10 9}}Copy the code

Binder calls the ams.activitypaUsed () method.

1> ActivityManagerService.java 2 3public final void activityPaused(IBinder token) { 4 synchronized(this) { 5 ActivityStack stack = ActivityRecord.getStackLocked(token); 6 if (stack ! = null) { 7 stack.activityPausedLocked(token, false); 8} 9} 10}Copy the code

Call the ActivityStack. ActivityPausedLocked () method.

1> ActivityStack.java 2 3final void activityPausedLocked(IBinder token, boolean timeout) { 4 final ActivityRecord r = isInStackLocked(token); 5 if (r ! Mhandler. removeMessages(PAUSE_TIMEOUT_MSG, r); 8 if (mPausingActivity == r) { 9 mService.mWindowManager.deferSurfaceLayout(); CompletePauseLocked (true /* resumeNext */, null /* resumingActivity */); 13 } finally { 14 mService.mWindowManager.continueSurfaceLayout(); 15 } 16 return; 17} else {18 // will not enter else branch 19} 20} 21}Copy the code

The line mhandle.removemessages (PAUSE_TIMEOUT_MSG, r) removes the message that was delayed by 500ms. Now look at the completePauseLocked() method.

1> ActivityStack.java 2 3private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) { 4 ActivityRecord prev = mPausingActivity; 5 6 if (prev ! SetState (PAUSED, "completePausedLocked"); 9 if (prev.finishing) {// 1. Finishing is true, Into this branch 10 prev = finishCurrentActivityLocked (prev FINISH_AFTER_VISIBLE, false, 11 "completedPausedLocked"); 12 } else if (prev.app ! = null) {13} else {15 prev = null; 16} 17... 20} 19 20 if (resumeNext) {21 // The ActivityStack in focus is final ActivityStack topStack = mStackSupervisor.getFocusedStack(); 23 if (! topStack.shouldSleepOrShutDownActivities()) { 24 // 2. Restore to display the activity of 25 mStackSupervisor. ResumeFocusedStackTopActivityLocked (topStack, prev, null); 26 } else { 27 checkReadyForSleep(); 28 ActivityRecord top = topStack.topRunningActivityLocked(); 29 if (top == null || (prev ! = null && top ! = prev)) { 30 mStackSupervisor.resumeFocusedStackTopActivityLocked(); 31} 32} 33} 34...... 35}Copy the code

In two steps. Note 1 judges the finishing state. Remember where finishing was assigned to true? In the Activity. The finish () – > AMS. FinishActivity () – > ActivityStack. RequestFinishActivityLocked () – > ActivityStack. FinishActivityLocked () method. So then call the finishCurrentActivityLocked () method. Comment 2 shows the Activity that should be displayed.

With the finishCurrentActivityLocked again () method, and see this name, affirmation is to stop/destroy no more than.

1> activitystack. Java 2 3/* 4 * prev, FINISH_AFTER_VISIBLE, false,"completedPausedLocked" 6 */ 7final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj, Eight String "reason) {9 10 / / the Activity to be displayed for stack 11 final ActivityRecord next = mStackSupervisor. TopRunningActivityLocked ( 12 true /* considerKeyguardState */); 1. Mode is FINISH_AFTER_VISIBLE, Enter 15 if this branch (mode = = FINISH_AFTER_VISIBLE && (r.v isible | | r.n owVisible) 16 && next! = null && ! next.nowVisible) { 17 if (! MStackSupervisor. MStoppingActivities. The contains (r)) {/ / 18 to join mStackSupervisor. MStoppingActivities 19 addToStopping (r, false /* scheduleIdle */, false /* idleDelayed */); 20} 21 / / set the state to STOPPING 22 r.s etState (STOPPING, "finishCurrentActivityLocked"); 23 return r; 24} 25 26...... 27 28 // Execute destroy, 29 here but the code does not perform the if (mode = = FINISH_IMMEDIATELY 30 | | (prevState = = PAUSED 31 && (mode = = FINISH_AFTER_PAUSE | | inPinnedWindowingMode())) 32 || finishingActivityInNonFocusedStack 33 || prevState == STOPPING 34 || prevState == STOPPED 35 || prevState == ActivityState.INITIALIZING) { 36 boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm:" + reason); 37... 38 return activityRemoved ? null : r; 39} 40... 41}Copy the code

Note 1 in the mode of value is FINISH_AFTER_VISIBLE, and now the new Activity is not onResume, so r.v isible | | r.n owVisible and next! = null && ! Next-nowvisible is valid and does not enter the destroy flow. I didn’t get the answer I was looking for, but at least it was in line with expectations. If you destroy right here, the problem of delaying onDestroy by 10 seconds is solved.

The addToStopping(r, false, false) method is executed for all activities that are not yet destroyed. Let’s keep going after them.

1> ActivityStack.java 2 3void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed) { 4 if (! mStackSupervisor.mStoppingActivities.contains(r)) { 5 mStackSupervisor.mStoppingActivities.add(r); 6... 7} 8... 9 // In the omitted code, the storage capacity of mStoppingActivities is limited. Exceedances may result in early departure of destruction process 10}Copy the code

The activities awaiting destruction are saved in the mStoppingActivities collection of ActivityStackSupervisor, which is an ArrayList

.

This is the end of the finish process. Before an Activity is saved in the ActivityStackSupervisor. MStoppingActivities set, the new Activity is displayed.

When to call onStop/onDestroy? Actually, that’s the fundamental problem. Finish () does not see the essence, but it can help us form a complete process, which has always been the biggest significance of AOSP, helping us to form a complete closed loop of fragmentary upper-level knowledge.

Who directs the onStop/onDestroy call?

To get back to the point, during an Activity jump, as long as the previous Activity does not interact with the user, that is, after onPause() is called back, the next Activity starts its own lifecycle to ensure a smooth user experience. So onStop/onDestroy is called at an indeterminate time, even after a full 10 seconds as in the example at the beginning of this article. So, who drives the execution of onStop/onDestroy? Let’s look at the onResume process for the next Activity.

Directly see ActivityThread. HandleResumeActivity () method, I believe everyone calls to life cycle process is also very familiar with.

1> ActivityThread.java 2 3public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, 4 String reason) { 5 ...... 6 // Callback onResume 7 Final ActivityClientRecord r = performResumeActivity(Token, finalStateRequest, Reason); 8... 9 final Activity a = r.activity; 10... 11 if (r.window == null && ! a.mFinished && willBeVisible) { 12 ...... 13 if (a.mVisibleFromClient) { 14 if (! a.mWindowAdded) { 15 a.mWindowAdded = true; 16 // Add decorView to WindowManager 17 wm.addView(decor, L); 18 } else { 19 a.onWindowAttributesChanged(l); 20 } 21 } 22 } else if (! willBeVisible) { 23 ...... 24} 25... 27 // Idler 28 looper.myQueue ().addidleHandler (new Idler()); 29}Copy the code

The handleResumeActivity() method is the most important part of the UI display process. It first calls back activity.onResume () and then adds the DecorView to the Window, which in turn includes creating ViewRootImpl, Create Choreographer, Binder communication with WMS, register vsync signals, famous measure/ Draw/Layout. This piece of source code is really worth reading, but it is not the focus of this article and will be covered separately later.

After the final interface is drawn and displayed, there is the code looper.myQueue ().addidleHandler (new Idler()). If you’re familiar with IdleHandler, it provides a mechanism to execute the callback method of IdleHandler when the main thread message queue is idle. As for “idle,” we can look at the messagequyue.next () method.

1> MessageQueue.java 2 3Message next() { 4 ...... 5 int pendingIdleHandlerCount = -1; 6 int nextPollTimeoutMillis = 0; 7 for (;;) {8 // Blocking methods are implemented primarily by native layer epoll listening for write events of file descriptors. 9 // If nextPollTimeoutMillis = -1, the block will not timeout. 10 // If nextPollTimeoutMillis = 0, it will not be blocked and will be returned immediately. 11 // If nextPollTimeoutMillis > 0, nextPollTimeoutMillis will be blocked for milliseconds (timeout), and will be returned immediately if any program wakes up during the timeout. 12 nativePollOnce(ptr, nextPollTimeoutMillis); 13 synchronized (this) { 14 Message prevMsg = null; 15 Message msg = mMessages; 16 if (msg ! MSG. Target == null) {17 // MSG. Target == null 19 do {20 prevMsg = MSG; 19 do {20 prevMsg = MSG; 21 msg = msg.next; 22 } while (msg ! = null && ! msg.isAsynchronous()); 23 } 24 if (msg ! = null) {25 if (now < msg.when) {26 27 nextPollTimeoutMillis = (int) math.min (msg.when - now, integer.max_value); 28} else {29 // Get Message 30 mBlocked = false; 31 if (prevMsg ! = null) { 32 prevMsg.next = msg.next; 33 } else { 34 mMessages = msg.next; 35 } 36 msg.next = null; 37 msg.markInUse(); // FLAG_IN_USE 38 return MSG; 39 } 40 } else { 41 nextPollTimeoutMillis = -1; 42} 43... PendingIdleHandlerCount = -1 48 * 2. The mMessage is empty or taken 49 * / 50 if need to deal with the delay (pendingIdleHandlerCount < 0 51 && (mMessages = = null | | now < mMessages. When)) { 52 pendingIdleHandlerCount = mIdleHandlers.size(); PendingIdleHandlerCount (pendingIdleHandlerCount <= 0) {55 mBlocked = true; 57 continue; 58 } 59 60 if (mPendingIdleHandlers == null) { 61 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; 62 } 63 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); PendingIdleHandlerCount = -1 for (int I = 0; i < pendingIdleHandlerCount; i++) { 68 final IdleHandler idler = mPendingIdleHandlers[i]; 69 mPendingIdleHandlers[i] = null; // release the reference to the handler 70 71 boolean keep = false; 72 try {73 // Run Idler 74 keep = idler.queueidle (); 75 } catch (Throwable t) { 76 Log.wtf(TAG, "IdleHandler threw exception", t); 77 } 78 79 if (! keep) { 80 synchronized (this) { 81 mIdleHandlers.remove(idler); PendingIdleHandlerCount = 0; pendingIdleHandlerCount = 0; 88 nextPollTimeoutMillis = 0; 90 89}}Copy the code

Additional processing of the IdleHandler is done after the normal message processing mechanism. The IdleHandler object in the mIdleHandlers array is executed when the Message is empty or when delayed processing is required. There is also some additional logic about pendingIdleHandlerCount to prevent looping.

So, unsurprisingly, when the new Activity has finished drawing and displaying the page, the main thread can pause to execute the IdleHandler. Back in handleResumeActivity(), looper.myQueue ().addidleHandler (new Idler()), where Idler is a concrete implementation of IdleHandler.

1> ActivityThread.java 2 3private class Idler implements MessageQueue.IdleHandler { 4 @Override 5 public final boolean queueIdle() { 6 ActivityClientRecord a = mNewActivities; 7... 8 } 9 if (a ! = null) { 10 mNewActivities = null; 11 IActivityManager am = ActivityManager.getService(); 12 ActivityClientRecord prev; 13 do { 14 if (a.activity ! = null && ! A.activity. MFinished) {15 try {16 // call ams.activityIdle () 17 am.activityidle (a.token, a.createdConfig, stopProfiling); 18 a.createdConfig = null; 19 } catch (RemoteException ex) { 20 throw ex.rethrowFromSystemServer(); 21 } 22 } 23 prev = a; 24 a = a.nextIdle; 25 prev.nextIdle = null; 26 } while (a ! = null); 27} 28... 29 return false; 30}} 31Copy the code

Binder calls ams.activityIdle ().

1> ActivityManagerService.java 2 3public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) { 4 5 final long origId = Binder.clearCallingIdentity(); 6 synchronized (this) { 7 ActivityStack stack = ActivityRecord.getStackLocked(token); 8 if (stack ! = null) { 9 ActivityRecord r = 10 mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */, 11 false /* processPausingActivities */, config); 12... 13} 14} 15}Copy the code

Call the ActivityStackSupervisor. ActivityIdleInternalLocked () method.

1> ActivityStackSupervisor.java 2 3final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, 4 boolean processPausingActivities, Configuration config) { 5 6 ArrayList<ActivityRecord> finishes = null; 7 ArrayList<UserState> startingUsers = null; 8 int NS = 0; 9 int NF = 0; 10 boolean booting = false; 11 boolean activityRemoved = false; 12 13 ActivityRecord r = ActivityRecord.forTokenLocked(token); 14 15... 16 / / access to stop the Activity of 17 final ArrayList < ActivityRecord > stops = processStoppingActivitiesLocked (r, 18 true /* remove */, processPausingActivities); 19 NS = stops ! = null ? stops.size() : 0; 20 if ((NF = mFinishingActivities.size()) > 0) { 21 finishes = new ArrayList<>(mFinishingActivities); 22 mFinishingActivities.clear(); // stop 26 for (int I = 0; i < NS; i++) { 27 r = stops.get(i); 28 final ActivityStack stack = r.getStack(); 29 if (stack ! = null) { 30 if (r.finishing) { 31 stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false, 32 "activityIdleInternalLocked"); 33 } else { 34 stack.stopActivityLocked(r); 35} 36} 37} 38 39 // The destroy 40 for (int I = 0; i < NF; i++) { 41 r = finishes.get(i); 42 final ActivityStack stack = r.getStack(); 43 if (stack ! = null) { 44 activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle"); 45} 46} 47...... 48 49 return r; 50}Copy the code

And stops and finishes are two arrays of activityrecords to stop and destroy. Stops the array is through ActivityStackSuperVisor processStoppingActivitiesLocked () method to obtain, after go in to look at.

1> ActivityStackSuperVisor.java 2 3final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity, 4 boolean remove, boolean processPausingActivities) { 5 ArrayList<ActivityRecord> stops = null; 6 7 final boolean nowVisible = allResumedActivitiesVisible(); ActivityNdx = mstoppingActivities.size () - 1; activityNdx >= 0; --activityNdx) { 10 ActivityRecord s = mStoppingActivities.get(activityNdx); 11... 12 } 13 return stops; 14}Copy the code

Instead of looking at the detailed processing logic in the middle, let’s just focus on the mStoppingActivities collection in ActivityStackSuperVisor. As mentioned earlier in the analysis of the addToStopping() method at the end of the Finish () process, the activities awaiting destruction are saved in the mStoppingActivities collection of the ActivityStackSupervisor. It is an ArrayList

. So here we are, finally getting through the process. Back to the example at the beginning of this article, onStop/onDestroy would not be called back because Idler could not be executed because the main thread in SecondActivity was continuously blocked with messages.

Who delayed onStop/onDestroy by 10s?

Yeah, it doesn’t get called back. But is that really the case? No, it was called back 10 seconds later. This means that even if the main thread does not have a chance to execute the Idler, the system still provides a backstop mechanism to prevent activities that are no longer needed from being recycled for a long time, causing problems such as memory leaks. From the actual phenomenon, we can guess that the bottom pocket mechanism is the onResume 10 seconds after the active release operation.

Back to before the show to jump the Activity ActivityStackSuperVisor. ResumeFocusedStackTopActivityLocked () method. I’m not going to chase you down here, but I’m going to give you the chain of calls.

ASS.resumeFocusedStackTopActivityLocked() ->

ActivityStack.resumeTopActivityUncheckedLocked() ->

ActivityStack.resumeTopActivityInnerLocked() ->

ActivityRecord.completeResumeLocked() -> ASS.scheduleIdleTimeoutLocked()

1>  ActivityStackSuperVisor.java
2
3  void scheduleIdleTimeoutLocked(ActivityRecord next) {
4    Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next);
5    mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);
6}
Copy the code

The value of IDLE_TIMEOUT is 10, where a message is sent 10 seconds later. This message is in ActivityStackSupervisorHandler processing.

1 private final class ActivityStackSupervisorHandler extends Handler { 2...... 3 case IDLE_TIMEOUT_MSG: { 4 activityIdleInternal((ActivityRecord) msg.obj, true /* processPausingActivities */); 5 } break; 6... 7} 8 9 void activityIdleInternal(ActivityRecord r, boolean processPausingActivities) { 10 synchronized (mService) { 11 activityIdleInternalLocked(r ! = null ? r.appToken : null, true /* fromTimeout */, 12 processPausingActivities, null); 14 13}}Copy the code

Forget what activityIdleInternalLocked method can search CTRL + F upward. If the main thread executes an Idler within 10 seconds, the message is removed.

At this point, all the questions will be cleared up.

The last

Activity onStop/onDestroy relies on IdleHandler to call back and forth and is normally called when the main thread is idle. However, in some special scenarios, the main thread cannot be idle and onStop/onDestroy cannot be called. This does not mean that the Activity will never be retrieved, however. The system provides a backstop mechanism that will be triggered if the onResume callback continues for 10 seconds.

There is a bottom-feeding mechanism, but that’s certainly not what we want anyway. If onStop/onDestroy in our project is called 10 seconds late, how can we troubleshoot the problem? The looper.getMainLooper ().setMessagelogging () method can be used to print messages from the main thread message queue. Each time a message is processed, the following is printed:

1logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
2logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
Copy the code

In addition, due to the uncertainty of the call timing of onStop/onDestroy, it is important to consider when performing operations such as resource release to avoid the situation that resources are not released in a timely manner.