跳到主要内容为什么 Activity.finish() 之后 10s 才 onDestroy?源码深度解析 | 极客日志Kotlinjava
为什么 Activity.finish() 之后 10s 才 onDestroy?源码深度解析
深入分析了 Android Activity.finish() 后 onDestroy 延迟 10 秒调用的原因。通过源码追踪发现,onStop 和 onDestroy 的触发依赖于主线程空闲时的 IdleHandler 回调。当新 Activity 启动时主线程繁忙,导致 IdleHandler 无法执行,系统会启用 10 秒超时机制强制销毁。文章提供了复现场景、源码流程解析及避免此类问题的最佳实践方案。
FlinkHero1 浏览 为什么 Activity.finish() 之后 10s 才 onDestroy?源码深度解析
1. 问题背景与复现
在 Android 开发中,我们常遇到一个现象:调用 Activity.finish() 后,onDestroy() 并没有立即回调,而是延迟了约 10 秒。这会导致资源释放不及时、状态赋值异常等不可控问题。
为了定位问题,我们需要先复现该场景。创建一个简单的跳转流程:FirstActivity 点击按钮跳转到 SecondActivity,并在 FirstActivity 中记录生命周期回调的时间间隔。
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 {
startActivity(Intent(this, SecondActivity::class.java))
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")
}
}
正常情况下,日志显示 onStop 和 onDestroy 会在 onPause 后的几百毫秒内完成。
若模拟目标 Activity (SecondActivity) 启动时进行大量动画或持续向主线程消息队列塞入任务,例如不断 post 延时任务:
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, 耗时:6 ms
SecondActivity: onResume
FirstActivity: onStop, 耗时:10033 ms
FirstActivity: onDestroy, 耗时:10037 ms
可以看到,当新 Activity 的主线程过于繁忙,无法及时空闲时,旧 Activity 的 onStop 和 onDestroy 会被强制延迟约 10 秒。
2. Activity.finish() 流程分析
基于 Android 9.0 AOSP 源码进行分析。
2.1 Finish 入口
public void finish() {
finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}
private void finish(int finishTask) {
if (mParent == null) {
try {
if (ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)) {
mFinished = true;
}
} catch (RemoteException e) {
}
}
}
AMS 层接收到请求后,调用 ActivityStack.requestFinishActivityLocked(),最终进入 finishActivityLocked()。
2.2 Pause 阶段
在 finishActivityLocked() 中,如果当前是 Resume 状态的 Activity,会调用 startPausingLocked()。
final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
ActivityRecord resuming, boolean pauseImmediately) {
mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,
PauseActivityItem.obtain(...));
if (!pauseImmediately) {
schedulePauseTimeout(prev);
}
return true;
}
这里有两步操作:一是通过 Binder 通知客户端执行 onPause;二是设置 500ms 超时机制。如果 500ms 内收到 activityPaused 回调,则移除超时消息。
2.3 Stop/Destroy 阶段
onPause 完成后,系统会尝试销毁当前 Activity。但在 finishCurrentActivityLocked() 中,如果模式为 FINISH_AFTER_VISIBLE(即等待下一个 Activity 可见),且新 Activity 尚未完全显示,则不会立即销毁。
if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
&& next != null && !next.nowVisible) {
addToStopping(r, false, false);
r.setState(STOPPING, "finishCurrentActivityLocked");
return r;
}
此时,待销毁的 Activity 被保存在 ActivityStackSupervisor.mStoppingActivities 集合中,等待后续处理。
3. 谁指挥着 onStop/onDestroy 的调用?
关键在于新 Activity 的 onResume 流程。当新 Activity 完成绘制并显示后,主线程会注册一个 IdleHandler。
public void handleResumeActivity(...) {
Looper.myQueue().addIdleHandler(new Idler());
}
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
am.activityIdle(a.token, a.createdConfig, stopProfiling);
return false;
}
}
AMS 收到 activityIdle 后,会遍历 mStoppingActivities 集合,对其中记录的 Activity 执行 stopActivityLocked 或 destroyActivityLocked。
核心逻辑: onStop 和 onDestroy 的触发依赖于主线程消息队列的空闲。如果主线程一直忙碌(如上文模拟的大量消息),IdleHandler 就无法执行,导致生命周期回调被阻塞。
4. 10s 延迟的兜底机制
既然依赖主线程空闲,为何会有 10s 延迟而不是无限等待?这是因为系统设计了超时机制。
在 resumeFocusedStackTopActivityLocked() 流程中,系统会调度一个超时消息:
void scheduleIdleTimeoutLocked(ActivityRecord next) {
Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next);
mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);
}
如果在 10 秒内主线程空闲并执行了 IdleHandler,则会移除该超时消息。否则,10 秒后超时消息触发,系统会强制调用 activityIdleInternalLocked,从而清理 mStoppingActivities 中的 Activity。
这就是为什么即使主线程卡死,Activity 最终也会在 10 秒左右被销毁的原因。
5. 解决方案与最佳实践
虽然系统有兜底机制,但 10 秒的延迟会影响用户体验和资源管理。以下是优化建议:
5.1 避免主线程阻塞
确保在 Activity 切换期间,主线程不执行耗时操作。特别是不要在 onCreate 或 onStart 中同步加载大量数据或执行复杂计算。
5.2 异步加载策略
使用协程、RxJava 或 Handler 将耗时任务移至子线程。对于 UI 渲染,利用 ViewTreeObserver 监听布局完成事件,而非在主循环中轮询。
5.3 监控主线程负载
开发阶段可利用 Looper.getMainLooper().setMessageLogging() 打印消息队列日志,排查是否有异常消息堆积:
Looper.getMainLooper().setMessageLogging(
new MessageLogger(Log.DEBUG, "MainLooper"));
5.4 资源释放时机调整
不要过度依赖 onDestroy 释放关键资源。建议在 onPause 或 onStop 中释放可暂停的资源(如传感器、网络请求),仅在 onDestroy 中释放不可恢复的资源(如数据库连接)。
6. 总结
Android Activity 的生命周期销毁并非由 finish() 直接触发,而是依赖于主线程空闲时的 IdleHandler 回调。当主线程繁忙时,系统会通过 10 秒超时机制强制回收 Activity。理解这一机制有助于开发者优化性能,避免资源泄漏和体验卡顿。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online