答上这道题,就能进阿里:为什么 Activity.finish() 之后 10s 才 onDestroy ?

2024-02-02 17:38

本文主要是介绍答上这道题,就能进阿里:为什么 Activity.finish() 之后 10s 才 onDestroy ?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.没有及时回调的 onStop/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() 之后才会开始正常的生命周期。而 onStoponDestroy() 整整过了 10s 才回调。

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

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

2.从 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 对象,在上一篇文章 为什么不能使用 Application Context 显示 Dialog? 中详细介绍过,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 我在之前的一篇文章 从源码看 Activity 生命周期(上篇) 做过详细介绍。它会向主线程的 Handler H 发送 EXECUTE_TRANSACTION 事件,调用 XXXActivityItemexecute()postExecute() 方法。execute() 方法中会 Binder 调用 ActivityThread 中对应的 handleXXXActivity() 方法。在这里就是 handlePauseActivity() 方法,其中会通过 Instrumentation.callActivityOnPause(r.activity) 方法回调 Activity.onPause()

> Instrumentation.javapublic void callActivityOnPause(Activity activity) {activity.performPause();
}
复制代码

到这里,onPause() 方法就被执行了。但是流程没有结束,接着就该显示下一个 Activity 了。前面刚刚说过会调用 PauseActivityItemexecute()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 被保存在了 ActivityStackSupervisormStoppingActivities 集合中,它是一个 ArrayList<ActivityRecord>

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

问题似乎进入了困境,什么时候回调 onStop/onDestroy 呢?其实这个才是根本问题。上面撸了一遍 finish() 并看不到本质,但是可以帮助我们形成一个完整的流程,这个一直是看 AOSP 最大的意义,帮助我们把零碎的上层知识形成一个完整的闭环。

3.是谁指挥着 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()) ,这里的 IdlerIdleHandler 的一个具体实现类。

> 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 分别是要 stop 和 destroy 的两个 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 被保存在了 ActivityStackSupervisormStoppingActivities 集合中,它是一个 ArrayList<ActivityRecord>

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

4.谁让 onStop/onDestroy 延迟了 10s ?

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

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

ASS.resumeFocusedStackTopActivityLocked() -> ActivityStack.resumeTopActivityUncheckedLocked() -> ActivityStack.resumeTopActivityInnerLocked() -> ActivityRecord.completeResumeLocked() -> 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 的话,就会移除这个消息。

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

5.最后

说一些题外话,Android 面试进阶指南 是我在小专栏维护的一个付费专栏,且已经有部分付费用户。本文是第九篇文章了,为了维护付费用户的权益,没有办法把所有文章都同步过来。如果你对这个专栏感兴趣,不妨 戳进来 看一看。

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 调用时机的不确定性,在做资源释放等操作的时候,一定要考虑好,以避免产生资源没有及时释放的情况。

更多大厂Android面试真题解析,可以点击这里获取!

这篇关于答上这道题,就能进阿里:为什么 Activity.finish() 之后 10s 才 onDestroy ?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/671474

相关文章

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

阿里云服务器ces

允许公网通过 HTTP、HTTPS 等服务访问实例 https://help.aliyun.com/document_detail/25475.html?spm=5176.2020520101.0.0.3ca96b0b3KGTPq#allowHttp

LLM系列 | 38:解读阿里开源语音多模态模型Qwen2-Audio

引言 模型概述 模型架构 训练方法 性能评估 实战演示 总结 引言 金山挂月窥禅径,沙鸟听经恋法门。 小伙伴们好,我是微信公众号《小窗幽记机器学习》的小编:卖铁观音的小男孩,今天这篇小作文主要是介绍阿里巴巴的语音多模态大模型Qwen2-Audio。近日,阿里巴巴Qwen团队发布了最新的大规模音频-语言模型Qwen2-Audio及其技术报告。该模型在音频理解和多模态交互

超越IP-Adapter!阿里提出UniPortrait,可通过文本定制生成高保真的单人或多人图像。

阿里提出UniPortrait,能根据用户提供的文本描述,快速生成既忠实于原图又能灵活调整的个性化人像,用户甚至可以通过简单的句子来描述多个不同的人物,而不需要一一指定每个人的位置。这种设计大大简化了用户的操作,提升了个性化生成的效率和效果。 UniPortrait以统一的方式定制单 ID 和多 ID 图像,提供高保真身份保存、广泛的面部可编辑性、自由格式的文本描述,并且无需预先确定的布局。

eclipse中,更新JDK之后,启动Tomcat报错:

更新到这个版本之后,启动Tomcat报错: 四月 25, 2016 10:13:20 上午 org.apache.catalina.core.AprLifecycleListener lifecycleEvent INFO: The APR based Apache Tomcat Native library which allows optimal performance in prod

node.js实现阿里云短信发送

效果图 实现 一、准备工作 1、官网直达网址: 阿里云 - 短信服务 2、按照首页提示依次完成相应资质认证和短信模板审核; 3、获取你的accessKeySecret和accessKeyId; 方法如下: 获取AccessKey-阿里云帮助中心 4、获取SignName(签名名称)和 TemplateCode(模板code); 二、代码实现 1、项目结构 【/c

element-ui打包之后图标不显示,woff、ttf加载404

1、bug 起因 昨天在 vue 项目中编写 element-ui 的树形结构的表格,发现项目中无法生效,定位问题之后发现项目使用的 element-ui 的版本是 2.4.11 。看了官方最新版本是 2.15.14,然后得知 2.4.11 版本是不支持表格树形结构的。于是决定升级 element-ui 的版本,方便后续的开发。 升级之后本地简单的过了一遍系统功能,并没有发现有什么不妥,于

一个瑞典游戏工作室决定离开索尼,之前和之后都发生了什么?

我们在前两篇中探究了国家政策、硬件基础与黑客文化如何让瑞典成为了游戏热土,而它充满地域特色的开发者社区与教育体系的构建,又是如何聚拢了游戏人才,让体系持续生长扩张。 除了大学、科技园和开发者之家外,我们此行从斯德哥尔摩到舍夫德到马尔默,还采访了三家知名工作室的创始人。它们一家产出如今罕见的双人合作游戏,还有一位特立独行的作者型开发者屡屡占据头条;一家贡献了现象级网红作品,当前在朝“正经向”大

JS_阿里云oss视频上传后,如何获取视频封面

当您需要获取视频封面、提取视频关键帧图像进行视频编辑,或者提取视频中特定场景帧图像用于视频监控等时,可以将视频上传至OSS存储空间,然后通过本文所示方法进行视频截帧。 使用示例 本文示例使用的Bucket为杭州地域名为oss-console-img-demo-cn-hangzhou的Bucket,视频外网访问地址为: https://oss-console-img-demo-cn-hangzho

之后是缠缠绵绵的四年恋爱

他和她初次相见是在操场上。她忽然来例假,染红了白裙子,却浑然不觉,还在和同学说笑。他看见后脸红了,脱下自己的上衣让她围在腰间。那一刻,是她一辈子也难忘的。     之后是缠缠绵绵的四年恋爱,她试图帮他,而他不肯;男人哪会用女孩子帮忙?     毕业时,他们本来免不了天各一方,但她死心塌地地跟着他走。家里人反对,几乎与她反目,她却认定这男人是她想要的。     她