面试官:Activity finish 后,会立即回调 onDestory() 吗?若不会,最长延迟多久?...

编程入门 行业动态 更新时间:2024-10-10 23:19:28

面试官:Activity finish 后,会立即回调 onDestory() 吗?<a href=https://www.elefans.com/category/jswz/34/1697866.html style=若不会,最长延迟多久?..."/>

面试官:Activity finish 后,会立即回调 onDestory() 吗?若不会,最长延迟多久?...

秉心说 TM | 作者

承香墨影 | 校对

| 原文

Hi,大家好,这里是承香墨影!

Activity 作为 Android 四大组件,生命周期自有 Framework 维护,我们只需要依据其生命周期回调方法,在不同的生命周期做不同的业务处理。

通常,我们认为在 finish() 之后,Activity 就会回调 onDestory() 并销毁,我们会在 Activity 的 onDestroy() 做资源的释放与解注册,避免造成内存和资源的泄露。

那么 finish() 之后,onDestory() 是立即被回调的吗?有没有可能存在阻塞的情况,导致 onDestory() 被延迟回调了?如果被阻塞,系统有兜底策略吗?这其中的原理是什么?

今天给大家推荐的来自 @秉心说 TM 的文章,从源码的角度分析 finish()onDestory() 的关系,希望对大家有所帮助。

一、没有及时回调的 onDestroy

交流群里碰到一个很有意思的问题,调用 Activity.finish() 之后 10s 才回调 onDestroy()

由此产生了一些不可控问题,例如在 onDestroy() 中释放资源不及时,赋值状态异常等等。我之前倒没有遇到过类似的问题,但是 AOSP 总是我们最好的老师。从 Activity.finish() 开始撸了一遍流程,找到了问题的答案。

在读源码之前,我们先来复现一下 10s onDestroy() 的场景。写一个最简单的 FirstActivity 跳转到 SecondActivity 的场景,并记录下各个生命周期和调用 finish() 的时间间隔。

class FirstActivity : BaseLifecycleActivity() {private val binding by lazy { ActivityFirstBinding.inflate(layoutInflater) }var startTime = 0Loverride 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() 距离 finish() :${System.currentTimeMillis() - startTime} ms")}override fun onStop() {super.onStop()Log.e("finish","onStop() 距离 finish() :${System.currentTimeMillis() - startTime} ms")}override fun onDestroy() {super.onDestroy()Log.e("finish","onDestroy() 距离 finish() :${System.currentTimeMillis() - startTime} ms")}
}

SecondActivity 是一个普通的没有进行任何操作的空白 Activity 。点击按钮跳转到 SecondActivity,打印日志如下:

FirstActivity: onPause,onPause() 距离 finish() :5 ms
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onStop,onStop() 距离 finish() :660 ms
FirstActivity: onDestroy,onDestroy() 距离 finish() :663 ms

可以看到正常情况下,FirstActivity 回调 onPause() 之后,SecondActivity 开始正常的生命周期流程,直到 onResume() 被回调,对用户可见时,FirstActivity 才会回调 onPause()onDestroy()。时间间隔也都在正常范围以内。

我们再模拟一个在 SecondActivity 启动时,进行大量动画的场景,源源不断的向主线程消息队列塞消息。修改一下 SecondActivity 的代码。

class SecondActivity : BaseLifecycleActivity() {private val binding by lazy { ActivitySecondBinding.inflate(layoutInflater) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(binding.root)postMessage()}private fun postMessage() {binding.secondBt.post {Thread.sleep(10)postMessage()}}
}

再来看一下日志:

FirstActivity: onPause, onPause() 距离 finish() :6 ms
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onStop, onStop() 距离 finish() :10033 ms
FirstActivity: onDestroy, onDestroy() 距离 finish() :10037 ms

FirstActivity 的 onPause() 没有受到影响。因为在 Activity 跳转过程中,目标 Activity 只有在前一个 Activity onPause() 之后才会开始正常的生命周期。而 onStop()onDestroy() 整整过了 10s 才回调。

对比以上两个场景,我们可以猜测,当 SecondActivity 的主线程过于繁忙,没有机会停下来喘口气的时候,会造成 FirstActivity 无法及时回调 onStop()onDestroy()。基于以上猜测,我们就可以从 AOSP 中来寻找答案了。

接下来就是大段的枯燥的源码分析了。带着问题去读 AOSP,可以让这个过程不是那么"枯燥",而且一定会有很多不一样的收获。

二、从 Activity.finish() 说起

以下源代码基于 Android 9.0 版本。

> Activity.javapublic void finish() {finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}

重载了带参数的 finish() 方法。参数是 DONT_FINISH_TASK_WITH_ACTIVITY,含义也很直白,不会销毁 Activity 所在的任务栈。

> Activity.javaprivate void finish(int finishTask) {// mParent 一般为 null,在 ActivityGroup 中会使用到if (mParent == null) {......try {// Binder 调用 AMS.finishActivity()if (ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)) {mFinished = true;}} catch (RemoteException e) {}} else {mParent.finishFromChild(this);}......
}

这里的 mParent 大多数情况下都是 null ,不需要考虑 else 分支的情况。一些大龄 Android 程序员可能会了解 ActivityGroup,在此种情况下 mParent 可能会不为 null。(因为我还年轻,所以没有使用过 ActivityGroup,就不过多解释了)其中 Binder 调用了 AMS.finishActivity() 方法。

> ActivityManagerService.javapublic final boolean finishActivity(IBinder token, int resultCode, Intent resultData,int finishTask) {......synchronized(this) {// token 持有 ActivityRecord 的弱引用ActivityRecord r = ActivityRecord.isInStackLocked(token);if (r == null) {return true;}......try {boolean res;final boolean finishWithRootActivity =finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;// finishTask 参数是 DONT_FINISH_TASK_WITH_ACTIVITY,进入 else 分支if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY|| (finishWithRootActivity && r == rootR)) {res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,finishWithRootActivity, "finish-activity");} else {// 调用 ActivityStack.requestFinishActivityLocked()res = tr.getStack().requestFinishActivityLocked(token, resultCode,resultData, "app-request", true);}return res;} finally {Binder.restoreCallingIdentity(origId);}}
}

注意方法参数中的 token 对象,Token 是 ActivityRecord 的静态内部类,它持有外部 ActivityRecord 的弱引用。继承自 IApplicationToken.Stub,是一个 Binder 对象。ActivityRecord 就是对当前 Activity 的具体描述,包含了 Activity 的所有信息。

传入的 finishTask() 方法的参数是 DONT_FINISH_TASK_WITH_ACTIVITY,所以接着会调用 ActivityStack.requestFinishActivityLocked() 方法。

> ActivityStack.javafinal boolean requestFinishActivityLocked(IBinder token, int resultCode,Intent resultData, String reason, boolean oomAdj) {ActivityRecord r = isInStackLocked(token);if (r == null) {return false;}finishActivityLocked(r, resultCode, resultData, reason, oomAdj);return true;
}final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,String reason, boolean oomAdj) {// PAUSE_IMMEDIATELY 为 true,在 ActivityStackSupervisor 中定义return finishActivityLocked(r, resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
}

最后调用的是一个重载的 finishActivityLocked() 方法。

> ActivityStack.java// 参数 pauseImmediately 是 false
final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,String reason, boolean oomAdj, boolean pauseImmediately) {if (r.finishing) { // 重复 finish 的情况return false;}mWindowManager.deferSurfaceLayout();try {// 标记 r.finishing = true,// 前面会做重复 finish 的检测就是依赖这个值r.makeFinishingLocked();final TaskRecord task = r.getTask();......// 暂停事件分发r.pauseKeyDispatchingLocked();adjustFocusedActivityStack(r, "finishActivity");// 处理 activity resultfinishActivityResultsLocked(r, resultCode, resultData);// mResumedActivity 就是当前 Activity,会进入此分支if (mResumedActivity == r) {......// Tell window manager to prepare for this one to be removed.r.setVisibility(false);if (mPausingActivity == null) {// 开始 pause mResumedActivitystartPausingLocked(false, false, null, pauseImmediately);}......} else if (!r.isState(PAUSING)) {// 不会进入此分支......} return false;} finally {mWindowManager.continueSurfaceLayout();}
}

调用 finish() 之后,肯定是要先 pause() 当前 Activity,没毛病。接着看 startPausingLocked() 方法。

> ActivityStack.javafinal boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,ActivityRecord resuming, boolean pauseImmediately) {......ActivityRecord prev = mResumedActivity;if (prev == null) {// 没有 onResume 的 Activity,不能执行 pauseif (resuming == null) {mStackSupervisor.resumeFocusedStackTopActivityLocked();}return false;}......mPausingActivity = prev;// 设置当前 Activity 状态为 PAUSINGprev.setState(PAUSING, "startPausingLocked");......if (prev.app != null && prev.app.thread != null) {try {......// 1. 通过 ClientLifecycleManager 分发生命周期事件// 最终会向 H 发送 EXECUTE_TRANSACTION 事件mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,PauseActivityItem.obtain(prev.finishing, userLeaving,prev.configChangeFlags, pauseImmediately));} catch (Exception e) {mPausingActivity = null;}} else {mPausingActivity = null;}......// mPausingActivity 在前面已经赋值,就是当前 Activityif (mPausingActivity != null) { ......if (pauseImmediately) { // 这里是 false,进入 else 分支completePauseLocked(false, resuming);return false;} else {// 2. 发送一个延时 500ms 的消息,等待 pause 流程一点时间// 最终会回调 activityPausedLocked() 方法schedulePauseTimeout(prev);return true;}} else {// 不会进入此分支}
}

这里面有两步重点操作。第一步是注释 1 处通过 ClientLifecycleManager 分发生命周期流程。第二步是发送一个延时 500ms 的消息,等待一下 onPause() 流程。但是如果第一步中在 500ms 内已经完成了流程,则会取消这个消息。所以这两步的最终逻辑其实是一致的。

这里就直接看第一步。

mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,PauseActivityItem.obtain(prev.finishing, userLeaving,prev.configChangeFlags, pauseImmediately));

ClientLifecycleManager 会向主线程的 Handler H 发送 EXECUTE_TRANSACTION 事件,调用 XXXActivityItem 的 execute()postExecute() 方法。execute() 方法中会 Binder 调用 ActivityThread 中对应的 handleXXXActivity() 方法。在这里就是 handlePauseActivity() 方法,其中会通过 Instrumentation.callActivityOnPause(r.activity) 方法回调 Activity.onPause()

> Instrumentation.javapublic void callActivityOnPause(Activity activity) {activity.performPause();
}

到这里,onPause() 方法就被执行了。但是流程没有结束,接着就该显示下一个 Activity 了。前面刚刚说过会调用 PauseActivityItem 的 execute()postExecute() 方法。execute() 方法回调了当前 Activity.onPause(),而 postExecute() 方法就是去寻找要显示的 Activity 。

> PauseActivityItem.javapublic void postExecute(ClientTransactionHandler client, IBinder token,PendingTransactionActions pendingActions) {try {ActivityManager.getService().activityPaused(token);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}
}

Binder 调用了 AMS.activityPaused() 方法。

> ActivityManagerService.javapublic final void activityPaused(IBinder token) {synchronized(this) {ActivityStack stack = ActivityRecord.getStackLocked(token);if (stack != null) {stack.activityPausedLocked(token, false);}}
}

调用了 ActivityStack.activityPausedLocked() 方法。

> ActivityStack.javafinal void activityPausedLocked(IBinder token, boolean timeout) {final ActivityRecord r = isInStackLocked(token);if (r != null) {// 看这里mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);if (mPausingActivity == r) {mService.mWindowManager.deferSurfaceLayout();try {// 看这里completePauseLocked(true /* resumeNext */, null /* resumingActivity */);} finally {mService.mWindowManager.continueSurfaceLayout();}return;} else {// 不会进入 else 分支}}
}

上面有这么一行代码 mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r),移除的就是之前延迟 500ms 的消息。接着看 completePauseLocked() 方法。

> ActivityStack.javaprivate void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {ActivityRecord prev = mPausingActivity;if (prev != null) {// 设置状态为 PAUSEDprev.setState(PAUSED, "completePausedLocked");if (prev.finishing) { // 1. finishing 为 true,进入此分支prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false,"completedPausedLocked");} else if (prev.app != null) {// 不会进入此分支} else {prev = null;}......}if (resumeNext) {// 当前获取焦点的 ActivityStackfinal ActivityStack topStack = mStackSupervisor.getFocusedStack();if (!topStack.shouldSleepOrShutDownActivities()) {// 2. 恢复要显示的 activitymStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);} else {checkReadyForSleep();ActivityRecord top = topStack.topRunningActivityLocked();if (top == null || (prev != null && top != prev)) {mStackSupervisor.resumeFocusedStackTopActivityLocked();}}}......
}

这里分了两步走。

注释 1 处判断了 finishing 状态,还记得 finishing 在何处被赋值为 true 的吗?

Activity.finish() -> AMS.finishActivity() -> ActivityStack.requestFinishActivityLocked() -> ActivityStack.finishActivityLocked() 方法中。所以接着调用的是 finishCurrentActivityLocked() 方法。注释 2 处就是来显示应该显示的 Activity ,就不再追进去细看了。

再跟到 finishCurrentActivityLocked() 方法中,看这名字,肯定是要 stop/destroy 没跑了。

> ActivityStack.java/** 把前面带过来的参数标出来* prev, FINISH_AFTER_VISIBLE, false,"completedPausedLocked"*/
final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj,String reason) {// 获取将要显示的栈顶 Activityfinal ActivityRecord next = mStackSupervisor.topRunningActivityLocked(true /* considerKeyguardState */);// 1. mode 是 FINISH_AFTER_VISIBLE,进入此分支
if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)&& next != null && !next.nowVisible) {if (!mStackSupervisor.mStoppingActivities.contains(r)) {// 加入到 mStackSupervisor.mStoppingActivitiesaddToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);}// 设置状态为 STOPPINGr.setState(STOPPING, "finishCurrentActivityLocked");return r;}......// 下面会执行 destroy,但是代码并不能执行到这里if (mode == FINISH_IMMEDIATELY|| (prevState == PAUSED&& (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))|| finishingActivityInNonFocusedStack|| prevState == STOPPING|| prevState == STOPPED|| prevState == ActivityState.INITIALIZING) {boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm:" + reason);......return activityRemoved ? null : r;}......
}

注释 1 处 mode 的值是 FINISH_AFTER_VISIBLE ,并且现在新的 Activity 还没有 onResume(),所以 r.visible || r.nowVisiblenext != null && !next.nowVisible 都是成立的,并不会进入后面的 destroy() 流程。虽然看到这还没得到想要的答案,但是起码是符合预期的。如果在这就直接 destroy() 了,延迟 10s 才 onDestroy() 的问题就无疾而终了。

对于这些暂时还不销毁的 Activity 都执行了 addToStopping(r, false, false) 方法。我们继续追进去。

> ActivityStack.javavoid addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed) {if (!mStackSupervisor.mStoppingActivities.contains(r)) {mStackSupervisor.mStoppingActivities.add(r);......}......// 省略的代码中,对 mStoppingActivities 的存储容量做了限制。超出限制可能会提前出发销毁流程
}

这些在等待销毁的 Activity 被保存在了 ActivityStackSupervisor 的 mStoppingActivities 集合中,它是一个 ArrayList<ActivityRecord>

整个 finish() 流程就到此为止了。前一个 Activity 被保存在了 ActivityStackSupervisor.mStoppingActivities 集合中,新的 Activity 被显示出来了。

问题似乎进入了困境,什么时候回调 onStop/onDestroy 呢?

其实这个才是根本问题。上面撸了一遍 finish() 并看不到本质,但是可以帮助我们形成一个完整的流程,这个一直是看 AOSP 最大的意义,帮助我们把零碎的上层知识形成一个完整的闭环。

三、是谁指挥onStop/onDestroy调用?

回到正题来,在 Activity 跳转过程中,为了保证流畅的用户体验,只要前一个 Activity 与用户不可交互,即 onPause() 被回调之后,下一个 Activity 就要开始自己的生命周期流程了。

所以 onStop/onDestroy 的调用时间是不确定的,甚至像文章开头的例子中,整整过了 10s 才回调。那么,到底是由谁来驱动 onStop/onDestroy 的执行呢?我们来看看下一个 Activity 的 onResume() 过程。

直接看 ActivityThread.handleResumeActivity() 方法,相信大家对生命周期的调用流程也很熟悉了。

> ActivityThread.javapublic void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {......// 回调 onResumefinal ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);......final Activity a = r.activity;......if (r.window == null && !a.mFinished && willBeVisible) {......if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;// 添加 decorView 到 WindowManagerwm.addView(decor, l);} else {a.onWindowAttributesChanged(l);}}} else if (!willBeVisible) {......}......// 主线程空闲时会执行 IdlerLooper.myQueue().addIdleHandler(new Idler());
}

handleResumeActivity() 方法是整个 UI 显示流程的重中之重,它首先会回调 Activity.onResume() , 然后将 DecorView 添加到 Window 上,其中又包括了创建 ViewRootImpl,创建 Choreographer,与 WMS 进行 Binder 通信,注册 vsync 信号,著名的 measure/draw/layout。这一块的源码真的很值得一读,不过不是这篇文章的重点,后面会单独来捋一捋。

在完成最终的界面绘制和显示之后,有这么一句代码 Looper.myQueue().addIdleHandler(new Idler())

IdleHandler 不知道大家是否熟悉,它提供了一种机制,当主线程消息队列空闲时,会执行 IdleHandler 的回调方法。至于怎么算 "空闲",我们可以看一下 MessageQueue.next() 方法。

> MessageQueue.javaMessage next() {......int pendingIdleHandlerCount = -1;int nextPollTimeoutMillis = 0;for (;;) {// 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。// 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。// 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。// 如果 nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)// 如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// 消息触发时间未到,设置下一次轮询的超时时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 得到 MessagemBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;msg.markInUse(); // 标记 FLAG_IN_USEreturn msg;}} else {nextPollTimeoutMillis = -1;}....../** 两个条件:* 1. pendingIdleHandlerCount = -1* 2. 此次取到的 mMessage 为空或者需要延迟处理*/if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// 没有 idle handler 需要运行,继续循环mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// 下一次 next 时,pendingIdleHandlerCount 又会被置为 -1,不会导致死循环for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {// 执行 Idlerkeep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// 将 pendingIdleHandlerCount 置零pendingIdleHandlerCount = 0;nextPollTimeoutMillis = 0;}
}

在正常的消息处理机制之后,额外对 IdleHandler 进行了处理。当本次取到的 Message 为空或者需要延时处理的时候,就会去执行 mIdleHandlers 数组中的 IdleHandler 对象。其中还有一些关于 pendingIdleHandlerCount 的额外逻辑来防止循环处理。

所以,不出意外的话,当新的 Activity 完成页面绘制并显示之后,主线程就可以停下歇一歇,来执行 IdleHandler 了。再回来 handleResumeActivity() 中来,Looper.myQueue().addIdleHandler(new Idler()) ,这里的 Idler 是 IdleHandler 的一个具体实现类。

> ActivityThread.javaprivate class Idler implements MessageQueue.IdleHandler {@Overridepublic final boolean queueIdle() {ActivityClientRecord a = mNewActivities;......}if (a != null) {mNewActivities = null;IActivityManager am = ActivityManager.getService();ActivityClientRecord prev;do {if (a.activity != null && !a.activity.mFinished) {try {// 调用 AMS.activityIdle()am.activityIdle(a.token, a.createdConfig, stopProfiling);a.createdConfig = null;} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}}prev = a;a = a.nextIdle;prev.nextIdle = null;} while (a != null);}......return false;}
}

Binder 调用了 AMS.activityIdle()

> ActivityManagerService.javapublic final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {final long origId = Binder.clearCallingIdentity();synchronized (this) {ActivityStack stack = ActivityRecord.getStackLocked(token);if (stack != null) {ActivityRecord r =mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */,false /* processPausingActivities */, config);......}}
}

调用了 ActivityStackSupervisor.activityIdleInternalLocked() 方法。

> ActivityStackSupervisor.javafinal ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,boolean processPausingActivities, Configuration config) {ArrayList<ActivityRecord> finishes = null;ArrayList<UserState> startingUsers = null;int NS = 0;int NF = 0;boolean booting = false;boolean activityRemoved = false;ActivityRecord r = ActivityRecord.forTokenLocked(token);......// 获取要 stop 的 Activityfinal ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,true /* remove */, processPausingActivities);NS = stops != null ? stops.size() : 0;if ((NF = mFinishingActivities.size()) > 0) {finishes = new ArrayList<>(mFinishingActivities);mFinishingActivities.clear();}// 该 stop 的 stopfor (int i = 0; i < NS; i++) {r = stops.get(i);final ActivityStack stack = r.getStack();if (stack != null) {if (r.finishing) {stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false,"activityIdleInternalLocked");} else {stack.stopActivityLocked(r);}}}// 该 destroy 的 destroyfor (int i = 0; i < NF; i++) {r = finishes.get(i);final ActivityStack stack = r.getStack();if (stack != null) {activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");}}......return r;
}

stopsfinishes 分别是要 stopdestroy 的两个 ActivityRecord 数组。stops 数组是通过 ActivityStackSuperVisor.processStoppingActivitiesLocked() 方法获取的,追进去看一下。

> ActivityStackSuperVisor.javafinal ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity,boolean remove, boolean processPausingActivities) {ArrayList<ActivityRecord> stops = null;final boolean nowVisible = allResumedActivitiesVisible();// 遍历 mStoppingActivitiesfor (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {ActivityRecord s = mStoppingActivities.get(activityNdx);......}return stops;
}

中间的详细处理逻辑就不看了,我们只需要关注这里遍历的是 ActivityStackSuperVisor 中的 mStoppingActivities 集合 。在前面分析 finish() 流程到最后的 addToStopping() 方法时提到过,

这些在等待销毁的 Activity 被保存在了 ActivityStackSupervisor 的 mStoppingActivities 集合中,它是一个 ArrayList<ActivityRecord>

看到这里,终于打通了流程。再回头想一下文章开头的例子,由于人为的在 SecondActivity 不间断的向主线程塞消息,导致 Idler 迟迟无法被执行,onStop/onDestroy 也就不会被回调。

四、谁让 onStop/onDestroy 延迟了 10s ?

对,不会被回调。可实际情况是这样吗?

并不是,明明是过了 10s 被回调。这就说明了即使主线程迟迟没有机会执行 Idler,系统仍然提供了兜底机制,防止已经不需要的 Activity 长时间无法被回收,从而造成内存泄漏等问题。从实际现象就可以猜测到,这个兜底机制就是 onResume() 之后 10s 主动去进行释放操作。

再回到之前显示待跳转 Activity 的 ActivityStackSuperVisor.resumeFocusedStackTopActivityLocked() 方法。我这里就不带着大家追进去了,直接给出调用链。

ASS.resumeFocusedStackTopActivityLocked() -> ActivityStack.resumeTopActivityUncheckedLocked() -> ActivityStack.resumeTopActivityInnerLocked() -> ActivityRecordpleteResumeLocked() -> ASS.scheduleIdleTimeoutLocked()

> ActivityStackSuperVisor.javavoid scheduleIdleTimeoutLocked(ActivityRecord next) {Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next);mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);
}

IDLE_TIMEOUT 的值是 10,这里延迟 10s 发送了一个消息。这个消息是在 ActivityStackSupervisorHandler 中处理的。

private final class ActivityStackSupervisorHandler extends Handler {
......
case IDLE_TIMEOUT_MSG: {activityIdleInternal((ActivityRecord) msg.obj, true /* processPausingActivities */);} break;
......
}void activityIdleInternal(ActivityRecord r, boolean processPausingActivities) {synchronized (mService) {activityIdleInternalLocked(r != null ? r.appToken : null, true /* fromTimeout */,processPausingActivities, null);}
}

忘记 activityIdleInternalLocked() 方法的话可以 ctrl+F 向上搜索一下。如果 10s 内主线程执行了 Idler 的话,就会移除这个消息。

到这里,所有的问题就全部理清了。

五、最后

Activity 的 onStop/onDestroy 是依赖 IdleHandler 来回调的,正常情况下当主线程空闲时会调用。但是由于某些特殊场景下的问题,导致主线程迟迟无法空闲,onStop/onDestroy 也会迟迟得不到调用。但这并不意味着 Activity 永远得不到回收,系统提供了一个兜底机制,当 onResume() 回调 10s 之后,如果仍然没有得到调用,会主动触发。

虽然有兜底机制,但无论如何这肯定不是我们想看到的。如果我们项目中的 onStop/onDestroy 延迟了 10s 调用,该如何排查问题呢?可以利用 Looper.getMainLooper().setMessageLogging() 方法,打印出主线程消息队列中的消息。每处理一条消息,都会打印如下内容:

logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

另外,由于 onStop/onDestroy 调用时机的不确定性,在做资源释放等操作的时候,一定要考虑好,以避免产生资源没有及时释放的情况。

推荐的鸿洋的 wanandroid,其中的「每日一问」系列就收录了这个问题,大家有空的时候可以浏览一下,有些问题还是很有启发的。

  • wanandroid
    /

-- End --

本文对你有帮助吗?留言、转发、点好看是最大的支持,谢谢!

推荐阅读:

点亮技能树 - 从害怕到玩转 Android 代码混淆!

Android 视图系统的设计与实现 | 通俗易懂

面试官:遇到过死锁问题吗?怎么发生的?如何解决呢?

更多推荐

面试官:Activity finish 后,会立即回调 onDestory() 吗?若不会,最长延迟多久?...

本文发布于:2024-02-25 18:08:31,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1699852.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:若不   回调   面试官   最长   多久

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!