为什么 Activity.finish() 之后 10s 才 onDestroy ?

本文围绕Android中Activity调用finish()后onStop/onDestroy延迟10s回调的问题展开。通过复现场景、分析AOSP源码,发现正常依赖IdleHandler回调,主线程繁忙会导致延迟。系统有10s兜底机制,还给出排查问题方法及资源释放注意事项。

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

对比以上两个场景,我们可以猜测,当 SecondActivity 的主线程过于繁忙,没有机会停下来喘口气的时候,会造成 FirstActivity 无法及时回调 onStoponDestroy 。基于以上猜测,我们就可以从 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 事件,调用 XXXActivityItemexecute()postExecute() 方法。execute() 方法中会 Binder 调用 ActivityThread 中对应的 handleXXXActivity() 方法。在这里就是 handlePauseActivity() 方法,其中会通过 Instrumentation.callActivityOnPause(r.activity) 方法回调 Activity.onPause()

 

> Instrumentation.java

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

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

> 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;
}

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

看到这里,终于打通了流程。再回头想一下文章开头的例子,由于人为的在 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 调用时机的不确定性,在做资源释放等操作的时候,一定要考虑好,以避免产生资源没有及时释放的情况。



作者:飞翔的企鹅666
链接:https://www.jianshu.com/p/da6d15bbbca7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

package com.weishitech.sjykqyaowneng.ui.webview.activity import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.text.TextUtils import android.view.KeyEvent import android.view.View import android.webkit.WebChromeClient import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Toast import com.gyf.immersionbar.ImmersionBar import com.weishitech.sjykqyaowneng.AppAdEnum import com.weishitech.sjykqyaowneng.R import com.weishitech.sjykqyaowneng.base.activity.BaseBindActivity import com.weishitech.sjykqyaowneng.base.inter.insert.IAdInsertView import com.weishitech.sjykqyaowneng.base.util.ViewUtil import com.weishitech.sjykqyaowneng.bean.WindowInfo import com.weishitech.sjykqyaowneng.databinding.ActivityWebviewBinding import com.weishitech.sjykqyaowneng.fragment.MenuUrlUtils import com.weishitech.sjykqyaowneng.ui.home.activity.WindowManagerActivity import com.weishitech.sjykqyaowneng.utils.WebViewWindowManager /** * @author ZhaoWanCheng * @date 2025/12/8 * @desc WebView加载页面 */ class WebViewActivity : BaseBindActivity<ActivityWebviewBinding>(), View.OnClickListener, IAdInsertView { companion object { fun startActivity(context: Context, url: String) { val intent = Intent(context, WebViewActivity::class.java) intent.putExtra("url", url) context.startActivity(intent) } } private var mUrl: String? = null override fun init() { ImmersionBar.with(this) .titleBar(mBinding.view) .keyboardEnable(true) .statusBarDarkFont(true) .init() initInsert(AppAdEnum.AD_INTERSTITIAL_WEBVIEW) mBinding.ivBack.setOnClickListener(this) mBinding.ivStar.setOnClickListener(this) mBinding.tvNumber.setOnClickListener(this) mBinding.ivFinish.setOnClickListener(this) initWebViewSettings() setWebViewListeners() mBinding.webview.loadUrl(getUrl(intent)) mBinding.llSearch.setOnClickListener { var intent = Intent(this, SearchActivity::class.java) intent.putExtra("keywords", ViewUtil.getText(mBinding.tvTitle)) startActivity(intent) } } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) mBinding.webview.loadUrl(getUrl(intent!!)) } private fun getUrl(intent: Intent): String { mUrl = intent.getStringExtra("url") if (TextUtils.isEmpty(mUrl)) { return "https://www.baidu.com" } if (mUrl!!.startsWith("http://") || mUrl!!.startsWith("https://") || mUrl!!.startsWith("www.") ) { return mUrl!! } mUrl = "https://www.baidu.com/s?wd=" + Uri.encode(mUrl) return mUrl!! } // ====================== WebView 核心配置 ====================== /** * 初始化WebView设置(启用JS、缓存等) */ private fun initWebViewSettings() { val settings: WebSettings = mBinding.webview.settings // 启用JavaScript(必备) settings.javaScriptEnabled = true // 启用DOM存储(适配复杂网页) settings.domStorageEnabled = true // 允许混合内容(HTTP/HTTPS混合页面) settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW // 缓存模式(根据需求调整) settings.cacheMode = WebSettings.LOAD_DEFAULT // 自适应屏幕 settings.useWideViewPort = true settings.loadWithOverviewMode = true settings.loadWithOverviewMode = true // 缩放至屏幕的大小 } /** * 设置WebView监听:进度+页面加载状态 */ private fun setWebViewListeners() { // 1. WebChromeClient:监听加载进度 mBinding.webview.webChromeClient = object : WebChromeClient() { override fun onReceivedTitle(view: WebView?, title: String?) { if (mBinding == null || mBinding.progressBar == null) { return } super.onReceivedTitle(view, title) if (!TextUtils.isEmpty(title)) { mBinding.tvTitle.text = title } MenuUrlUtils.addUrl(title, mUrl) } override fun onProgressChanged(view: WebView?, newProgress: Int) { super.onProgressChanged(view, newProgress) if (mBinding == null || mBinding.progressBar == null) { return } // 更新进度条进度 mBinding.progressBar.progress = newProgress // 进度100%时隐藏进度条(延迟100ms,避免进度条闪一下) if (newProgress == 100) { mBinding.progressBar.visibility = View.GONE } else { // 非100%时显示进度条 if (mBinding.progressBar.visibility !== View.VISIBLE) { mBinding.progressBar.visibility = View.VISIBLE } } } } // 2. WebViewClient:监听页面加载状态(开始/完成/失败) mBinding.webview.webViewClient = object : WebViewClient() { override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) if (mBinding == null || mBinding.progressBar == null) { return } // 页面开始加载:重置进度条并显示 mBinding.progressBar.progress = 0 mBinding.progressBar.visibility = View.VISIBLE if (MenuUrlUtils.isCollectUrl(mUrl)) { mBinding.ivStar.setImageResource(R.mipmap.ic_star_on) } else { mBinding.ivStar.setImageResource(R.mipmap.ic_star_un) } } override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) if (mBinding == null || mBinding.progressBar == null) { return } // 页面加载完成:隐藏进度条(兜底,防止onProgressChanged未触发100%) mBinding.progressBar.visibility = View.GONE } override fun onReceivedError( view: WebView?, request: WebResourceRequest?, error: WebResourceError? ) { super.onReceivedError(view, request, error) if (mBinding == null || mBinding.progressBar == null) { return } // 加载失败:隐藏进度条,可显示错误提示 mBinding.progressBar.visibility = View.GONE } override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { if (url!!.startsWith("http://") || url.startsWith("https://")) { view!!.loadUrl(url) return true } else { if (url.startsWith("weixin://") || url.startsWith("alipays://") || url.startsWith("tel://") || url.startsWith("baiduboxapp://") ) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(intent) return true } } return false } } } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (mBinding.webview != null && mBinding.webview.canGoBack()) { mBinding.webview.goBack() return true } finish() return true } return super.onKeyDown(keyCode, event) // 对于其他按键,调用父类方法 } override fun onClick(v: View?) { when (v!!.id) { R.id.iv_back -> { if (mBinding.webview != null && mBinding.webview.canGoBack()) { mBinding.webview.goBack() return } finish() } R.id.iv_star -> { var title = ViewUtil.getText(mBinding.tvTitle) if (MenuUrlUtils.isCollectUrl(mUrl)) { MenuUrlUtils.removeCollectUrl(mUrl) mBinding.ivStar.setImageResource(R.mipmap.ic_star_un) } else { MenuUrlUtils.addCollectUrl(title, mUrl) mBinding.ivStar.setImageResource(R.mipmap.ic_star_on) } } R.id.tv_number -> { val currentUrl = mBinding.webview.url val title = ViewUtil.getText(mBinding.tvTitle).ifEmpty { currentUrl ?: "未命名页面" } if (currentUrl.isNullOrEmpty()) return val info = WindowInfo(title, currentUrl) // 判断是否已有窗口列表 if (WebViewWindowManager.getInstance().getAllWindows().any { it.url == currentUrl }) { // 已存在 → 直接打开管理页面 startActivity(Intent(this, WindowManagerActivity::class.java)) } else { // 首次添加 → 保存并提示 WebViewWindowManager.getInstance().addWindow(info) Toast.makeText(this, "已添加到窗口列表", Toast.LENGTH_SHORT).show() } } R.id.iv_finish -> { finish() } } } override fun onDestroy() { mBinding.webview.webViewClient = WebViewClient() mBinding.webview.webChromeClient = null mBinding.webview.destroy() super.onDestroy() } }package com.weishitech.sjykqyaowneng.bean data class WindowInfo( val title: String, val url: String, val timestamp: Long = System.currentTimeMillis() ) package com.weishitech.sjykqyaowneng.ui.home.activity; import android.content.Intent; import android.widget.Toast; import androidx.recyclerview.widget.GridLayoutManager; import com.weishitech.sjykqyaowneng.base.activity.BaseBindActivity; import com.weishitech.sjykqyaowneng.bean.WindowInfo; import com.weishitech.sjykqyaowneng.databinding.ActivityWindowManagerBinding; import com.weishitech.sjykqyaowneng.ui.home.adapter.WindowAdapter; import com.weishitech.sjykqyaowneng.ui.webview.activity.WebViewActivity; import com.weishitech.sjykqyaowneng.utils.WebViewWindowManager; import java.util.List; public class WindowManagerActivity extends BaseBindActivity<ActivityWindowManagerBinding> { private WindowAdapter adapter; @Override protected void init() { loadWindows(); } private void loadWindows() { List<WindowInfo> windows = WebViewWindowManager.getInstance().getAllWindows(); if (windows.isEmpty()) { Toast.makeText(this, "暂无打开的窗口", Toast.LENGTH_SHORT).show(); finish(); return; } mBinding.rv.setLayoutManager(new GridLayoutManager(this, 2)); adapter = new WindowAdapter(windows); mBinding.rv.setAdapter(adapter); // 点击卡片进入网页 adapter.setOnItemClickListener(windowInfo -> { Intent intent = new Intent(WindowManagerActivity.this, WebViewActivity.class); intent.putExtra("url", windowInfo.getUrl()); startActivity(intent); finish(); }); // 点击关闭按钮:删除并刷新 adapter.setOnCloseClickListener(windowInfo -> { WebViewWindowManager.getInstance().removeWindow(windowInfo.getUrl()); loadWindows(); // 重新加载 }); } }package com.weishitech.sjykqyaowneng.utils; import android.content.Context; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.weishitech.sjykqyaowneng.bean.WindowInfo; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; public class WebViewWindowManager { private static final String PREFS_NAME = "webview_windows"; private static final String KEY_WINDOWS = "windows"; private final List<WindowInfo> windows = new ArrayList<>(); private Context context; // 私有构造函数,防止外部实例化 private WebViewWindowManager() {} // 静态内部类实现单例(线程安全,延迟加载) private static class SingletonHolder { private static final WebViewWindowManager INSTANCE = new WebViewWindowManager(); } public static WebViewWindowManager getInstance() { return SingletonHolder.INSTANCE; } /** * 初始化:在 Application 中调用 setup(context) */ public void setup(Context context) { this.context = context.getApplicationContext(); init(); } /** * 从 SharedPreferences 加载已保存的窗口数据 */ private void init() { if (context == null) return; String json = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .getString(KEY_WINDOWS, null); if (json != null && !json.isEmpty()) { Gson gson = new Gson(); Type type = new TypeToken<List<WindowInfo>>() {}.getType(); List<WindowInfo> loadedList = gson.fromJson(json, type); if (loadedList != null) { windows.clear(); windows.addAll(loadedList); } } } /** * 获取所有窗口的副本(避免外部修改) */ public List<WindowInfo> getAllWindows() { return new ArrayList<>(windows); // 返回副本 } /** * 添加新窗口,相同 URL 的旧窗口会被移除,新窗口放在首位 */ public void addWindow(WindowInfo info) { if (info == null || info.getUrl() == null) return; // 移除已存在的同 URL 窗口 windows.removeIf(window -> window.getUrl().equals(info.getUrl())); // 添加到最前面(最新优先) windows.add(0, info); saveToSP(); } /** * 根据 URL 删除窗口 */ public void removeWindow(String url) { if (url == null) return; windows.removeIf(window -> window.getUrl().equals(url)); saveToSP(); } /** * 清空所有窗口 */ public void clear() { windows.clear(); saveToSP(); } /** * 保存到 SharedPreferences */ private void saveToSP() { if (context == null) return; Gson gson = new Gson(); String json = gson.toJson(windows); context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) .edit() .putString(KEY_WINDOWS, json) .apply(); // 异步提交 } } package com.weishitech.sjykqyaowneng.ui.home.adapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.weishitech.sjykqyaowneng.R; import com.weishitech.sjykqyaowneng.bean.WindowInfo; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class WindowAdapter extends RecyclerView.Adapter<WindowAdapter.WindowViewHolder> { private List<WindowInfo> windowList; private OnItemClickListener clickListener; private OnCloseClickListener closeClickListener; // 新增:关闭按钮点击 public interface OnItemClickListener { void onItemClick(WindowInfo windowInfo); } public interface OnCloseClickListener { void onCloseClick(WindowInfo windowInfo); } public WindowAdapter(List<WindowInfo> windowList) { this.windowList = windowList; } public void setOnItemClickListener(OnItemClickListener listener) { this.clickListener = listener; } public void setOnCloseClickListener(OnCloseClickListener listener) { this.closeClickListener = listener; } @NonNull @Override public WindowViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_window_card, parent, false); return new WindowViewHolder(view); } @Override public void onBindViewHolder(@NonNull WindowViewHolder holder, int position) { WindowInfo info = windowList.get(position); holder.tvTitle.setText(info.getTitle()); // 提取域名显示 holder.tvUrl.setText(extractDomain(info.getUrl())); // 主体点击:跳转 holder.itemView.setOnClickListener(v -> { if (clickListener != null) { clickListener.onItemClick(info); } }); // 关闭按钮点击 holder.btnClose.setOnClickListener(v -> { if (closeClickListener != null) { closeClickListener.onCloseClick(info); } }); } @Override public int getItemCount() { return windowList.size(); } public void updateData(List<WindowInfo> newList) { this.windowList = newList; notifyDataSetChanged(); } static class WindowViewHolder extends RecyclerView.ViewHolder { TextView tvTitle, tvUrl; ImageButton btnClose; ImageView ivFavicon; public WindowViewHolder(@NonNull View itemView) { super(itemView); tvTitle = itemView.findViewById(R.id.tvTitle); tvUrl = itemView.findViewById(R.id.tvUrl); btnClose = itemView.findViewById(R.id.btnClose); ivFavicon = itemView.findViewById(R.id.ivFavicon); } } // 提取域名工具方法 private String extractDomain(String url) { if (url == null || url.isEmpty()) return "未知网站"; try { Pattern pattern = Pattern.compile("https?://([^/]+)"); Matcher matcher = pattern.matcher(url); if (matcher.find()) { return matcher.group(1).replace("www.", ""); } } catch (Exception ignored) {} return url; } }ivFavicon这个添加新建窗口的图片,以及添加点击WebViewActivity页面R.id.tv_number直接进入新建窗口页面,不是只是提示已添加新窗口,这个控件也根据添加的新窗口数计数
最新发布
12-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值