28、Activity生命周期调试:ActivityManager、启动流程、生命周期回调、任务栈

说实话,Activity生命周期这块,很多Android开发都觉得“我懂了”。但真到了线上问题排查,或者要解释一个诡异的闪退时,往往就卡住了。我个人习惯把生命周期调试分成四个维度来看:谁在管(ActivityManager)、怎么启动的(流程)、回调怎么走(生命周期)、以及它们住在哪(任务栈)。今天咱们就一个一个掰开聊。

一、ActivityManager:幕后的大管家

ActivityManager(简称AM)其实是个系统服务。你写的startActivity(),最终都会通过Binder调用到系统进程里的ActivityManagerService(AMS)。

我在项目中遇到过一个问题:App启动特别慢,log里看到AMS一直在等某个锁。后来发现是某个三方SDK在Application.onCreate里做了太多同步操作,导致AMS分配ActivityRecord时被阻塞。说白了,AMS是单线程处理请求的,你主线程卡了,它也跟着卡。

关键点:AMS负责创建ActivityRecord、管理任务栈、调度生命周期。调试时重点关注AMS相关的log,tag通常是ActivityManageram_开头。

二、启动流程:从点击到onCreate

一个Activity的启动,大致分这么几步:

  1. 发起请求:你的App通过Binder向AMS发送startActivity请求。
  2. AMS处理:AMS解析Intent,检查权限,创建ActivityRecord,决定放在哪个任务栈。
  3. 进程检查:如果目标Activity所在进程没启动,AMS会先让Zygote fork一个新进程。
  4. Application初始化:新进程里先创建Application,调用onCreate。
  5. 创建Activity:AMS通过Binder通知App进程,App进程的ActivityThread开始创建Activity实例,走生命周期。

嗯,这里要注意:Application.onCreate()是在Activity.onCreate()之前执行的。如果你在Application里做了耗时操作,第一个Activity的启动就会被拖慢。

调试技巧:adb shell am start -W <包名/Activity全路径>可以查看启动耗时。输出里会有ThisTimeTotalTimeWaitTime,分别代表当前Activity、整个任务链、以及包含AMS调度的时间。

三、生命周期回调:不是只有onCreate/onDestroy

标准的生命周期回调大家都熟,但调试时真正有用的,是那些“边界情况”。

回调 触发时机 调试关注点
onCreate Activity第一次创建 检查savedInstanceState是否为空,判断是否冷启动
onStart Activity可见但不可交互 此时View已测量,但还没绘制完成
onResume Activity可交互 前台生命周期开始,注意不要在这里做重量级操作
onPause Activity失去焦点 必须快速完成,否则系统会认为ANR
onStop Activity不可见 可以做一些轻量级保存,但别依赖它一定会被调用
onDestroy Activity被销毁 检查是否因为finish()还是系统回收

我曾经踩过一个坑:在onPause里做了网络请求,结果用户快速切换App时,onPause执行太久,系统直接杀了我的进程。后来我改成在onStop里做,但onStop也不保证一定执行——比如低内存时系统直接杀进程,onStop和onDestroy都不会走。

避坑指南:不要在onSaveInstanceState里保存大数据。这个方法在onStop之前调用,但如果你保存的数据太大,序列化时间过长,会影响切换体验。我曾经见过一个App在onSaveInstanceState里保存了整个列表的JSON,结果每次切后台都卡顿。

四、任务栈:Activity的“宿舍楼”

任务栈(Task)是存放Activity的容器。默认情况下,同一个App的Activity都在同一个栈里,后进先出。但通过launchMode和Intent Flag,你可以改变这个行为。

调试任务栈,我最常用的命令是:

adb shell dumpsys activity activities

这个命令会输出当前所有任务栈的信息,包括:

  • 每个栈的ID(stackId)
  • 栈里有哪些Activity(ActivityRecord)
  • 每个Activity的状态(resumed、paused、stopped等)
  • Affinity(亲和性)信息

举个例子,你看到输出里有:

Stack #0:
  Task id #123:
    * ActivityRecord{xxx u0 com.example/.MainActivity t123}
      State: RESUMED
    * ActivityRecord{yyy u0 com.example/.DetailActivity t123}
      State: STOPPED

这说明当前栈顶是MainActivity,DetailActivity在它下面,处于STOPPED状态。如果你按返回键,DetailActivity会重新onRestart -> onStart -> onResume。

我个人习惯在调试多任务切换问题时,先跑一遍dumpsys activity activities,看看栈结构是不是预期的。比如singleTask启动模式,如果目标Activity已经在别的栈里,系统会把那个栈整个拉到前台。这时候看栈ID就能发现问题。

五、一张图看懂整体流程

下面这张SVG图,把Activity从启动到销毁的完整流程、AMS的角色、以及任务栈的关系串起来了。建议你保存下来,排查问题时对照着看。

Activity生命周期调试核心流程 用户操作(点击/返回) App进程 ActivityThread ActivityManagerService (系统进程) 生命周期回调顺序 onCreate onStart onResume onPause onStop onDestroy 任务栈(Task)结构 Activity C (栈顶) Activity B Activity A ← 后进先出(LIFO) ← 按返回键依次出栈

六、调试实战:一个典型问题排查

假设你遇到一个Bug:从Activity A跳转到B,再按返回键,结果回到了桌面而不是A。你会怎么查?

我的排查步骤:

  1. 先跑dumpsys activity activities,看任务栈结构。
  2. 如果发现A和B不在同一个栈里,检查B的launchMode是不是设置了singleTasksingleInstance
  3. 再看Intent里有没有加FLAG_ACTIVITY_NEW_TASK
  4. 最后检查Affinity,如果B的taskAffinity和A不同,系统会把它放到新栈。

我曾经就遇到过类似问题:一个第三方SDK的Activity设置了singleTask,结果每次跳转都创建新栈,返回时直接回到桌面。最后在Manifest里把taskAffinity改成和主App一致才解决。

快速定位技巧:在log里搜索am_create_activityam_resume_activity,可以精确看到每个Activity的创建和恢复时间点。配合dumpsys activity,基本能覆盖90%的生命周期问题。

好了,Activity生命周期调试的核心内容就这些。记住:AMS是调度中心,生命周期是表象,任务栈是容器。下次遇到诡异的问题,先看栈结构,再看log时间线,最后检查launchMode和Flag。这套组合拳打下来,大部分问题都能定位。


专注资料整理