再谈Activity生命周期
开发过Android应用程序的猿们应该都知道Activity,我也接触Android一年多了,期间看过N多关于Activity生命周期的博客,甚至源码,但是对它的生命周期总是容易遗忘一些细节,今天再重新总结一下。
本文重点有两个:
1、 onSaveInstanceState(Bundle)方法的理解,即它究竟会在何时得到执行;
2、 当从ActivityA启动ActivityB时,如果ActivityB界面中存在“浑浊”区域时,即ActivityA部分界面仍然可见时,将不会执行到onStop方法,什么时候判断它是浑浊的?
还是先来一张经典的官方图片,先说说我的理解:
Activity启动时,首先执行onCreate()方法,在onCreate中我们需要完成界面布局加载、控件实例化、数据绑定等操作;之后onStart方法将得到执行,onStart方法执行的时机官方描述是:此时,Activity将要显示而又没有显示时执行onStart。怎么理解呢?就是显示界面所需数据(包括各种资源、控件大小位置)均已计算完成,执行完onStart后就立即绘制界面。界面绘制完成后,就会执行onResume方法,官方对onResume执行时机的描述是:界面已经完全显示,但是还不能实现与用户的交互。因此,当onResume执行完成后,就可以实现用户操作了。此时,activity位于任务栈的顶端,任何情况下,系统都不会主动关闭这个activity。前面介绍的onCreate、onStart、onResume三个方法,我们使用最多,执行的操作最多的就是onCreate了,至于其余两个方法,就看自己的需求了,我们可以在里面去实例化控件、绑定数据源,也可以什么都不做。 这部分比较简单,后面将出现多种情况,我们一一分析:
1、 ActivityA在任务栈顶端运行时,当有其他ActivityB被启动时,Activity将失去任务栈顶端位置,进入后台运行,此时,onPause方法将会得到执行。注意到onPause方法后面还有一个onStop方法,官方说,当“the activity is no longer visible”的时候会执行onStop方法,当ActivityB中存在透明区域时,即我们看屏幕时仍然然能看到ActivityA的部分界面时,onStop方法就不会执行。(这里我们先不讨论这究竟是一种什么状态,后面我会详细说明) 现在我们假设onStop没有得到执行,只执行了onPause,此时如果用户再次导航到ActivityA,那么onResume方法将得到执行,我们可以继续操作ActivityA了;如果onStop执行过,那么用户重新导航到ActivityA时,经历的过程就是onRestartàonStartàonResume;
2、 ActivityA在任务栈顶端运行时,用户点击返回键退出,此时执行的过程比较简单,就是onPauseàonStopàonDestory.
3、 ActivityA因为各种原因进入后台以后,不再拥有最高优先级,当系统内存告急时,系统可能就会干掉ActivityA。这里有两点我们需要注意,其实不怕被干掉,干掉了我重新恢复就行了,那么我们怎么才能恢复呢?第一,activity只有进入后台才有可能被系统干掉,那么activity进入后台必定会执行onPause方法,实际上,activity任何时候离开任务栈都必定会执行onPause方法,因此,我们在这里面进行数据的持久化保存,是安全可恢复的;另外,activity还有一个方法叫做onSaveInstanceState(Bundle),这里的这个Bundle包含有可用于恢复activity的全部数据,系统对其进行持久化保存,在activity下次启动时会传递给onCreate和onRestoreInstanceState方法。但是onSaveInstanceState的具体执行时机,官方说法是当系统要干掉activity之前调用它,我们很难判断出究竟何时执行它,网上也有很多讨论,下面我来谈谈我的理解。
onSaveInstanceState究竟会在何时得到执行?
官方API文档说了很大一长串,很费解,于是我参考了网上的一些说法,比如这篇博客(http://www.cnblogs.com/perfy/p/3820491.html)里面就说了:“onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则 onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。”似乎有些理解了,但是我还是不能够确切的知道onSaveInstanceState究竟会在何时得到执行,强迫症的我只有继续找答案,最后我终于注意到官方API文档最后一句说“If called, this method will occur before onStop().There are no guarantees about whether it will occur before or after onPause().”,这句话一下就提醒了我,因为只要任务栈顶端activity界面不浑浊(对浑浊的详细理解见后面),那么onPause和onStop都会得到执行,而onPause是只要activity进入后台就一定会执行;结合之前博客看到的“未经你许可”思想,那么只要没有调用Activity的finish方法(查看源码知道,点击返回键实际上也是调用的finish()方法),都是未经许可了,都会在onStop之前调用它。这下好办了:
a、 当按下Home键,离开了activity,但是我没有调用到finish方法,所以onSaveInstanceState会执行;
b、 当我从ActivityA启动ActivityB,A进入了后台,我没有调用finish方法,所以onSaveInstanceState会执行;
c、 屏幕横竖屏切换时、按电源键关闭屏幕是,我都没有调用finish方法,所以onSaveInstanceState会执行;
d、 当用户点击返回键,或者点击界面的退出功能按钮,直接或间接地调用了finish方法,所以onSaveInstanceState会执行;
所以总结下,android把自己说得太聪明了,它哪里知道后面会杀死这个activity,他不过就是把所有未退出的Activity的状态都进行保存,内存低的时候直接杀掉这个activity就行啦,才不会去调用onSaveInstanceState。 所以这也就引出一点,onSaveInstanceState方法和 onRestoreInstanceState方法虽然看着是一对兄弟,很亲密,但是他们“不一定”是成对的被调用的,onRestoreInstanceState被调用的前提是activity A“确实”被系统销毁了。
界面何时浑浊?
之前看到这里的时候,心里面就很敬佩Android,你想,当从activityA启动一个ActivityB的时候,如果ActivityB存在哪怕一丁点透明的地方,系统都能知道它“浑浊了”,ActivityA有部分可见那么就不能执行它的onStop方法?这个问题一直在我的脑海中,因此,今天我进行了测试。我弄了两个activity,A和B ,我把B的布局文件背景设置为android:background="@android:color/transparent",心想,我直接让他透明不就得了。就这样我进行了测试,当我看见结果时,我呆了,怎么B没有透明啊,白色的。。。这不科学。 于是我继续查阅了资料,这才发现其中的奥秘。 其实我们每个Activity的界面显示都是由一个就叫做window的对象管理着的,我们在布局文件里面设置了透明,只是使得这个window对象的内容透明了,但是window本身默认是不透明的,所以进一步测试我发现,android系统判断界面是否浑浊和我们能不能看见activityA一点关系也没有,只要我们把window设置为透明的,即使真正现实的界面布局哪怕一丁点缝隙不留,使得ActivityA完全不可见,onStop仍然不会执行,它判断的唯一依据就是这个window对象是否透明,那么怎么设置window透明呢?
这里给出两个方法(见博客http://blog.sina.com.cn/s/blog_4b53da1c01010nxe.html):
1.使用android内置的透明样式:
在AndroidManifest.xml中的<activity>标签中添加
android:theme="@android:style/Theme.Translucent"
其中,android:style/Theme.Translucent是android内置的透明样式
2.使用自定义的透明样式:
(1).res/values下建colors.xml文件:
<?xmlversion="1.0"encoding="UTF-8"?>
<resources>
<colorname="transparent">#9000</color>
</resources>
这个值设定了整个界面的透明度,为了看得见效果,现在设为透明度为56%(9/16)左右。
(2).res/values/下建styles.xml:
<?xmlversion="1.0"encoding="utf-8"?>
<resources>
<stylename="Transparent">
<itemname="android:windowBackground">@color/transparent</item>
<itemname="android:windowIsTranslucent">true</item>
<itemname="android:windowAnimationStyle">@+android:style/Animation.Translucent</item>
</style>
</resources>
(3).设置andrdoi:theme:
在AndroidManifest.xml中的任意<activity>标签中添加
android:theme="@style/transparent"
后注:android:theme有很多使用方法,可以设置Activity为多种类型,如此类的透明背景,无标题栏,显示为对话框模式,桌面背景等,后面会有专门内容介绍。
以上内容都是经过我的测试的,为避免篇幅过长,具体测试代码,见Github仓库:https://github.com/BBigBoy/AndroidBaseTest
任何疑问,请直接回复联系。