Android 基础 -- 生命周期和启动模式实践总结

源自:http://imxie.itscoder.com/2016/07/21/Activity-lifecycle-launchmode/

http://www.xueandroid.com/


Activity / Fragment 的生命周期是每个 Android 开发者最最基础的知识点。所以特别有必要自己整理一番。总看别人博客和书上的死知识,还不如自己动手实践,然后输出要印象深刻,理解透彻。



Activity 生命周期
正常情况下的生命周期分析
异常状态下的生命周期
Fragment
普通的 Fragment
ViewPager 中的 Fragment
启动模式
Activity 的四种启动模式
具体实践
参考文档
Activity 生命周期


正常情况下的生命周期分析






针对一个特定的 Activity ,第一次启动,回调如下:onCreate —-> onStart —-> onResume


Log 日志
D/KLog: (MainActivity.java:19) onCreate
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume


切换回到桌面的时候,回调如下:onPause —-> onStop


Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (MainActivity.java:68) onStop


Back 键退出的话,最后会 onDestroy


启动一个新的 Activity , 我们看看两个 Activity 的生命周期:


Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (OtherActivity.java:25) onCreate
D/KLog: (OtherActivity.java:31) onStart
D/KLog: (OtherActivity.java:49) onResume
D/KLog: (MainActivity.java:68) onStop
可以得到顺序是:onPause(A) —-> onCreate(B) —-> onStart(B) —-> onResume(B) —-> onStop(A)


这个时候我们 Back 回到第一个 Activity 时发生的回调:


Log 日志
D/KLog: (OtherActivity.java:37) onPause
D/KLog: (MainActivity.java:56) onRestart
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume
D/KLog: (OtherActivity.java:55) onStop
D/KLog: (OtherActivity.java:61) onDestroy
可以得到顺序是: onPause(B) —-> onRestart(A) —-> onStart(A) —-> onResume(A) —-> onStop(B) —-> onDestroy(B)


如果我在 B Activity 中的 onCreate 回调中直接 finish():


Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (OtherActivity.java:25) onCreate
D/KLog: (MainActivity.java:62) onResume
D/KLog: (OtherActivity.java:62) onDestroy
我们发现 B Activity 只会执行 onCreate 和 onDestroy。


接下来我们启动一个特殊的 Activity (半透明或者对话框样式)到关闭它:


Log 日志
D/MainActivity: onPause
D/DialogActivity: onCreate
D/DialogActivity: onStart
D/DialogActivity: onResume
D/DialogActivity: onPause
D/MainActivity: onResume
D/DialogActivity: onStop
D/DialogActivity: onDestroy


在正常使用应用的过程中,前台 Activity 有时会被其他导致 Activity 暂停的可视组件阻挡。 例如,当半透明 Activity 打开时(比如对话框样式中的 Activity ),上一个 Activity 会暂停。 只要 Activity 仍然部分可见但目前又未处于焦点之中,它会一直暂停。






问题:如果是启动一个普通的 Dialog ,Activity 的会执行 onPause 吗?
答案是不会的,我们可以这样理解,普通的 dialog 是依附在本 Activity 的,相当于是一个整体,所以它本身的焦点也是属于 Activity 的。故不会调用 onPause 。


异常状态下的生命周期






onSaveInstanceState 方法只会出现在 Activity 被异常终止的情况下,它的调用时机是在 onStop 之前,它和 onPause 方法没有既定的时序关系,可能在它之前,也可能在它之后。当 Activity 被重新创建的时候, onRestoreInstanceState 会被回调,它的调用时机是 onStart 之后。
系统只会在 Activity 即将被销毁并且有机会重新显示的情况下才会去调用 onSaveInstanceState 方法。
当 Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前 Activity 的视图结构,并且在 Activity 重启后为我们恢复这些数据,比如文本框中用户输入的数据、listview 滚动的位置等,这些 view 相关的状态系统都会默认为我们恢复。具体针对某一个 view 系统能为我们恢复哪些数据可以查看 view 的源码中的 onSaveInstanceState 和 onRestoreInstanceState 方法。


Demo 代码:


@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    KLog.d(getClass().getSimpleName(),"onSaveInstanceState");
    outState.putString(STATE, "test");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    KLog.d(getClass().getSimpleName(),"[onRestoreInstanceState]: " + savedInstanceState.getString(STATE));
}
为了方便我们旋转下屏幕来异常终止 Activity :


Log 日志
D/MainActivity: onPause
D/MainActivity: onSaveInstanceState
D/MainActivity: onStop
D/MainActivity: onDestroy
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: [onRestoreInstanceState]: test
D/MainActivity: onResume


摘自 Android 开发者艺术探索 一书:


关于保存和恢复 View 的层次结构,系统工作流程是: Activity 异常终止, Activity 调用 onSaveInstanceState 去保存数据,然后 Activity 会委托 Windows 去保存数据,接着 Window 再委托它上面的顶层容器去保存数据。顶层容器是一个 ViewGroup ,一般来说它很可能是 DectorView ,最后顶层容器再去通知它的子元素保存数据。(这是一种委托思想,上层委托下层,父容器委托子元素去处理事情,如 View 的绘制过程,事件分发都是采用类似的思想)


Fragment


普通的 Fragment










从图可以看出,Fragment 生命周期大部分的状态与 Activity 相似,特殊的是


onAttach() —— 当 Fragment 被加入到 Activity 时调用(在这个方法中可以获得所在的 Activity ).
onCreateView() —— 当 Activity 要得到 Fragment 的 Layout 时,调用此方法,Fragment 在其中创建自己的 Layout (界面)。
onActivityCreated() —— 当 Activity 的 onCreated() 方法返回后调用此方法
onDestroyView() —— 当 Fragment 中的视图被移除的时候,调用这个方法。
onDetach() —— 当 Fragment 和 Activity 分离的时候,调用这个方法。
ViewPager 中的 Fragment


我们开发中经常会用到 ViewPager + Fragment 组合的形式来完成特定的需求。本身 Fragment 生命周期就比 Activity 要复杂很多,当它在 ViewPager 中又是怎么回调呢?


我先给 ViewPager 加入三个 Fragment:


viewPager = (ViewPager) findViewById(R.id.viewpager);
fragmentList.add(new OneTextFragment());
fragmentList.add(new TwoTextFragment());
fragmentList.add(new ThreeTextFragment());
viewPager.setAdapter(new FtAdapter(getSupportFragmentManager(), fragmentList));
启动这个 Activity 的日志如下:


D/ViewPagerHostActivity: onCreate
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/OneTextFragment: onAttach
D/OneTextFragment: onCreate
D/TwoTextFragment: onAttach
D/TwoTextFragment: onCreate
D/TwoTextFragment: onActivityCreated
D/OneTextFragment: onActivityCreated
D/OneTextFragment: onStart
D/OneTextFragment: onResume
D/TwoTextFragment: onStart
D/TwoTextFragment: onResume
我们发现启动后,有两个 Fragment(one,two) 被创建,为什么会创建两个?这个问题我们留着后面说。


当 Activity 进入后台:


D/ViewPagerHostActivity: onPause
D/ViewPagerHostActivity: onSaveInstanceState
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop
当 Activity 返回前台:


D/ViewPagerHostActivity: onRestart
D/TwoTextFragment: onStart
D/OneTextFragment: onStart
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/TwoTextFragment: onResume
D/OneTextFragment: onResume
当 Activity 销毁:


D/ViewPagerHostActivity: onPause
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop
D/TwoTextFragment: onDestroyView
D/TwoTextFragment: onDestroy
D/TwoTextFragment: onDetach
D/OneTextFragment: onDestroyView
D/OneTextFragment: onDestroy
D/OneTextFragment: onDetach
D/ViewPagerHostActivity: onDestroy
滑动一页:


D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/ThreeTextFragment: onActivityCreated
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume
当前显示的页面是 TwoTextFragment 然后 ThreeTextFragment 也已经创建好了。


再滑动一页:


D/OneTextFragment: onStop
D/OneTextFragment: onDestroyView
当前显示的页面是 ThreeTextFragment ,我们发现 OneTextFragment 已经销毁。


我们可以得到默认状态下的 ViewPager 会缓存 1 个 Fragment,相当于有两个 Fragment 是创建状态。
当我们增加一行代码:


viewPager.setOffscreenPageLimit(2);
当 Activity 创建时的生命周期:


D/ViewPagerHostActivity: onCreate
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/OneTextFragment: onAttach
D/OneTextFragment: onCreate
D/TwoTextFragment: onAttach
D/TwoTextFragment: onCreate
D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/TwoTextFragment: onActivityCreated
D/OneTextFragment: onActivityCreated
D/OneTextFragment: onStart
D/OneTextFragment: onResume
D/TwoTextFragment: onStart
D/TwoTextFragment: onResume
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume
三个 Fragment 都创建好了,并且左右切换不会走任何生命周期(虽然是废话)。


setOffscreenPageLimit 源码:


public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
通过源码我们可以知道,ViewPager 的缓存的默认值和最小值是 1。


启动模式


Activity 的四种启动模式


Standard:标准模式,一调用 startActivity() 方法就会产生一个新的实例。


SingleTop: 来了 intent, 每次都创建新的实例,仅一个例外:当栈顶的activity 恰恰就是该activity的实例(即需要创建的实例)时,不再创建新实例。这解决了栈顶复用问题


SingleTask: 来了 intent 后,检查栈中是否存在该 activity的实例,如果存在就把 intent 发送给它,否则就创建一个新的该activity的实例,放入一个新的 task 栈的栈底。肯定位于一个 task 的栈底,而且栈中只能有它一个该 activity 实例,但允许其他 activity 加入该栈。解决了在一个 task 中共享一个 activity。


SingleInstance: 这个跟 SingleTask 基本上是一样,只有一个区别:在这个模式下的Activity实例所处的task中,只能有这个activity实例,不能有其他的实例。一旦该模式的activity的实例已经存在于某个栈中,任何应用在激活该activity时都会重用该栈中的实例,解决了多个task共享一个 activity。


这些启动模式可以在功能清单文件 AndroidManifest.xml 中进行设置,中的 launchMode 属性。


具体实践


SingleTop 栈顶复用模式
<activity android:name=".dLaunchChapter.OneActivity"
android:launchMode="singleTop"/>
我们在清单里先给 OneActivity 启动模式设置为 singleTop ,然后代码启动活动的顺序为 One --> One,反复点击多次,然后我们看看栈内情况。


adb 命令 :dumpsys activity | grep -i run


root@vbox86p:/ # dumpsys activity | grep -i run
    Running activities (most recent first):
        Run #1: ActivityRecord{23e3b5b u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t595}
        Run #0: ActivityRecord{1a2c6f3 u0 com.hugo.demo.activitydemo/.LaunchActivity t595}
该启动模式下并且 OneActivity 在栈顶所以不会创建新的实例,其生命周期调用 onPause —-> onNewIntent —-> onResume


SingleTask 栈内复用模式
修改 OneActivity 的启动模式为 SingleTask ,然后我们代码启动的顺序为 One —-> Two —-> One,接了下看看栈内情况:


One —-> Two 我们记录下当前的 Activity 栈:


Running activities (most recent first):
        Run #2: ActivityRecord{1e8701b7 u0 com.hugo.demo.activitydemo/.dLaunchChapter.TwoActivity t632}
        Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}
接下来我们执行 Two —-> One ,当前 Activity 栈信息:


Running activities (most recent first):
   Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}
当 TwoActivity 启动 OneActivity(SingleTask) 的时候,堆栈信息里只剩下了 OneActivity 并且和第一次内存信息 39e11719 相同,所以确实是复用了没有新建实例,接下来我们看看 Log 日志,再验证下我们的猜想,看看具体走了哪些生命周期:


D/TwoActivity: onPause
D/OneActivity: onNewIntent
D/OneActivity: onRestart
D/OneActivity: onStart
D/OneActivity: onResume
D/TwoActivity: onStop
D/TwoActivity: onDestroy
果然此时 OneActivity 没有重新创建,并且系统把它切换到了栈顶并调用 onNewIntent 方法,同时我们发现, SingleTask 默认具有 clearTop 效果,导致 TwoActivity 出栈。


我们代码指定 OneActivity 的栈,效果还是一样的吗?
带着问题我们修改下代码,增加一行:


<activity
android:name=".dLaunchChapter.OneActivity"
android:launchMode="singleTask"
android:taskAffinity="com.hugo.demo.singleTask"/>
然后我们按照刚刚顺序 One —-> Two —-> One 启动 Activity ,现在的栈内信息还会更上次一样吗?


Running activities (most recent first):
        Run #2: ActivityRecord{1bc18519 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t636}
        Run #1: ActivityRecord{36e5e368 u0 com.hugo.demo.activitydemo/.dLaunchChapter.TwoActivity t635}
我们发现,虽然是复用了 OneActivity 而且移到了栈顶,但是并没有销毁 TwoActivity 。


原因在于 singleTask 模式受 taskAffinity 影响,TwoActivity 和 OneActivity 所在的 Activity 栈不同。


总结,启动一个 lauchMode 为 singleTask 的 Activity 时有两种情况:


若系统中存在相同 taskAffinity 值的任务栈 (tacks1 )时,会把 task1 从后台调到前台,若实例存在则干掉其上面的所有 Activity 并调用 onNewInstance 方法重用,没有该实例则新建一个。
否则,新建一个任务栈,并以此 Activity 作为 root 。
SingleInstance 单实例模式
这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性以外,还加强了一点,就是具有此模式的 Activity 只能单独地位于任务栈。


好了,关于生命周期和启动模式实践+知识点整理已经完成啦,非常推荐大家下载源码自己运行看看 Log 日志,查看源码:Github,这样可以对这篇文章知识更加深刻。

猜你喜欢

转载自blog.csdn.net/watertekhqx/article/details/77604355