面试官:为什么 Activity.finish() 之后 10s 才 onDestroy ?

Android面试核心:从基础原理到架构设计的深度解析与实战指南 在移动开发领域,Android系统架构与性能优化是开发者必须掌握的核心技术栈。理解其底层原理,如组件生命周期管理、消息循环机制(Handler/Looper)以及内存管理模型,是构建稳定、高效应用的基础。这些原理直接决定了应用的技术价值,体现在流畅的用户体验、低内存占用以及良好的可维护性上。在实际应用场景中,无论是处理复杂的异步任务、优化列表滚动性能,还是设计可测试的软件架构,都离不开对基础机制的深刻理解。本文聚焦于Android面试中的高频考点,结合**内存泄漏**排查与**Kotlin协程**等现代技术 阅读详情

没有及时回调的 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 = 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() 距离 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.java

public void finish() {
    finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}

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

> Activity.java

private 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.java

public 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.java

final 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 result
        finishActivityResultsLocked(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 mResumedActivity
                startPausingLocked(false, false, null, pauseImmediately);
            }
            ......
        } else if (!r.isState(PAUSING)) {
            // 不会进入此分支
            ......
        } 
        return false;
    } finally {
        mWindowManager.continueSurfaceLayout();
    }
}

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

> ActivityStack.java

    final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
            ActivityRecord resuming, boolean pauseImmediately) {
        ......
        ActivityRecord prev = mResumedActivity;

        if (prev == null) {
            // 没有 onResume 的 Activity,不能执行 pause
            if (resuming == null) {
                mStackSupervisor.resumeFocusedStackTopActivityLocked();
            }
            return false;
        }
        ......

        mPausingActivity = prev;
		// 设置当前 Activity 状态为 PAUSING
        prev.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 在前面已经赋值,就是当前 Activity
        if (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 事件,调用 XXXActivityItem 的 execute() 和 postExecute() 方法。execute() 方法中会 Binder 调用 ActivityThread 中对应的 handleXXXActivity() 方法。在这里就是 handlePauseActivity() 方法,其中会通过

Instrumentation.callActivityOnPause(r.activity) 方法回调 Activity.onPause() 。
> Instrumentation.java

public void callActivityOnPause(Activity activity) {
    activity.performPause();
}

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

> PauseActivityItem.java

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

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

> ActivityManagerService.java

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

调用了 ActivityStack.activityPausedLocked() 方法。

> ActivityStack.java

final 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.java

private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
    ActivityRecord prev = mPausingActivity;

    if (prev != null) {
		// 设置状态为 PAUSED
        prev.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) {
		// 当前获取焦点的 ActivityStack
        final ActivityStack topStack = mStackSupervisor.getFocusedStack();
        if (!topStack.shouldSleepOrShutDownActivities()) {
			// 2. 恢复要显示的 activity
            mStackSupervisor.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) {
     
    // 获取将要显示的栈顶 Activity
    final 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.mStoppingActivities
            addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
        }
		// 设置状态为 STOPPING
        r.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.nowVisible 和 next != null && !next.nowVisible 都是成立的,并不会进入后面的 destroy 流程。虽然看到这还没得到想要的答案,但是起码是符合预期的。如果在这就直接 destroy 了,延迟 10s 才 onDestroy 的问题就无疾而终了。
对于这些暂时还不销毁的 Activity 都执行了 addToStopping(r, false, false) 方法。我们继续追进去。

> ActivityStack.java

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

这些在等待销毁的 Activity 被保存在了 ActivityStackSupervisor 的 mStoppingActivities 集合中,它是一个 ArrayList 。
整个 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.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    ......
    // 回调 onResume
    final 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 到 WindowManager
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    } else if (!willBeVisible) {
        ......
    }
    ......

    // 主线程空闲时会执行 Idler
    Looper.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.java

Message 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 {
                    // 得到 Message
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse(); // 标记 FLAG_IN_USE
                    return 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 handler

            boolean keep = false;
            try {
                // 执行 Idler
                keep = 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.java

private class Idler implements MessageQueue.IdleHandler {
    @Override
    public 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.java

public 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.java

final 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 的 Activity
    final 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 的 stop
    for (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 的 destroy
    for (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;
}

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

> ActivityStackSuperVisor.java

final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity,
        boolean remove, boolean processPausingActivities) {
    ArrayList<ActivityRecord> stops = null;

    final boolean nowVisible = allResumedActivitiesVisible();
    // 遍历 mStoppingActivities
    for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {
        ActivityRecord s = mStoppingActivities.get(activityNdx);
        ......
    }
    return stops;
}

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

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

看到这里,终于打通了流程。再回头想一下文章开头的例子,由于人为的在 SecondActivity 不间断的向主线程塞消息,导致 Idler 迟迟无法被执行,onStop/onDestroy 也就不会被回调。
谁让 onStop/onDestroy 延迟了 10s ?
对,不会被回调。 可实际情况是这样吗?并不是,明明是过了 10s 被回调。这就说明了即使主线程迟迟没有机会执行 Idler,系统仍然提供了兜底机制,防止已经不需要的 Activity 长时间无法被回收,从而造成内存泄漏等问题。从实际现象就可以猜测到,这个兜底机制就是 onResume 之后 10s 主动去进行释放操作。
再回到之前显示待跳转 Activity 的 ActivityStackSuperVisor.resumeFocusedStackTopActivityLocked() 方法。我这里就不带着大家追进去了,直接给出调用链。

ASS.resumeFocusedStackTopActivityLocked() -> ActivityStack.resumeTopActivityUncheckedLocked() -> ActivityStack.resumeTopActivityInnerLocked() -> ActivityRecord.completeResumeLocked() -> ASS.scheduleIdleTimeoutLocked()
> ActivityStackSuperVisor.java

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

拼多多Android面试题及参考答案 Android 系统采用分层架构设计,从底层到上层依次为Linux 内核层硬件抽象层(HAL)系统运行时库层应用框架层应用层,共五层。位置:Linux 内核层与系统运行时库层之间,是“硬件与系统的隔离层”。核心作用:屏蔽硬件差异,为上层提供统一的硬件访问接口,解决 Android 开源与硬件厂商闭源的矛盾,具体功能如下:硬件能力抽象:将硬件的具体实现(如不同厂商的摄像头芯片、音频芯片)封装为标准化接口(如Camera HALAudio HAL。 阅读详情

相关推荐

程序员未来真的会被淘汰吗?

实际上的计算机:web,图形学Q,Linux系统开发,一系列嵌入式开发,数据库,高性能服务器,中间件开发,三维建模,网络安全,通信协议°,加密算法,服务器集群维护,文件存储引擎Q,编译器,工业软件,客户端,计算机视觉,自然语言处理,算法调优,游戏引擎Q。实际上的计算机:web,图形学Q,Linux系统开发,一系列嵌入式开发,数据库,高性能服务器,中间件开发,三维建模,网络安全,通信协议°,加密算法,服务器集群维护,文件存储引擎Q,编译器,工业软件,客户端,计算机视觉,自然语言处理,算法调优,游戏引擎Q。

——心比天高,仗剑走天涯,保持热爱,奔赴向梦想!低调,谦虚,自律,反思,成长,还算是比较正能量的博主,公益免费传播……内心特别想在AI界做出一些可以推进历史进程影响力的东西(兴趣使然,有点小情怀,也有点使命感呀)… 1260

为什么 Activity.finish() 之后 10s 才 onDestroy ?我蒙蔽了

作者:秉心说TMhttps://juejin.cn/post/6898588053451833351本文由作者授权发布。目录没有及时回调的 onStop/onDestroy从 Activ...

weixin_38754349的博客 747

基于 Python 的 LIF 模型:探索神经元同步与小世界网络

首先,我们定义了一个LIFNeuronself.soma.L = self.soma.diam = 12.6157 # 固定神经元形状self.cm = cm # 电容self.V_rest = V_rest # 静息电位self.V = V_rest # 初始化膜电位在初始化过程中,每个神经元被赋予初始的静息电位,并且其膜电位会在后续的模拟过程中动态变化。

qq_36224726的博客 1424

finish()和onDestroy()的关系

问题背景 早上老铁跟我说,他们项目接入讯飞语音的时候重复进Activity导致了空指针异常,怀疑是第二次进入Activity的时候调用了onCreate()之后,上一次该Activity的onDestroy才开始调用,然后操作相同变量导致了这个crash;换句话来说就是调用了finish()方法之后延迟了很久(4-5s)才调用onDestroy(),然后5秒内再次打开该Activity导致崩溃,说...

Must_Do的博客 5203

LM1875官方电路图元件作用解释说明以及电路调校

R1是为C1提供充放电回路用的电阻,这是老外工作严谨的地方,其实接好电位器之后直流通路就已经存在了,这个加上只能防止最恶劣情况时也能把C1的电能放掉,尽管只有一点点 C1是输入电容,主要作用是隔离前后级的直流信号,但是这个电容对音质的影响很大,可以换用不同的品牌和容量来部分修改音质和音色 R2提供LM1875正输入端的直流电平控制点,接地就表示在理想情况下Lm1875的输出直流电平是0V;还有一个作用就是确定放大器的交流输入阻抗为电阻值。 R3、R4是反馈电阻,这个电路的放大倍数为R4/R3+1=21倍。.

m0_50862404的博客 1万+

Cadence Allegro PCB文件对比实战:3分钟搞定两份设计差异(附颜色标注技巧)

本文详细介绍了如何高效使用Cadence Allegro的Design Compare功能进行PCB文件差异对比。通过实战工作流,可在3分钟内精准定位并理解两份设计在布局、布线、元件等方面的关键差异,并分享了自定义颜色标注技巧,帮助工程师快速聚焦重要修改,提升设计评审与团队协作效率。

weixin_29248653的博客 379

android退出时finish()调用问题

若是只有一个单独的Activity的工程,例如Activity A,调用android.app.Activity.finish()的话,并不会关闭该工程,只是使之不处于前台,而被置于后台。参考网上的方法,可以调用   android.os.Process.killProcess(android.os.Process.myPid());这种方法确实的是可以关闭该工程进程的,您可以到应用程序管理里头看

古语云 2528

BERT + BiLSTM-CRF 处理流程

BERT是一个基于 Transformer 的预训练语言模型。与传统的语言模型不同,BERT 是双向的,即它同时利用上下文信息进行词表示学习。BERT 在大规模语料库上进行预训练,可以捕捉到深层次的语言知识和上下文关系,生成非常丰富的词向量表示。在序列标注任务中,BERT 被用作输入文本的编码器,将每个词映射为上下文相关的词嵌入(embeddings)。

AI Agent 首席体验官 2258

引用activity_面试官:为什么 Activity.finish() 之后 10s 才 onDestroy ?

目录没有及时回调的 onStop/onDestroy从 Activity.finish() 说起是谁指挥着 onStop/onDestroy 的调用?谁让 onStop/onDestroy 延迟了 10s ?没有及时回调的 onStop/onDestroy交流群里碰到一个很有意思的问题,调用 Activity.finish() 之后 10s 才回调 onDestroy() 。由此产生了一些不可控问...

weixin_33443597的博客 247

8台 × 8卡 910B NPU之顶尖大模型选型分析

本文对比了2026年主流大语言模型在昇腾910B NPU集群上的部署方案。重点评估了GLM5、MiniMax M2.5、Kimi K2.5、DeepSeek V3.2和Qwen 3.5旗舰版等候选模型,从部署需求(显存占用)、能力表现(基准测试、多模态支持)和NPU生态适配度三个维度进行分析。结果显示DeepSeek V3.2综合表现最优:支持BF16/W8A8/INT4量化,在4TB显存环境下可实现2000+ TPS,首token延迟<100ms,且具备官方NPU支持。备选方案Qwen 3.5旗舰版

FeelTouch Labs 757

SAP S/4HANA 数据 API 化与 MCP 集成全流程实战指南

通过 RAP 发布 OData、利用 Communication Arrangement 打通外网、再在 MCP-server 里封装函数签名,你就能把 S/4HANA 的核心数据安全、标准化地暴露给任意支持 MCP 的大模型。这条链路的每一个环节都由 SAP 与 MCP 社区提供了现成框架,既兼顾了企业合规,也让 LLM 落地更为轻量。期待你在项目里把这些拼图真正串联起来,让业务数据与生成式 AI 碰撞出高价值的火花。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 977

activity ontouchevent没有被调用_面试官:为什么 Activity.finish() 之后 10s 才 onDestroy ?...

本文作者作者:秉心说TM链接:https://juejin.cn/post/6898588053451833351本文由作者授权发布。目录没有及时回调的 onStop/onDestroy从 Activity.finish() 说起是谁指挥着 onStop/onDestroy 的调用?谁让 onStop/onDestroy 延迟了 10s ?1没有及时回调的 onStop/onDestro...

weixin_36269652的博客 274

RK3588部署YOLO模型 包含模型量化+npu轻量化+ubuntu+RGA图像加速和多线程模型加速 包源码

本专栏为深度学习部署全栈学习体系,包含68篇由浅入深的教程,专为零基础学习者设计。内容涵盖TensorRT与RK3588平台部署技术,包含Jetson系列和RK系列硬件部署实战,采用"理论+源码+实战"三位一体的教学模式。学习路径从基础环境搭建到模型优化、容器化部署、多硬件平台适配,最终掌握工业级AI部署能力。特色包括C++基础教学、Docker容器化、INT8量化、多线程优化等核心技术,并附带YOLOv5、人脸检测、车牌识别等多个实战项目源码。适合算法工程师转部署方向、嵌入式开发者及对

FJN110的博客 3731

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

秉心说 TM | 作者承香墨影 | 校对https://juejin.cn/post/6898588053451833351 | 原文Hi,大家好,这里是承香墨影!Activity 作为 ...

承香墨影的博客 4083

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

面试官:为什么 Activity

所以这两步的最终逻辑其实是一致的。可以看到正常情况下,FirstActivity 回调 onPause 之后,SecondActivity 开始正常的生命周期流程,直到 onResume 被回调,对用户可见时,FirstActivity 才会回调 onPause 和 onDestroy。回到正题来,在 Activity 跳转过程中,为了保证流畅的用户体验,只要前一个 Activity 与用户不可交互,即 onPause() 被回调之后,下一个 Activity 就要开始自己的生命周期流程了。

2401_86964146的博客 517

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

面试官:为什么 Activity,ios大厂面试题

这一块的源码真的很值得一读,不过不是这篇文章的重点,后面会单独来捋一捋。可实际情况是这样吗?一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

X7gUWZnYP的博客 972

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Android面试进阶:从生命周期到架构设计,掌握核心原理与工程实践

在Android开发领域,理解核心组件原理与系统机制是构建稳定、高性能应用的基础。Activity、Fragment等组件的生命周期管理,不仅是面试高频考点,更是处理页面状态、数据恢复与内存管理的工程实践核心。其背后涉及系统对应用进程与任务栈的调度原理,深刻理解这些机制,能有效避免内存泄漏与状态不一致问题,提升应用健壮性。随着Jetpack架构组件的普及,ViewModel、LiveData等现代技术方案,为数据驱动UI和生命周期感知编程提供了标准化支持,推动了MVVM等架构模式在工程中的落地。这些技术价值

weixin_33713350的博客 476

小红书Android秋招笔试复盘:从Handler到AMS的完整考点解析

Android开发岗位的笔试考察,往往不是简单背诵八股文,而是对核心技术原理的深度理解。从Handler消息机制到AMS/ATMS的Activity启动流程,从Binder IPC到ANR定位与解决,这些知识点构成了大厂笔试的高频考点。理解其底层原理,不仅能提升代码健壮性,也是解决线上问题的关键。在秋招场景下,针对小红书等互联网公司的Android笔试,系统梳理这些知识体系,配合编程题训练,能有效提高通过率。本文以一场真实笔试为例,完整复盘了题型结构、知识点考察边界、答题策略与复盘方法,为准备Android

weixin_34391445的博客 317

Android中调用finish()后不能立即执行onDestroy()的BUG

 (2013-12-02 15:55:58) 转载▼ 标签: ondistory it 分类: android应用技巧 在目前项目中(android2.0),发现一个严重BUG。当打开了A,B两个页面的时候,此时当前页面为B,如果在B中调用finish()后,虽然已经回到了A,但B却并没有执行onDestroy()方法,只有

yuanyuan_186的专栏 2674

Android finish()自动回调时机,Activity资源释放逻辑放在onDestroy中一定合适吗

首先看Google对finish()的描述, Call this when your activity is done and should be closed. The ActivityResult is propagated back to whoever launched you via onActivityResult(). 当activity完...

多学习,多读书 3653

android 线程手动结束,在Activity中创建的线程当activity执行结束,执行activity的finish()方法后,有没有必要手动消毁在此...

在Activity中创建的线程当activity执行结束,执行activity的finish()方法后,有没有必要手动消毁在此0在Activity中创建的线程当activity执行结束,执行activity的finish()方法后,有没有必要手动消毁在此activity中创建的子线程?不消毁会不会占用内存,如何是消毁的话,在哪里比较好,在run后,还是在activity的ondestory()方法...

weixin_39837352的博客 395

Activity中OnDestory()回调慢的原因及解决办法

前言:近日在测试时,发现bug,聊天页面的数据,有时候有,有时候没有。经过查找,原来是这个页面销毁后,很长时间才回调OnDestroy()方法!如果在回调OnDestroy()方法之前,又重新点击这个页面,就导致这个页面没有数据。 导致原因: 在OnDestroy()中进行了数据存储等操作! 原因分析: 先附上源码中Activity的OnDestroy()的官方解释: * P

Heijinbaitu的博客 6003

Activity 的 finish 过程

本文以 Android 9.0 源码为基础,分析 `Activity` 的 finish 过程。要进行分析,必须先确定好场景,不然就没有了方向。我们分析的场景是这样的: `MainActivity` 通过 `startActivityForResult` 方法启动了 `SecondActivity`,然后 `SecondActivity` 调用 `finish()` 方法关闭自己。

willwaywang6 5540
下一篇: Activity的初级,中级,高级问法,你都掌握了吗?
AndroidYingZi
博客等级 码龄6年 6粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值