你没看错,如今是2018年,我正在写一篇连我本身都感到很惊讶的关于Android系统的Activity生命周期的文章。android
一开始,我以为Activity的生命周期虽然过于复杂,但它不该该是一个难题。个人意思是:对于Android开发新手来讲,如何正确地处理Activity生命周期可能有点困难,可是我没法想象对于那些富有经验的android开发者来讲,这依然是一个棘手的问题。安全
我仍是想的太简单了。bash
一下子我会告诉你整个故事,可是先让我简述下我写这篇文章的目的。数据结构
我想要与大家分享我是如何处理Activity的生命周期的,它比官方文档里描述的更简单,并且还涵盖绝大多数棘手的极端情形。框架
一个星期内发生的两件事情让我意识到:即便在今天,Activity的生命周期仍然是Android开发人员面临的一个难题。异步
几周前一位Redditor在“androiddev”里分享了一篇关于Activity的生命周期的文章。这篇文章的写做基础来源于我在StackOverflow社区里分享的一个答案。当时提问者推荐了一种处理Activity的生命周期的方法,我以为并不许确,因而我当即提交了一条评论,但愿其余读者能意识到这一点。async
而后陆续有几位redditor回答并质疑了个人观点,进而演变成了一场漫长而很是有见地的讨论。在讨论过程当中,我观察到了一些有趣的关于Activity的生命周期的现象:ide
几天以后,我正在访问一个潜在客户。该公司雇佣了一批富有经验的Android开发人员,他们尝试在老的代码库里添加新功能,这不是一件容易的事。函数
在快速代码审查期间,我注意到其中一位维护人员决定在Activity的onCreate(Bundle)中订阅EventBus,并在onDestroy()中取消订阅。这样作会带来很大的麻烦,因此我建议他们重构这部分代码。动画
上述两件事让我意识到仍是有许多android开发人员对Activity的生命周期充满困惑。如今,我想尝试经过分享个人经验来给其余开发人员提供便利。
我不打算在这里为Activity的生命周期从新编写一份文档,这代价太大了。我会告诉你如何在Activity生命周期的各个方法之间划分逻辑以实现最简单的设计并避免最多见的陷阱。
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)中没有如下内容:
每当我须要决定某一段逻辑是否应该放在onCreate(Bundle)方法里时,我就会问本身这个问题:这段逻辑与Activity的初始化有关吗?若是答案是否认的,我就会另寻别处。
当Android framework建立了一个Activity实例,并且它的onCreate(Bundle) 方法执行完毕以后,这个Activity就会处于已建立状态。
处于已建立状态的Activity 并不会触发资源分配,也不会接收到系统里其余对象发出的事件。从这种意义上来说,“created”状态是一种已经准备好了,可是不活跃和隔离的状态。
为了使Activity可以与用户交互,Android framework接着会调用Activity的onStart()方法使其处于活跃状态。onStart() 方法中的一些基本的逻辑处理包括:
对于第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()方法。
我搜索了个人一个项目的代码库,发现有32个onStart()方法被覆盖重写,平均每段代码约5行,一共有大约150多行代码。相反,只有2个onResume()方法被覆盖重写,一共8行代码,这8行代码主要是用于恢复播放动画和视频。
这总结了我对onResume()的见解:它只能用于在屏幕上启动或恢复一些动态的对象。你想在onResume()而不是onStart()中作这件事的缘由将在稍后的onPause()部分讨论。
在Android framework调用完onResume()方法后,Activity将从已启动状态转换为"resumed"状态。在此状态下,用户能够与它进行交互。
在这个方法里,您应该暂停或中止在onResume()中恢复或启动的动画和视频。就像onResume()同样,你几乎不须要重写onPause()。在onPause()方法而不是onStop()方法中处理动画的缘由是由于当Activity被部分隐藏(好比,弹出系统对话框)或者是在多窗口模式下失去焦点的时候,只有onPause()方法会被调用。所以若是你想在 只有用户正在与Activity交互的状况下播放动画,同时避免在多窗口模式下分散用户注意力并延长电池寿命,onPause() 是你惟一能够信赖的选择。 这一结论的前提是你但愿你的动画或视频在用户进入多窗口模式时中止播放,若是你但愿你的动画或视频在用户进入多窗口模式时继续播放,那么你不该该在onPause()中暂停它。在这种状况下,你须要将onResume()/ onPause()中的逻辑移动到onStart()/ onStop()。
一种特殊的状况是相机的使用,因为相机是全部应用程序共享的单一资源,所以一般您会想要在onPause()方法中释放它。
在Android framework调用完onPause()方法后,Activity将从"resumed"状态转换为已启动状态。
在这个方法里,您将注销全部观察者和监听者,并释放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框架的不合理设计,像这个问题中所描述的奇怪场景是可能会发生的。有几种方法能够解决这个问题,可是从onStop()中注销View的事件监听器是最干净的一种。
或者,您能够彻底忽略这种罕见的状况。这是大多数Android开发人员所作的,可是,您的用户偶尔会遇到一些奇怪的行为和崩溃。
在Android framework调用完onStop()方法后,Activity将从已启动状态转换为已建立状态。
永远都不要覆盖重写这个方法。
我搜索了我全部的项目代码库,没有一个地方须要我重写onDestroy()方法。
若是你思考一下我前面对onCreate(Bundle)的职责的描述,你会发现这是彻底合理的,它仅仅执行了初始化Activity对象的操做,因此彻底没有必要手动完成清理工做。
当我在查看新的Android代码库时,我一般会搜索几个常见的错误模式,这使我可以快速地了解代码的质量。 onDestroy()的覆盖重写是我寻找的第一个错误模式。
此方法用于保存一些临时的状态,在这个方法里你位移须要作的就是将你想保存的状态存入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的使用来讲是最安全的。
我不认为这是巧合。