天天看点

Android Activity 启动模式和生命周期笔记

        官方文档地址:http://developer.android.com/guide/components/tasks-and-back-stack.html

        一个应用程序通常包含多个Activity,每个Activity被设计用来让用户执行特定的action,并且能够启动其它的Activity。比如,一个邮件应用中可能会包含一个用于展示邮件列表的Activity,而当用户点击了其中某一封邮件的时候,就会打开另外一个Activity来显示该封邮件的具体内容。

        一个Activity甚至能启动设备中其它应用程序中的Activity。比如,如果你的应用希望去发送一封邮件,你就可以定义一个Intent去执行发送action,并且传入一些数据,如对方邮箱地址、邮件内容等。其它应用程序中的一个声明自己可以响应这种Intent的Activity随即就会被打开。在这种情景下,这个Intent是为了要发送邮件的,所以说邮件应用程序当中的“compose”Activity就被打开了(如果有多个Activity支持相同的Intent,那么系统会让用户选择一个使用)。当邮件发送出去之后,你的应用程序会继续,看起来就像刚才那个编写邮件的Activity就是你的应用程序中的一部分一样。即便多个Activity可能来自于不同的应用程序,Android也可以通过保持这些Activity在相同的任务(Task)中来保证无缝的用户体验。

        一个任务是用户要执行某些工作时要交互的Activity的集合,这些Activity被组织在一个栈(Stack)中,这个栈又被称为返回栈(the back stack),栈中Activity的顺序就是按照它们被打开的顺序依次存放的。

        手机的Home界面是大多数任务开始的地方,当用户在Home界面上点击了一个应用的图标时(或Home界面的一个快捷方式时),这个应用的任务就会被转移到前台。如果这个应用目前并没有任何一个任务的话(说明这个应用最近没有被启动过),系统就会去创建一个新的任务,该应用的主Activity将作为栈的根Activity(root activity)打开。

        当当前Activity启动了另外一个Activity的时候,新的Activity就会被放置到返回栈的栈顶并将获得焦点。前一个Activity仍然保留在返回栈当中,但会处于停止状态。当一个Activity停止时,系统会保留它的用户界面的当前状态。当用户按下Back键的时候,当前Activity被从栈顶弹出(该Activity被销毁),前一个Activity将继续(其之前的UI状态将被恢复)。返回栈中Activity的顺序永远不会改变,只能被压入(pushed)、被弹出(popped)——被当前Activity启动时被压入,当用户按返回键离开它时被弹出。因此,返回栈是一个典型的后进先出(last in, first out)的数据结构。下图通过时间轴的方式展示了多个Activity在返回栈当中的状态变化:

Android Activity 启动模式和生命周期笔记

       如果用户继续按Back键,返回栈中的Activity会一个个地被弹出并露出前一个,直到最终返回到Home界面(或者返回到该任务开始时正在运行的那个Activity)。当返回栈中所有的Activity都被弹出时,任务就不存在了。

        刚看到老郭前几个月翻译了,翻译的也不错,我就不继续翻译了,参见:Android任务和返回栈完全解析,细数那些你所不知道的细节。本来正在详细地看英文文档,顺便翻译一下,严格按照原文翻译。老郭翻译时做了自己的加工,我这个强迫症只能去看英文文档了。

Activity的亲和性(taskAffinity):

affinity用于指定一个Activity更加愿意依附于哪一个task。默认情况下,同一个应用程序中的所有Activity都具有相同的taskAffinity,就是程序的包名。

Activity的allowTaskReparenting:

表明该Activity可以转移到具有相同affinity的task中。

Activity的alwaysRetainTaskState:

设置为true时,来保证任务在后台太久后,Task最底层之上的Activity不会被清理掉。

Activity的clearTaskOnLaunch:

设置为true时,只要用户离开当前任务,在返回时,最底层之上的所有Activity都会被清理掉。

Activity的finishOnTaskLaunch:

设置为true时,只要用户离开当前任务,在返回时,该Activity会被清理掉。

Activity的启动模式(launchMode):

standard:默认的启动模式。这种启动模式表示每次启动该Activity时系统都会为其创建一个新的实例,并且总会把它放入到当前的任务当中。声明成这种启动模式的Activity可以被实例化多次,一个任务当中也可以包含多个这种Activity的实例。

singleTop:如果要启动的Activity实例已经当前task的栈顶,系统不会重新创建Activity的实例,而是调用它的onNewIntent()方法把intent传给它。

singleTask:先在系统中查找属性值affinity等于它的taskAffinity的任务是否存在,如果存在这样的任务,它就会在这个任务中启动,直接调用它的onNewIntent()方法把intent传给它,而且它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。否则不存在这样affinity的任务,就为它创建一个新的task,实例化该Activity并把它放入新task的根。

singleInstance:系统不会向保持该Activity实例的task中添加任何其它Activity,这个Activity实例始终是它所在task中的唯一成员,它所启动的其它Activity都将在别的task中打开。也就是说:singleInstance的Activity所在的Task不允许存在其他Activity,任何从该Activity加载的其它Actiivty(假设为Activity2)都会被放入其它的Task中,如果存在与Activity2相同affinity的Task,则在该Task内创建Activity2。如果不存在,则重新生成新的Task并入栈。

Intent的flag:

在调用startActivity()方法的时候,可以为Intent加入一个flag来改变Activity与任务的关联方式。

FLAG_ACTIVITY_NEW_TASK:

新启动的其它应用的Activity就会被放置到一个新的任务当中。这个flag的作用通常是模拟一种Launcher的行为,即列出一推可以启动的东西,但启动的每一个Activity都是在运行在自己独立的任务当中的。

FLAG_ACTIVITY_SINGLE_TOP:

如果要启动的Activity在当前任务中已经存在了,并且还处于栈顶的位置,那么就不会再次创建这个Activity的实例,而是直接调用它的onNewIntent()方法把intent传给它。这种flag和在launchMode中指定"singleTop"模式所实现的效果是一样的。

FLAG_ACTIVITY_CLEAR_TOP:

如果要启动的Activity在当前任务中已经存在了,就不会再次创建这个Activity的实例,而是会把这个Activity之上的所有Activity全部清理掉。比如说,一个任务当中有A、B、C、D四个Activity,然后D调用了startActivity()方法来启动B,并将flag指定成FLAG_ACTIVITY_CLEAR_TOP,那么此时C和D就会被关闭掉,现在返回栈中就只剩下A和B了。

那么此时Activity B会接收到这个启动它的Intent,你可以决定是让Activity B调用onNewIntent()方法(不会创建新的实例),还是将Activity B销毁掉并重新创建实例。如果Activity B没有在manifest中指定任何启动模式(也就是默认的"standard"模式),并且Intent中也没有加入一个FLAG_ACTIVITY_SINGLE_TOP,那么此时Activity B就会销毁掉,然后重新创建实例。而如果Activity B在manifest中指定了任何一种启动模式,或者是在Intent中加入了一个FLAG_ACTIVITY_SINGLE_TOP,那么就会调用Activity B的onNewIntent()方法。

FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK结合在一起使用也会有比较好的效果,比如可以将一个后台运行的任务切换到前台,并把目标Activity之上的其它Activity全部关闭掉。这个功能在某些情况下非常有用,比如说从通知栏启动Activity的时候。

        不管Activity是在一个新任务当中启动,还是在当前任务中启动,返回键永远都会把我们带回到之前的一个Activity中,这就是用户体验。但是有一种情况是比较特殊的,就是如果Activity指定了启动模式是"singleTask",并且启动的是另外一个应用程序中的Activity,这个时候当发现该Activity正好处于一个后台任务当中的话,就会直接将这整个后台任务一起切换到前台。此时按下返回键会优先将目前最前台的任务(刚刚从后台切换到最前台)进行回退,而不会直接回到原来task中来的那个Activity。

        最近使用百度云推送的SDK时,碰到了些问题,所以认认真真地看了一下任务和返回栈。记得当时做过一个笔试题,就问Activity的4种启动模式,我每个就一句话解释(当时只是了解,没有深入研究过),也没提到affinity。

       关于生命周期的总结:

Android Activity 启动模式和生命周期笔记

Activity 等组件的实例的创建和销毁是由操作系统或者运行在你的进程中的 framework 代码负责的,我们不能手动创建和销毁,而在这个过程中各种状态可以通过相关回调方法被调用,而 onCreate 、onStop、onDestroy 等等这些方法也被称为生命周期方法,我们必须尊重这些方法,否则很容易导致内存泄漏或者程序崩溃。

Activity 中的 super.onStart 会触发 其 Fragment 的 onActivityCreated 和 onStart,而 Activity 中的 super.onPostResume 会触发 onResumeFragments,然后 super.onResumeFragments 会 触发 Fragment 的 onResume。

Activity 跳转时,首先当前 Activity onPause,然后直到下一个 Activity onResume 当前 Activity 才会 onStop。

正常情况下只要用户按了返回键退出当前 Activity 或者 代码中强制 finish 了 Activity,就表明退出 Activity ,系统会负责销毁和资源回收等工作。但只要 Activity 对用户不可见,那么该 Activity 就可能随时被非正常销毁,比如当前内存不足,系统需要强制清除在后台中的 Activity ,或者 Activity 长期处于后台而系统为了节省手机资源强行杀掉,这些情况是在未经过用户同意的情况下进行的,所以当用户再次回到这些 Activity 的时候系统应该重建这些 Activity,而系统为了保存状态和恢复状态提供了两个方法:onSaveInstanceState 和 onRestoreInstanceState,为了确保在意外销毁前能够保存状态,用来保存状态的方法 onSaveInstanceState 可能在 onDestroy 前随时被调用,如用户按下 HOME 键时;长按 HOME 键,选择运行其他的程序时;按下电源按键(关闭屏幕显示)时;启动一个新的 Activity 时;系统配置发生变化(切换屏幕方向、语言等)时。Activity 的 onSaveInstanceState 方法至少会保存视图树的状态和 Fragment 的状态。而恢复状态时可以在新的 Activity 实例 创建时的 onCreate(Bundle savedInstanceState) 回调中拿到之前保存的实例状态,你可以用这个去恢复状态,或者在 onStart() 之后的 onRestoreInstanceState(Bundle savedInstanceState) 方法中恢复状态。

在 onSaveInstanceState 被调用之后在 onCreate 被调用之前的一些操作是不被允许的,因为这些操作是在状态保存之后恢复之前发生的,可能会产生一系列问题。如在 onSaveInstanceState 保存了状态(包括 dialog 是否正在显示的状态, fragment 的当前堆栈状态, 以及列表当前滚动位置等View状态)后,如果再 commit 一个 FragmentTransaction,那么这个这个事务将不会被系统记录,在系统要恢复 Activity 状态时由于该该事务没被系统记录,因此产生了丢失,造成了意外的 UI 状态丢失,而 Android 系统为了不惜一切代价避免状态丢失,直接抛出 IllegalStateException: Can not perform this action after onSaveInstanceState 这样的 IllegalStateException 异常。要避免这样的异常需要我们在进行这些操作时格外注意:

  • 在 Activity 的生命周期方法中提交事务要格外小心: 在 onCreate() 方法中提交事务是没有问题的,因为这里可以根据保存的状态重建,但如果在像 onActivityResult()、onStart()、onResume() 等生命周期方法中提交事务就可能会出问题了,因为这些方法很可能在 Activity 状态被恢复之前被调用,FragmentActivity#onResume() 方法中会分发其关联的 Fragment 的 onResume(),不过 Fragment 并没有真正的 resumed,也就是说,此时之前的状态依然在保存着,不允许提交 Fragment 事务更改状态,而 onResumeFragments() 方法才是真正的 onResume(),在这个方法中所有的 Fragment 才真正的 resumed。所以如果确实需要在 onCreate() 方法之外的生命周期方法中提交 Fragment 事务,那就在 FragmentActivity#onResumeFragments() 或 Activity#onPostResume() 方法中提交事务,因为这两个方法中已经确保了 Activity 的状态已经被完全恢复了。
  • 避免在异步的回调中提交事务: 因为在这些回调执行的时候很难确定当前 Activity 正处于什么生命周期状态,而且突然地提交事务更改大量 UI 会产生糟糕的用户体验,所以如果遇到这样的场景可以考虑换一种实现思路,万不得已不要使用 commitAllowingStateLoss() 方法。

在生命周期的各个方法中管理 UI 和 各个组件的行为会让代码变得很难维护,如大量的广播注册和注销,监听器的开启与关闭,甚至有时候我们无法保证在 onStart() 中的代码在 onStop() 中的代码执行之前执行。所以需要一个额外的类去监听和处理 Activity 和 Fragment 生命周期的变化,而使用观察者模式或者手动添加一个 non-gui 的 Fragment 是个不错的选择。