上一篇文章唠了唠 任务栈,返回栈和启动模式,今天来聊一聊同样和 Activity 息息相关的 生命周期 。
关于 Activity 的生命周期,我相信大家倒着都可以说出来了。这里放一张 android-lifecycle 里的经典大图,其中也包含了 Fragment 的生命周期图。
由于这是一张 2014 年的图,注意 onRestoreInstanceState()
和 onSaveInstanceState()
的调用时机会因为 SDK 版本的不同稍有变化,文章后面也会提到。
目录
- 每个生命周期做了什么?
- onStart/onStop ?还是 onResume/onPause ?
- 如何进行 UI 状态的存储与恢复?
- Activity 和应用进程的关系
- 在什么时机触发 LeakCanary 的检测?
- 被 SharedPreference 拖累的 Activity
每个生命周期做了什么?
onCreate()
这是 Activity 的第一个生命周期方法,其中必须要做的操作就是 setContentView()
。
setContentView()
里面大概做了这么几件事:
- 创建 DecorView,并设置 PhoneWindow
- 解析 xml 布局文件,生成 View 对象并塞到 DecorView 中
此时 DecorView 并没有被绘制,Window 对象也没有被显示到屏幕,Activity 也是不可见的。
除此之外,开发者通常也会在 onCreate()
方法中做一些数据的初始化操作。
onCreate()
在一次完整的生命周期中只会回调一次,它也不是一个长驻状态,完成工作只会就会进入 onStart()
。
onStart()
onStart()
也不是一个长驻状态,官方文档对于它的描述是这样的:
The onStart() call makes the activity visible to the user, as the app prepares for the activity to enter the foreground and become interactive.
在 onStart()
方法中,Activity 对用户可见,应用准备进入前台和用户交互。我对这句 Activity 对用户可见 其实抱有很大的疑问。
不考虑特殊情况,正常启动一个 Activity,onCreate -> onStart ,此时所谓的 “可见”,见到的是什么?
这个问题在下面的 onResume
一节中会详细说明,读者可以先仔细揣摩一下。
onStart()
方法中可以做些什么呢?通常会和 onStop()
搭配做一些资源申请和释放的工作,例如相机的申请和释放。
onResume
熟悉 UI 绘制流程的读者肯定知道,onResume()
是真正进行 UI 绘制以及显示的地方。其中的核心逻辑就是 WindowManager.addView()
方法,实际调用的是 WindowManagerGlobal.addView()
方法。它大致干了这么几件事:
- 创建 ViewRootImpl 对象
- 调用 ViewRootImpl.setView() 方法
ViewRootImpl
的构造函数中做了这么几件事:
- 初始化跟 WMS 通信的 WIndowSession 对象,这是一个 Binder 对象
- 初始化 Choreographer 对象
ViewRootImpl.setView()
方法做了这么几件事:
- 调用 requestLayout() 方法,发起绘制
- Binder 调用 WMS.addToDisplay() 方法,将 window 添加到屏幕
requestLayout()
方法中就会进行我们所熟知的 测量、布局和绘制 流程,但并不是直接进行的,它依赖 vsync 信号。requestLayout()
只是通过前面已经初始化了的 Choreographer 对象进行注册监听,当下一个 vsync 信号来临时,会回调 performTraversals()
方法,这其中就会真正的进行测量、布局和绘制。
整个 UI 绘制流程的知识点很多,仅靠以上简单一段文字肯定是无法完全概括的,感兴趣的读者可以自己去翻翻源码。但是我们可以肯定是,onResume
是真正的用户界面可见的时机。
再回到之前的问题,onStart
中可见的是什么?我也无法回答这个问题,或者可能大家都曲解了官方文档的意思,是否应该理解为 “Activity 即将可见”。大家可以在留言区说说你的看法。
同样,onResume()
通常也可以和 onPause()
搭配做一些资源申请和释放的工作。那么,既然 onStart/onStop
和 onResume/onPause
都可以,该如何选择呢?同样放到后面进行解答。
onPause
onPause()
是一个很短暂的过程,之后如果用户返回了之前的 Activity,则会回调 onResume
。如果没有,则会回调 onStop
。
给 onPause
一个精准的描述的话,应该是 非前台,不可交互,但不一定不可见 。对于系统来说,无论是手机还是 PC ,同一个时间一定只有一个处于前台,获取焦点,且可与用户交互的活动窗口,所以 非前台,不可交互 很好理解。那 不一定不可见 如何理解呢?其实也很简单,类似 PC 的多窗口,Android 系统也是有多窗口模式的。
最后,注意 onPause
中不建议进行重量级的耗时操作,因为在 Activity 跳转过程中,前一个 Activity 的 onPause()
是发生在后一个 Activity 的任何生命周期之前的。
onStop
完全的不可见状态。但此时的 Activity 仍在内存中,只是没有关联到任何 Window 。如果后续有机会再次返回,则会回调 onRestart -> onStart
。
onDestroy
onStop
之后没有被用户捞回去,最后就得被销毁。主动的调用 finish
或者系统配置改变也会可能导致销毁。
注意在 onDestroy
中释放所有不需要的资源,否则可能导致内存泄露。
到目前为止,简单介绍了各个生命周期的回调时机和应该处理的事情,同时也带了一些疑问。下面来说说一些知识点。
onStart/onStop ?还是 onResume/onPause ?
在使用 EventBus 的时候,需要在相应的声明周期中进行注册和解注册的操作,如下所示:
@Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } 复制代码
当然,在 onResume/onPause
中一般也是没有问题的。
@Override public void onResume() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onPause() { super.onStop(); EventBus.getDefault().unregister(this); } 复制代码
但它们之间并不是完全没有区别的。通常情况下,onPause
之后很快就会 onStop
,但是考虑到 Android 7.0 之后新增的 多窗口模式 的话,Activity 可能会停留在 onPause
一段时间。这种情况下,如果在 onStop
中进行资源释放操作的话,可能并不能及时释放。如果你的 Activity 持有的是相机等系统资源,会导致其他应用无法使用该资源,对用户来说无疑是很不友好的。所以,在进行类似操作的时候要考虑一下应用场景。
onResume/onPause
关注的是 Activity 是否可以交互,onStart/onStop
关注的是 Activity 是否可见。
最后得说下,上面的代码中把生命周期处理和视图控制器耦合在一起,并不是那么优雅,可以通过 LifeCycle 组件进行解耦。拿我之前文章中的代码说明一下:
class LocationUtil( ) : LifeCycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun startLocation( ){ ...... } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun stopLocation( ){ ...... } } 复制代码
传送门:
如何进行 UI 状态的存储与恢复?
除了正常状态下的数据持久化存储,异常情况下的数据保存和恢复也是必要的。这里的异常情况一般指系统配置变化,典型的横竖屏切换,系统语言切换等。
异常情况下终止的 Activity,系统会调用 onSaveInstanceState()
方法来保存当前 Activity 的状态。那么哪些状态默认会被保存呢?我们可以看一下 TextView
的 onSaveInstanceState()
方法。
@Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); ...... if (freezesText || hasSelection) { SavedState ss = new SavedState(superState); if (freezesText) { if (mText instanceof Spanned) { final Spannable sp = new SpannableStringBuilder(mText); if (mEditor != null) { removeMisspelledSpans(sp); sp.removeSpan(mEditor.mSuggestionRangeSpan); } // 保存文字 ss.text = sp; } else { ss.text = mText.toString(); } } ... return ss; } return superState; } 复制代码
可以看到文字是保存了的,这里删减了很多代码,其实还保存了其他一些状态。只要是实现了 onSaveInstanceState()
方法的 View,都会被保存下来。当 Activity 重新创建时,会通过 onCreate()
或者 onRestoreInstanceState()
方法恢复状态。
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) } 复制代码
在 Kotlin 中重写这两个方法也可以看到,onCreate()
方法的参数是可空的,因为会有正常启动的情况。所以一般建议直接在 onRestoreInstanceState
方法中进行状态恢复即可。
对于非 UI 状态的其他数据,就得自己手动进行保存和恢复了。这里直接拿官方文档的示例代码:
override fun onSaveInstanceState(outState: Bundle?) { // Save the user's current game state outState?.run { putInt(STATE_SCORE, currentScore) putInt(STATE_LEVEL, currentLevel) } // Always call the superclass so it can save the view hierarchy state super.onSaveInstanceState(outState) }qubie override fun onRestoreInstanceState(savedInstanceState: Bundle?) { // Always call the superclass so it can restore the view hierarchy super.onRestoreInstanceState(savedInstanceState) // Restore state members from saved instance savedInstanceState?.run { currentScore = getInt(STATE_SCORE) currentLevel = getInt(STATE_LEVEL) } } 复制代码
要注意的是,通过 onSaveInstanceState
的方式存储数据,由于是发生在主线程,且存在序列化/反序列化的开销,并不建议存储大量数据。其实更好的做法是使用 ViewModel
,它可以在系统配置变化发生的 Activity 重建过程中来保存数据。
最后来说一下 onSaveInstanceState
的调用时机问题。在不同的 SDK 版本中,这个时机是不唯一的。
- SDK 11 之前,在
onPause()
之前调用 - SDK 28 之前,会在
onStop()
之前调用 - SDK 28 之后,会在
onStop
之后调用
当然,这对我们来说并没有什么实质的区别。
Activity 和应用进程的关系
当系统内存不足时,会存在单个 Activity 直接被系统回收的情况吗?
答案是否定的。
首先应用进程的生存时间并不是由自己直接控制的,而是由系统决定的。每一个 App 都至少对应着一个 Linux 进程。当系统内存不足无法满足正在与用户交互的进程的需求时,可能会回收一些资源。这些资源是一个一个进程,而不是进程里的一个一个组件,不会存在单个 Activity 被系统回收的情况。
那么,既然要回收进程,那么肯定会给进程分个三六五等。按照官方文档的描述,大致有这么几类,重要性依次降低。
Foreground Process :
- 有 Activity 处于前台,正在和用户交互
- BroadcastReceiver 的 onReceive 方法正在执行
- Service 正在运行代码,包括 onCreate,onStart,onDestroy
Visible Process :
- 有 Activity 可见,但不在前台
- Service 正在运行前台服务
- 持有一些用户可以感知的特定服务,如动态壁纸,输入法服务
Service Process :
有正在运行的 Service ,对用户不可见,但正在进行一些用户关心的工作,例如后台下载等。当系统内存不足的时候,可能会被干掉。
官方文章说运行超过 30 min 可能会被降级,但这个在不同的国产 ROM 肯定是有魔改的,具体应该以实际测试为准。
Cached Process :
当系统内存不足时可以随时自由终止的无用线程。但是为了更高效的切换应用,系统一般不会把它们全部 kill 掉。
在什么时机触发 LeakCanary 的检测?
之前群里的小伙伴 codelang 分享了一个有趣的知识点:
为什么 LeakCanary 要在 Activity onDestroy 之后调用 RefWatcher.watch() 开始监测内存泄露?
他给出的答案是:
从 ActivityThread 的角度看,Activity 就是一个对象,按照 GC Root,Activity 是被 ActivityThread 给引用着的。为什么要在 onDestroy 之后开始检测,因为这个时候 Activity 和 ActivityThread 的引用断开了,在 ActivityThread.performDestroyActivity() 中从 mActivities 中移除了当前 Activity 对象。
的确,在 onDestroy()
之前,Activity 根本没有断开和 GC Roots 的引用,检测个啥呢。
被 SharedPreference 拖累的 Activity
之前写过一篇 细数 SharedPreference 的槽点 来吐槽 SP 。不合理的使用 SP 可能会导致卡顿,甚至 ANR 。
apply()
方法和 commit()
方法不同,它是通过异步任务来进行存储操作的,通过 QueuedWork
类实现。
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); 复制代码
而在 onStop
中,需要等待这个异步任务的完成。看一下 ActivityThread.java
中 handleStopActivity()
方法:
@Override public void handleStopActivity(IBinder token, boolean show, int configChanges, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { ...... // 可能因等待写入造成卡顿甚至 ANR if (!r.isPreHoneycomb()) { QueuedWork.waitToFinish(); } ...... } 复制代码
无论修改了什么数据,这里的存储都是全量写入,如果数据量过大,一定是会存在性能问题的。此外还要注意 SP 的舒适化过程也是全量读取放到内存中,所以在数据量大的情况下,注意提前初始化。
最后
关于 Activity 的生命周期就说这么多了,后面如果碰到相关的有意思的问题,再回来补充。