第1-3章 任务和后台栈
首先需要强调的是:所有activities都属于一个任务。一个任务包含一个或几个activities并可以让用户与之交互。任务能移到后台并保持每一个activity 的状态,这是为了让用户执行其他任务而不丢失先前的工作。
接下来让我们快速预览一下本章的内容
1. 保存Activity的状态
2. 任务管理
3. 定义启动模式
4. 处理共用性
5. 清空Back Stack
6. 开始一个任务
一个应用程序通常包括多个activities。每一个activity应该围绕一个具体的动作来设计,让用户能执行这个动作并且能启动其他activities。例如,一个email程序可能有一个activity用来显示新email的列表,当用户选择一个email时,一个新的activity打开用于查看email的相关内容。一个activity甚至可以启动设备上其他应用程序中的activities。例如,如果你的应用程序想要发送一个email,你能定义一个intent用来执行“send” 的动作并包括一些的类似email的地址和一个消息数据。这样一个不属于你应用程序中的activity也能被启动,因为有一个声明这个activity自己能处理这类intent的动作 。这种情况下,intent为了发送一个email,所以由一个email应用程序“compose(构成)”的activity被启动了。当email 被发送完后,你的activity便恢复了,这看起来就像你的应用程序集成了email 应用程序的功能一样很强大。所以android有这样一种机制在一个应用程序中来让用户无缝体验几个应用程序中的功能。
Activities按照一定顺序被排列到一个栈中,每一个activity是按照一定顺序被打开的。设备的Home屏幕是启动大部分任务的地方。当用户在应用程序启动器(或一个在Home屏幕上的快捷方式)触摸一个图标(icon),那这个应用程序就会成为前台程序。首先进入的是“main”activity ,当你启动一个新任务(新的activity)后,那么“main”activity是作为一个根部的activity存在于栈底。
当前activity启动另一个activity时,新的activity被推送到栈的顶部并获得焦点。先前的activity被保留在栈中,但处于已停止的状态。当一个activity停止时,系统会记住它当前的UI状态。当用户点击返回键后,当前的activity从栈顶取出来被destroyed掉,然后先前的acitivity被恢复了。在栈中的activities决不会重新排列,只有出栈与入栈的操作,当一个activity启动时,它执行了入栈操作,当用户点击返回键时,当前activity便执行了出栈操作。后台栈的操作依照的是“后进先出”的原则。图1-3-1直观化了后台栈的变化。
图1-3-1. 后台栈的变化
如果用户继续点击返回键,那么在栈中的每一个activity都会出栈,直到返回到Home屏幕。当所有activities都从栈中移除时,这个任务就不再存在了。
一个任务是一个内聚性单元,当用户开始一个新任务或点击Home键回到Home屏幕时,它能自己移动到后台。在后台时,任务中的所有activities都被停止了,但后台栈的任务是完整保持的,当另一个任务发生时,先前的任务仅仅是失去焦点而已,如图1-3-2显示的那样。
图1-3-2两个任务:当任务A在后台时,任务B负责在前台处理用户交互,任务A等待恢复.
用户能在任务离开的地方重新拾取另一个任务让它返回到前台。假设,例如当前任务(任务A)有三个activities在它的栈中,当前activity在栈顶。用户点击Home键后又启动了一个新的应用程序。当Home屏幕出现时,任务A已经在后台中。当心的应用程序启动时,系统为这个应用程序启动了一个任务(任务B)并且它拥有自己的栈。在与任务B的应用程序中交互后,用户又返回Home屏幕并再次选择了最初任务A所在的应用程序。现在,任务A又回到前台了,它栈中的三个activities是完整的,并且栈顶的activity被恢复了。此刻,用户也能通过在Home屏幕中选择的任务B所在的应用程序再次切换到任务B。这个例子就是Android的多任务处理。注意,虽然多个任务能保持在后台,但如果在同一时刻运行很多后台任务,那么系统为了恢复内存可能会摧毁掉一些后台activities。因为后台栈中的activities是不会重新排列的,如果你的应用程序允许从一个或多个acitivity中启动一个特定的activity,那么一个新的activity实例被创建,并且它被推送到栈顶。同样的一个acitvity在你的应用程序中可能被多次实例化,如图1-3-3所示。
图1-3-3 一个单一的activity被多次实例化
下面让我们总结一下activities和任务的默认行为:
1. 当Activity A启动Activity B时,Activity A被停止了,但系统会保持它的状态。如果用户在Activity B中点击返回键。那么Activity A恢复了它的状态。
2. 当用户通过Home键离开一个任务时,当前activity被停止了,但任务还在后台。系统保持任务中的每个activity的状态。如果用户后来通过点击icon图片恢复了任务,那么这个任务恢复到前台并且显示栈顶中的activity。
3. 如果用户点击返回键,当前activity出栈并被摧毁。栈中先前的activity被恢复了。当一个activity被摧毁(destroyed)时,系统不会保持此activity的状态。
4. Activities能被多次实例化,并且还可以从其他任务中多次实例化。
1-3.1 保存Activity的状态
如上所述,我们知道是当activity停止时,系统默认保存它的状态的。当用户向后导航时,它会回到先前activity的状态,包括它上次离开时的UI状态等。然而你也能主动的通过activity的回调方法来保持activities的状态,这种情况用于acitvity被摧毁后又重新创建的时候。我们也说过某些极端的情况下如果系统内存不够了,需要恢复些内存,那么系统就会自动摧毁你的activity。当发生这种情况时,activity的状态信息丢失了,系统可能下次会重新创建它。但为了避免这种丢失用户数据的情况发生,我们应该在自己的activity中主动实现onSaveInstanceState()方法。当然也许你读过我前面的文章对此有些印象,但我希望能加强你的印象,为了你程序的健壮性。
1-3.2 管理任务
Android管理任务和后台栈的方法,就像上面描述的那样,所有的activities连续的在同一个任务中被启动,并且后台栈满足“后进先出”的原则。对于大多数应用程序它能工作的很好,你不需要担心那人任务中的activities和它们存在的方式。然而可能你有时候想要中断一些正常的行为。可能你想要你程序中的一个activity启动时便开始一个新的任务,或者当你启动一个activity时,你想要直接使用它已经存在的实例,又或者你想要后台栈清空所有的activities。你能通过在manifest中的<activity>节点和startActivity()方法做更多的事情。
<activity>节点中的属性我们能使用:
- taskAffinity
- launchMode
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
重要的intent flags我们能使用:
- FLAG_ACTIVITY_NEW_TASK
- FLAG_ACTIVITY_CLEAR_TOP
- FLAG_ACTIVITY_SINGLE_TOP
下面的内容,详细描述了以上提到的manifest属性和intent flags是如何使用的
1-3.2.1定义启动模式(Launch modes)
启动模式允许你定义一个新的activity实例与当前任务的关系是怎样的。你能通过两种方法定义不同的启动模式:第一种是使用manifest文件,第二种使用Intent flags。如果Activity A启动了Activity B,那么Activity B能在manifest定义它与当前任务的关系,并且Activity A也能请求Activity B与当前任务的关系。如果两个activities都定义了Activity B与当前任务的关系,那么Activity A的请求的级别将大于Activity B的请求。
1. 使用manifest文件
当在manifest文件中声明它与一个任务的关系时,请在<activity>节点中使用launchMode属性。
launchMode属性指定一个描述,它是以何种方式来被放入到一个任务中的。针对launchMode属性有四种不同的启动模式:
“standard”(默认的启动模式)
不指定launchMode属性的话默认就是使用的此模式。系统在任务中创建经由intent启动的一个新实例。这个activity能被多次实例化,每一个实例可以属于不同的任务,一个任务也能有多个实例。
“singleTop”
如果一个activity的实例已经存在于当前任务的栈顶,那么系统经由intent,通过调用activity中的onNewIntent()方法,而不是创建一个新的activity实例。这个activity能被多次实例化,每一个实例可以属于不同的任务,一个任务也能有多个实例(但仅限于activity在栈顶并且不存在此activity的实例时)。例如,假设一个任务的后台栈由根部activity A和其他activities B,C,D构成。D在栈顶。如果D属于“standard”模式,这时D在启动D的话,那么变成A-B-C-D-D。但如果D属于“singleTop”模式,要么D在前面已经存在了,通过onNewIntent()方法后,因为D在栈顶。所以栈中仍然是A-B-C-D而不是A-B-C-D-D。如果是Activity B的话,就会添加新的实例,就算他的启动模式是“singleTop”也一样,因为B不是处于栈顶的情况。当一个新的activity实例被创建时,用户能点击返回键回到先前的activity。但当一个已存在的activity实例处理一个新的intent时,在新的intent到达onNewIntent()之前,用户不能点击返回按钮回到先前的activity。在新的intent到达onNewIntent()之前。
“singleTask”
系统创建一个新的任务并在新任务中实例化activity。如果在这个单独的任务中已经存在一个其他的activity实例了,那么系统通过onNewIntent()方法经由intent来获得一个已存在的实例,而不是创建一个新实例当然也不会创建一个新任务了。“singleTask”的activity可以和其他activity共享一个后台任务栈中。并且前台同时仅有一个activity实例可以存在。通过activity启动一个新任务,返回按钮仍然能回到先前的activity。
“singleInstance”
和“singleTask”几乎一样,但它不可以和其他activity共享一个后台任务栈中,所有栈中它都是唯一的。
图1-3-4 “singleTask”模式下的图例,请注意这里有两个任务,前台任务为Act1,Act2,后台为ActX, ActY并且Y为单任务启动模式,那么当Act2启动Y时,整个后台任务的栈都在前台栈顶部了。
2. 使用Intent flags
当启动一个activity时,你能修改默认与activity相关的任务,在使用startActivity()的时候,通过传入intent对象,并在对象中包含一个flag(Intent.setFlag()或Intent.addFlag())来实现需要的操作。这些flags能被用于修改默认的行为:
(1)FLAG_ACTIVITY_NEW_TASK:同等于“singleTask”
(2)FLAG_ACTIVITY_SINGLE_TOP: 同等于“singleTop”
(3)FLAG_ACTIVITY_CLEAR_TOP: 同等于默认的启动模式“standard”
1-3.2.2 处理affinities
affinity表示一个activity喜欢属于哪一个任务。默认,来自于同一应用程序中的所有activities彼此都有一个affinity。所以默认情况下同一应用中的所有activities喜欢同一个任务。然而,你可以为一个activity修改默认的affinity。定义在不同应用程序中的activities能共享一个affinity,或者定义在相同应用程序中的activities能分配到不同的任务中的affinity。你还能为已经在manifest中使用taskAffninty属性的<activity>修改affinity。taskAffninty属性使用一个字符串,它的名字必须唯一,因为系统在应用程序中使用这个标识来指定默认的任务affinity。
Affinity进入两种情况:
- 当启动activity的intent包含FLAG_ACTIVITY_NEW_TASK的时候。
默认一个新的activity启动到任务中。它被推送到调用者的同一后台栈中。但是如果intent中包含FLAG_ACTIVITY_NEW_TASK,那么系统会寻找一个不同的任务来存放新的activity。往往会是一个新的任务,但这不是必须的。如果有一个已经存在的任务并且这个任务的affinity与activity一样,那么这个activity会启动到这个任务中,反之,则启动新的任务。
如果flag导致一个activity开始一个新任务并且当用户点击home键的时候离开了,那一定有些方法让用户导航回到任务中来。例如一些实体(如通知管理器)在一个外部的任务启动了activity,但这个activity不属于当前程序中的一部分,所以它们一直使用包含FLAG_ACTIVITY_NEW_TASK的intent来调用startActivity()方法。如果你有一个activity能被外部的实体调用那么可以使用这个flag,注意用户有一个单独的方法来回到后台的任务,例如在home屏幕上点击icon。
2. 当一个activity的allowTaskReparenting的属性为“true”时。在这种情况下,当任务回到前台时,activity能根据相应的affinity移动到启动它的任务中。例如,假设有一款旅游应用,其中报告选定城市的天气情况是应用中的一个activity。它和在同一应用中的其他activities一样有相同的affinity并且allowTaskReparenting的属性为“true”。当天气报告的这个activity启动时,最初这个activity属于相同affinity的其他activities。但是,当旅游应用回到前台时,天气报告这个activity被分配到旅游应用任务中并在内部显示。
1-3.2.3 清空后台栈
如果用户长时间离开一个任务,那么系统会清空掉任务中除了根部activity之外的所有activities。当用户再次返回到任务时,只有根部activity被恢复了。系统这样的行为是因为经过一段时间之后,用户可能想抛弃一些行为,当然这只是系统想的,也许事实上真正用户想的不一样。但系统流程就是这么走的。下面是一些activity的属性它能修改这个行为:
- alwaysRetainTaskState
如果任务中的根部activity这个属性为“true”,那么默认的行为不会发生。任务会长期保持所有的activity在它们的栈中
- clearTaskOnLaunch
如果任务中的根部activity这个属性为“true”,不论何时离开一个任务,那么后台栈也会清空根部activity。换句话说,它和alwaysRetainTaskState属性完全相反。用户一直返回到任务的初始状态,甚至只是离开任务一会儿。
- finishOnTaskLaunch
这个属性和clearTaskOnLaunch一样,但它操作与单一的activity,而不是整个任务。它能导致任意activity离开,包括根部activity。当它为“true”时,这个activity仅为当前会话保持。如果用户离开后再返回任务,这个activity将不在出现。
1-3.2.4 启动一个任务
通过给定一个intent filter,你能设置一个activity作为一个任务的入口点。它必须包含"android.intent.action.MAIN"和"android.intent.category.LAUNCHER",如代码清单1-3.1所示: <activity ... > <intent-filter ... > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> ... </activity>
代码清单1-3.1
一个intent filter会为这个activity在应用程序启动器中添加icon和lable,因为给定用户启动或返回这个activity的方法。另外用户能够离开一个任务并可以在稍后使用activity启动器返回。请注意如果没有标识"android.intent.action.MAIN"和"android.intent.category.LAUNCHER"的activity请不要使用 “singleInstance”启动模式,因为它会初始化一个新任务。用户点击home键后,新启动的任务就被放置到后台并不可见了。然后你没有方法能回到那个任务了,因为它没有在应用程序启动器中。这种情况下你应该在那个activity中使用finishOnTaskLaunch为“true”
本文来自jy02432443,QQ78117253。是本人辛辛苦苦一个个字码出来的,转载请保留出处,并保留追究法律责任的权利