你没看错, 现在是 2018 年, 我正在写一篇连我自己都感到很惊讶的关于 Android 系统的 Activity 生命周期的文章.
一开始, 我觉得 Activity 的生命周期虽然过于复杂, 但它不应该是一个难题. 我的意思是: 对于 Android 开发新手来说, 如何正确地处理 Activity 生命周期可能有点困难, 但是我无法想象对于那些富有经验的 android 开发者来说, 这依然是一个棘手的问题.
我还是想的太简单了.
一会儿我会告诉你整个故事, 但是先让我简述下我写这篇文章的目的.
我想要与你们分享我是如何处理 Activity 的生命周期的, 它比官方文档里描述的更简单, 而且还涵盖绝大多数棘手的极端情形.
Activity 的生命周期仍然是一个难题:
一个星期内发生的两件事情让我意识到: 即使在今天, Activity 的生命周期仍然是 Android 开发人员面临的一个难题.
几周前一位 Redditor 在 "androiddev" 里分享了一篇关于 Activity 的生命周期的文章. 这篇文章的写作基础来源于我在 StackOverflow 社区里分享的一个答案. 当时提问者推荐了一种处理 Activity 的生命周期的方法, 我觉得并不准确, 于是我立即提交了一条评论, 希望其他读者能意识到这一点.
然后陆续有几位 redditor 回答并质疑了我的观点, 进而演变成了一场漫长而非常有见地的讨论. 在讨论过程中, 我观察到了一些有趣的关于 Activity 的生命周期的现象:
Activity 的生命周期让许多有经验的 android 开发人员感到困惑.
官方文档里仍然存在着一些关于 Activity 的生命周期的过时信息和矛盾信息.
即使是编写 Google 官方教程的开发人员们也没有真正地理解 Activity 的生命周期以及它的影响.
几天之后, 我正在访问一个潜在客户. 该公司雇佣了一批富有经验的 Android 开发人员, 他们尝试在老的代码库里添加新功能, 这不是一件容易的事.
在快速代码审查期间, 我注意到其中一位维护人员决定在 Activity 的 onCreate(Bundle)中订阅 EventBus, 并在 onDestroy()中取消订阅. 这样做会带来很大的麻烦, 所以我建议他们重构这部分代码.
My take on Activity life-cycle::
上述两件事让我意识到还是有许多 android 开发人员对 Activity 的生命周期充满困惑. 现在, 我想尝试通过分享我的经验来给其他开发人员提供便利.
我不打算在这里为 Activity 的生命周期重新编写一份文档, 这代价太大了. 我会告诉你如何在 Activity 生命周期的各个方法之间划分逻辑以实现最简单的设计并避免最常见的陷阱.
onCreate(Bundle):
Android framework 并没有提供 Activity 的构造函数, 它会自动创建 Activity 类的实例. 那么我们应该在哪里初始化 Activity 呢?
你可以把所有应该在 Activity 的构造函数里处理的逻辑放在 onCreate(Bundle)这个方法里.
理想情况下, 构造函数只会初始化对象的成员变量:
- public ObjectWithIdealConstructor(FirstDependency firstDependency,
- SecondDependency secondDependency) {
- mFirstDependency = firstDependency;
- mSecondDependency = secondDependency;
- }
除了初始化成员变量外, onCreate(Bundle)方法还承担着另外两个职责: 恢复之前保存的状态并调用 setContentView()方法.
所以, onCreate(Bundle)方法中的基本功能结构应该是:
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getInjector().inject(this); // inject dependencies
- setContentView(R.layout.some_layout);
- mSomeView = findViewById(R.id.some_view);
- if (savedInstanceState != null) {
- mWelcomeDialogHasAlreadyBeenShown = savedInstanceState.getBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN);
- }
- }
显然, 这并不适用于所有人, 你可能会有一些不同的结构. 不过没关系, 只要你在 onCreate(Bundle)中没有以下内容:
- Subscription to observables
- Functional flows
Initialization of asynchronous functional flows
Resources allocations
每当我需要决定某一段逻辑是否应该放在 onCreate(Bundle)方法里时, 我就会问自己这个问题: 这段逻辑与 Activity 的初始化有关吗? 如果答案是否定的, 我就会另寻别处.
当 Android framework 创建了一个 Activity 实例, 而且它的 onCreate(Bundle) 方法执行完毕之后, 这个 Activity 就会处于已创建状态.
处于已创建状态的 Activity 并不会触发资源分配, 也不会接收到系统里其他对象发出的事件. 从这种意义上来讲,"created" 状态是一种已经准备好了, 但是不活跃和隔离的状态.
onStart():
为了使 Activity 能够与用户交互, Android framework 接着会调用 Activity 的 onStart()方法使其处于活跃状态. onStart() 方法中的一些基本的逻辑处理包括:
Registration of View click listeners
Subscription to observables (general observables, not necessarily Rx)
- Reflect the current state into UI (UI update)
- Functional flows
Initialization of asynchronous functional flows
Resources allocations
对于第 1 点和第 2 点, 你可能会感到很惊讶. 因为在大多数官方文档和教程里, 它们都会被放在 onCreate(Bundle)里. 我认为这是对复杂的 Activity 的生命周期的一种误解, 我会在接下来的 onStop()方法里讲到这一点.
所以, onStart()方法中的基本功能结构应该是:
- @Override
- public void onStart() {
- super.onStart();
- mSomeView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- handleOnSomeViewClick();
- }
- });
- mFirstDependency.registerListener(this);
- switch (mSecondDependency.getState()) {
- case SecondDependency.State.STATE_1:
- updateUiAccordingToState1();
- break;
- case SecondDependency.State.STATE_2:
- updateUiAccordingToState2();
- break;
- case SecondDependency.State.STATE_3:
- updateUiAccordingToState3();
- break;
- }
- if (mWelcomeDialogHasAlreadyBeenShown) {
- mFirstDependency.intiateAsyncFunctionalFlowAndNotify();
- } else {
- showWelcomeDialog();
- mWelcomeDialogHasAlreadyBeenShown = true;
- }
- }
需要强调的一点是, 你实际在 onStart()中所执行的操作是由每个特定 Activity 的详细需求决定的.
在 Android framework 调用完 onStart()方法后, Activity 将从已创建状态转换为已启动状态. 在此状态下, 它可以正常工作, 并且可以与系统的其他组件协作.
onResume():
关于 onResume()方法的第一条准则是你不需要覆盖 onResume()方法. 第二条准则也是你不需要覆盖 onResume()方法. 第三条准则是在某些特殊情况下你才会确实需要覆盖 onResume()方法.
我搜索了我的一个项目的代码库, 发现有 32 个 onStart()方法被覆盖重写, 平均每段代码约 5 行, 一共有大约 150 多行代码. 相反, 只有 2 个 onResume()方法被覆盖重写, 一共 8 行代码, 这 8 行代码主要是用于恢复播放动画和视频.
这总结了我对 onResume()的看法: 它只能用于在屏幕上启动或恢复一些动态的对象. 你想在 onResume()而不是 onStart()中做这件事的原因将在稍后的 onPause()部分讨论.
在 Android framework 调用完 onResume()方法后, Activity 将从已启动状态转换为 "resumed" 状态. 在此状态下, 用户可以与它进行交互.
onPause():
在这个方法里, 您应该暂停或停止在 onResume()中恢复或启动的动画和视频. 就像 onResume()一样, 你几乎不需要重写 onPause(). 在 onPause()方法而不是 onStop()方法中处理动画的原因是因为当 Activity 被部分隐藏 (比如, 弹出系统对话框) 或者是在多窗口模式下失去焦点的时候, 只有 onPause()方法会被调用. 因此如果你想在 只有用户正在与 Activity 交互的情况下播放动画, 同时避免在多窗口模式下分散用户注意力并延长电池寿命, onPause() 是你唯一可以信赖的选择. 这一结论的前提是你希望你的动画或视频在用户进入多窗口模式时停止播放, 如果你希望你的动画或视频在用户进入多窗口模式时继续播放, 那么你不应该在 onPause()中暂停它. 在这种情况下, 你需要将 onResume()/ onPause()中的逻辑移动到 onStart()/ onStop().
一种特殊的情况是相机的使用, 由于相机是所有应用程序共享的单一资源, 因此通常您会想要在 onPause()方法中释放它.
在 Android framework 调用完 onPause()方法后, Activity 将从 "resumed" 状态转换为已启动状态.
onStop():
在这个方法里, 您将注销所有观察者和监听者, 并释放 onStart()中分配的所有资源.
所以, onStop()方法里的基本功能结构应该是:
- @Override
- public void onStop() {
- super.onStop();
- mSomeView.setOnClickListener(null);
- mFirstDependency.unregisterListener(this);
- }
让我们来讨论一个问题: 为什么你需要在这个方法里取消注册?
首先, 如果此 Activity 不被 mFirstDependency 取消注册的话, 您可能会泄漏它. 这不是我愿意承担的风险.
所以, 问题变成了为什么要在 onStop()方法里取消注册, 而不是 onPause()方法或者是 onDestroy()方法?
想要快速清晰地解释这些确实有点棘手.
如果在 onPause()方法里调用 mFirstDependency.unregisterListener(this), 那么 Activity 将不会收到相关异步流程完成的通知. 因此, 它不能让用户感知到这一事件, 从而完全违背了多窗口模式的设计初衷, 这不是一种好的处理方式.
如果在 onDestroy()方法里调用 mFirstDependency.unregisterListener(this), 这同样不是一种好的处理方式.
当应用被用户推到后台 (例如, 点击 "home" 按钮) 时, Activity 的 onStop()将被调用, 从而使得其返回到 "已创建" 状态, 这个 Activity 可以在几天甚至几周的时间内保持这个状态.
如果这时候 mFirstDependency 产生了连续的事件流, 那么在这几天甚至几周的时间里, Activity 可以都处理这些事件, 即使用户在这段时间内从未真正与它交互过. 这将是对用户电池寿命的不负责任的浪费, 而且在这种情况下, 应用消耗的内存会逐渐增多, 应用进程被 OOM(Out Of Memory)Killer 杀死的可能性也会增大.
因此, 在 onPause()和 onDestroy()里调用 mFirstDependency.unregisterListener(this)都不是一种好的处理方式, 您应该在 onStop()中执行此操作.
关于注销 View 的事件监听器, 我想多说几句.
由于 Android UI 框架的不合理设计, 像这个问题 https://stackoverflow.com/questions/38368391/android-click-event-after-activity-onpause 中所描述的奇怪场景是可能会发生的. 有几种方法可以解决这个问题, 但是从 onStop()中注销 View 的事件监听器是最干净的一种.
或者, 您可以完全忽略这种罕见的情况. 这是大多数 Android 开发人员所做的, 但是, 您的用户偶尔会遇到一些奇怪的行为和崩溃.
在 Android framework 调用完 onStop()方法后, Activity 将从已启动状态转换为已创建状态.
onDestroy():
永远都不要覆盖重写这个方法.
我搜索了我所有的项目代码库, 没有一个地方需要我重写 onDestroy()方法.
如果你思考一下我前面对 onCreate(Bundle)的职责的描述, 你会发现这是完全合理的, 它仅仅执行了了初始化 Activity 对象的操作, 所以完全没有必要手动完成清理工作.
当我在查看新的 Android 代码库时, 我通常会搜索几个常见的错误模式, 这使我能够快速地了解代码的质量. onDestroy()的覆盖重写是我寻找的第一个错误模式.
onSaveInstanceState(Bundle):
此方法用于保存一些临时的状态, 在这个方法里你位移需要做的就是将你想保存的状态存入 Bundle 数据结构中:
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
- }
在这里, 我想提一个与 Fragment 有关的常见陷阱.
如果你在这个方法执行完之后, 提交 Fragment 事务, 那么应用程序会抛出 IllegalStateException 异常而导致崩溃.
你需要知道的一点是 onSaveInstanceState(Bundle)将在 onStop()之前被调用.
好消息是, 经过多年的努力, 谷歌终于意识到了这个 bug. 在 Android P 中, onStop()将在 onSaveInstanceState(Bundle)之前被调用.
总结:
是时候结束这篇文章了.
虽然本文的内容既不简短也不简单, 但我认为它比大多数文档 (包括官方教程) 更简单, 更完整.
我知道一些经验丰富的专业开发人员会质疑我处理 Activity 生命周期的方式, 没关系. 事实上, 我非常期待您的质疑.
不过, 请记住, 我已经使用这个方案好几年了. 根据我的经验, 使用这种方法编写的代码比许多项目中看到的对于 Activity 生命周期的处理逻辑要清晰简洁的多.
这种方法的最终验证来自 Google 本身.
从 Nougat 开始, Android 系统支持多屏任务. 我在这篇文章中与你分享的方法几乎不需要任何调整. 这基本上证实, 相对于官方文档, 它包含了对 Activity 生命周期更深入的见解.
另外, Android P 中对于 Activity 的 onStop()和 onSaveInstanceState(Bundle)方法之间的调用顺序的调整将使得这种方法对于 Fragments 的使用来说是最安全的.
我不认为这是巧合.
来源: https://juejin.im/post/5b0656066fb9a07ab83e6c13