原文链接 :UI Updates in a Multi-Window World
原文作者 : Kristin Marsicano
译文出自 : http://blog.csdn.net/zhulove86
译者 : Tony Zhu
截至目前为止,在activity前台的生命周期中,通常的做法是启动和取消UI的更新。然而,随着多窗口在android7.0 Nougat中的使用,可见生命周期不再像以前那样等同于前台的生命周期。反过来,当activity处在可见和后台的时候,你应该仔细考虑你的UI应该做什么,而不是将他们视为是同一个。
这篇文章将强调在android7.0 Nougat(及以后)的设备中更新UI,同时提供流畅用户体验的注意事项。
activity状态:简要的回顾
如果你还不熟悉activity的状态和生命周期的回调函数,或即使你了解,可以看看我在360AnDev 2016中深入activity声明周期的阐述。
actvity的生命周期依赖三个事项:
- 在内存中是否存在activity的实例?
- activity的界面对于用户是否可见?
- activity在前台是否为active ?
activity的四种状态是:
状态(State) | 是否在内存中?(In memory?) | 是否可见(Visible to user?) | 是否在前台(In foreground?) |
---|---|---|---|
不存在(Non-Existent) | 否 | 否 | 否 |
停止(Stopped) | 是 | 否 | 否 |
暂定(Paused) | 是 | 是/部分可见 | 否 |
运行(Running,又叫做Resumed,Active) | 是 | 是 | 是 |
Non-Existent 标识activity还没有启动起来,或者activity已经释放了(比如,用户按了返回键)。内存中没有对应的实例的对象,并且相应的没有用户可见或者可交互的界面;
Paused 标识activity的界面可见,或者部分可见,但是acitvity在前台不是active。一个activity可能是部分可见,举例,如果用户在屏幕上面启动了一个新的对话框主题或者透明的acitvity。一个acitvity也可以是完全可见的,但是不在前台,如果用户在多窗口模式下观看两个单独的活动。
Stopped 标识在内存中存在activity的实例化对象,但是activiy对应的界面是在屏幕上是不可见的。这种状态是个过渡状态,当activity第一次启动的时候,并且activity的界面是不可见的(比如,当用户在前台启动了另外一个全屏的activity,点击home键,或者在总览画面overview screen中切换任务)。
Running 标识activity在内存中有实例化对象,全部可见,并且activity界面在前台。这是当前用户交互的activity。在任何给定的时间只有一个activity在整个系统中可以运行。这就意味着如果一个activity切换到running状态,另外一个activity可能切换到其他状态。
android操作系统调用的方法,被称为生命周期回调,activity对象通知开发者activity的状态正在改变。onStop()方法就是这样的生命周期回调。
onStop()意味着activity正在从屏幕界面中消失。
当activity的界面从用户的视野中消失的时候,android操作系统调用onStop()。一旦onStop()执行完,这个activity就出在stopped状态。
onStop() 表示activity可见生命周期(用户可以看见activity界面的时间段)的结束。与此对应的,onStart(),表示activity可见生命周期的开始。你可以假定在onStart()和onStop()之间任何callback的调用,acitvity的界面是部分或者全部可见的。
知道了当你的acitvity对于用户不再可见是重要的。对于android开发者,我们希望为用户提供最佳的用户体验,同时最大限度的较少资源的使用。举个例子,一个app在显示本地数据。轮询本地的消耗功率。越准确,越频繁,需要更多的能量。如果用户无法看到你的应用,你是否还需要轮询这些信息。这取决于你的要求,但是你理应作为开发者需要考虑在activity处于stopped状态的时候,你可以减少或者完全停止哪些工作。
在android 7.0 Nougat中onStop()是停止UI相关更新的位置
android 7.0 Nougat早期的版本,在paused并且完全可见的场景中activity消耗的时间很少。paused和完全可见的场景出现在acitvity启动的时候。在这种情况下,当activity很快的切换到running(亦称为resumed)状态的时候,这种场景短暂地持续。在大多数情况下,当界面部分可见的时候(例如,当一个透明或者比屏幕略小的activity在屏幕上启动了),Nougat之前版本的activity处于paused状态。
正因为如此,常见的做法是使用onResume()和onPause()创建或者移除任何和用户界面相关的更新。假如在activity处于paused状态的时候,你不需要更新你的UI,只有当activity处于前台的时候才更新UI。换句话说,前台生命周期(见上图)大致相当于可见的生命周期。
然而,随着在Nougat中多窗口的出现,activity发现自己在paused和全部可见状态的时间较长。这就意味着,用户看到你paused状态的activity,并排的窗口中有另外一个正在运行的activity。记住,在同一时间仅有一个activity可以运行,因此当用户和窗口左侧的activity进行交互,这个activity就出现在前台,与此同时窗口右侧的activity就不能运行处于paused状态。
由于你的activity的界面完全可见,用户仍然希望你的app可以运行(比如,更行数据,视频播放等),即时它不在前台。在Nougat系统,那么你的activity在onStart()和onStop()的整个生命周期中都应该更新UI。
以视频为例。你有一个旧系统的app可以实现简单的视频回放。你在onResume()中启动或者恢复视频回放,在onPause()中暂停视频回放。直到现在这种方法都有效。
然而,在多窗口模式中,你的用户开始抱怨,因为他们希望观看视频,同时在另外一个窗口中发文字消息,但每当他们与第二个窗口中另外一个app交互的时候,你的app将停止播放视频。通过将视频回放的恢复和暂停移动到onStart()和onStop()中,你可以解决问题。对于实时的数据更新也会产生类似的问题,就像gallery app刷新显示新的图片,同时这些图被推送到Flickr。
onStop()不再懒惰
不过在你盲目的移除所有的UI安装和修改代码之前,提醒一句。在Nougat之前的版本,onStop()是一个惰性的操作。它不能保证当activity不可见的时候能尽快调用onStop()。相反,它可能在一个很短的时间之后发生。
在Nougat和之后的版本,可以保证acitivity不可见的时候可以尽快地调用onStop()。
关于我们的视频例子,如果你将暂定或者停止播放的代码移到onStop(),当用户按了返回键,在Nougat之前的版本中可能会听到声音继续在播放,即使视频不再是可见的。
那么,你该怎么办?为了确保在Nougat之前版本和Nougat的设备上有正确的用户体验,增加条件语句解决像视频播放这样的问题。将整洁的代码(在这种情况下,停止视频播放)放在onPause()和onStop()中,使用条件检测来控制
,如这段ExoPlayer演示代码段所示。
...
@Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
onHidden();
}
}
@Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
onHidden();
}
}
...
并不是所有的场景都像视频播放一样极端,在涉及Nougat之前版本中通过这种显示的方法配置。在许多情况下,总是停止你的UI更新才好,而不是在onPause()和onStop()中条件的执行更新。
零星的想法
决定在每个activity生命周期方法中做什么需要考虑:
- 你应用的需求
- 平衡资源使用和用户体验
activity的状态如何对应用户可以看到和操作到的
状态和它们的意义并没有改变,但多窗口增加部分呈现了一个罕见的状态(paused和全部可见状态延长的一段时间)更普遍了。幸运的是,当平台衍变而新增加类似多窗口功能的时候,acitvity生命周期深入的理解有助于你思考如何更新你的代码(如果有的话)。