Develop系列-API Guides-应用组件-Activities-Tasks and Back Stack
Tasks and Back Stack
一个app一般包含多个activities,每个activity执行不同任务。
Task是与用户交互执行一系列任务的activities集合,这些activities以打开顺序排列在一个栈列表中。
用户点击主页面的图标或者快捷方式,如果此应用从未被执行过,那么会创建一个新的task,主界面activity作为这个task的根activity,当根activity启动其他界面,一个新的activity会被压到task的栈顶并获取焦点。此时根activity仍然在task中保存,当用户按返回键时,当前的activity会被弹出task栈堆并销毁,前一个activity重新resumes(之前UI上的状态数据重新加载)。
Task中的activities顺序永远不会重排,只有push(入栈)和pop(出栈)两种动作,作为栈堆,保持着后进先出的原则。
如图所示,1是task的根activity;1启动2之后,2入栈,1被压到栈底,2在栈顶;2启动3之后,3入栈;返回键,3出栈被销毁,2继续在栈顶。这个task中所有在栈顶的activity都是可见并获取用户焦点的界面。
当所有activities都出栈后,那么这个task也就不存在了。
Task可以作为一个整体单元,可以被整体移动到后台,当用户通过Home键返回主界面并启动新的task。
Note:多个task可以同时在后台,但是有可能被系统在回收内存时销毁。
如下是activities和tasks的默认行为:
- A 启动 B,A被停止,系统保存A的状态数据。如果用户在B界面按返回键,B销毁,A恢复保存的状态数据。
- 用户按Home键离开某个task,task中所有activities停止,并退到后台,如果用户而后通过桌面图标启动此task,此task会切到前台并恢复栈顶activity。
- 如果用户按返回键,当前栈顶的activity会出栈,并被销毁,下一个切到栈顶的activity会恢复。activity被销毁后,系统不会保存其状态数据。
- activities能够被实例化多次,即使在不同的tasks。
当系统销毁后台task中的activities时,这些activities界面用户临时数据会丢失(即便如此,task中的顺序依旧存在),当这些activities重新回到栈顶时,不是通过resume,而是通过recreate。为了避免此类数据丢失,强烈建议通过onSaveInstanceState()来保存临时数据。
管理Tasks
有时候你需要一个activity在每次启动时都是重新创建一个新的task,而不是安排到现有的task中;或者你创建activity时,直接使用现有已经创建的activity实例,而不是在栈顶创建新的实例;又或者你想用户离开task后清除根activity之外的其他activities。
你可以通过<activity>的属性和intent的flag来改变默认的行为。
<activity>属性如下:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
intent的flag如下:
Caution:多数应用不需要改变系统的默认行为,如果你确定需要改变默认行为,那么你需要确保启动、返回等场景下应用的可用性,有可能返回键的行为会与用户的预期有冲突。
定义启动模式
启动模式允许你定制一个新的activity实例与当前task的关联,有如下两种方式:
- 用manifest文件:被启动时此activity该如何关联
- 用intent flags:新启动的activity该如何关联
如果A启动B,B能过通过manifest定义关联方式,A也能通过intent flags来定义B的关联方式,如果两个都有定义,那么A的intent flags优先。
Note:两种方式不是对等的,有些模式只能用manifest文件来定义,有些只能用intent flags来定义。
用manifest文件
<activity>元素的launchMode属性:
- standard(默认模式)
- 默认模式,系统创建新的activity实例,activity能被实例化多次,每个实例可以属于不同的tasks,一个task能包含多个实例。
- singleTop
- 如果activity的实例已经存在,并在task的栈顶,那么系统会调用存在实例的
onNewIntent()
而不是创建新的实例。activity可以被实例化多次,每个实例可以属于不同的task,一个task可以有多个实例(栈顶不是此activity的实例的task) - 例如,A-B-C-D,A是栈底,D是栈顶,此时又来一个intent需要启动D,如果D是standard模式,那么会变为A-B-C-D-D,然后如果D是singleTop模式,那么仍是A-B-C-D,intent会由D的onNewIntent来接收。如果此时启动B,那么不管B是standard或者singleTop模式,都是A-B-C-D-B
- 如果activity的实例已经存在,并在task的栈顶,那么系统会调用存在实例的
- singleTask
- 系统创建新的task,然而如果activity的实例已经在其他task中存在,那么系统会将intent分发给存在的实例,触发其onNewIntent(),而不是创建一个新的实例,系统只能存在一个实例,浏览器就是如此。
- singleInstance
- 与singleTask相同,系统不能启动其他activites到存在此activity的task,标记了singleInstance的activity是task的唯一成员。
一般来说,返回键总是给用户带来上一个界面,当你启动singleTask模式的activity时,如果此activity实例已经在后台task中存在,那么整个task会被切到前台,此时,返回键是逐个显示刚切到前台的task中的所有activities。如下图所示:
Note:manifest中launchMode属性有可能会被intent flags所覆盖。
使用Intent flags
与singleTask一致
与singleTop一致
如果activity实例已经在当前task中,启动时,此实例之上的activities都会被销毁,此实例触发onNewIntent()并处于栈顶。
FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK一般联合使用。
Note:如果launch mode是standard,那么已存在的实例也会被销毁,通过创建新的实例来替换销毁实例的位置,因为standard模式始终通过创建新实例来接收intent。
Handling affinities
affinity意味着activity隶属于哪个task。默认的,同一app的所有activities对于彼此有affinity,所以默认的,同一app的所有activities都在一个task中。然而,你也可以修改默认的affinity。不同app中的activities能共享一个affinity,或者同一个app中的activities能定义不同的affinities。
可以通过<activity>的taskAffinity属性来修改affinity,taskAffinity是唯一的字串。
两种使用affinity的场景:
- Intent包含FLAG_ACTIVITY_NEW_TASK,一般系统会创建新的task,当然如果已存在task的taskAffinity与新启动的activity相同,那么也不会创建新的task。
- 当allowTaskReparenting属性为true,
Tip:如果apk包含不止一个“application”,那么可以定义taskAffinity来区分。
Clearing the back stack
当用户离开task很长时间后,系统会清理除根activity之外的所有activities,当用户又重新回到这些task,那么只有根activity才会显示。
用户可以通过如下方式来修改这种默认行为:
如果为true在根activity中,那么上述默认行为不会发生,此task中所有activities都会长时间保持
如果为true在根activity中,那么上述默认行为会在用户离开task后立即发生
与clearTaskOnLaunch类似,但是只针对某个activity,而不是整个task,根activity如果定义属性为true也会被清除
启动一个task
同时定义"android.intent.action.MAIN"
和"android.intent.category.LAUNCHER"的activity
在桌面上有入口图标。
这个非常重要:用户可以离开这个task,也可以通过桌面图标返回这个task,所以定义这两种filiter的activites通常会初始化一个task。
singleTask和singleInstance应当在这种activity上定义,试想一下,singleTask启动的activity在一个新的task中,此时用户按Home键,如果桌面上无入口图标,那这个task将永远无法返回。
如果你真心不想用户能够返回一个activity,可以设置finishOnTAskLaunch属性。