1.2.3 Task and Back Stack - 任务和回退堆
一个应用通常包含多个Activities。每个activity的设计应该围绕着某种指定类型的action,如果这样做了,用户就可以执行该action,也可以用它来开启另外的activity。例如,邮件应用可能会有一个用于展示新邮件列表的页面。当用户选择了一封邮件时,就打开一个新的activity来浏览内容。
一个activity甚至可以打开存在于同一设备上不同应用里的activity。例如,如果你的应用想发送邮件,你可以定义一个intent来执行这个“发送”的动作,并在intent里带上数据,例如邮件地址和内容等。其它应用中已经申明自己可以处理这种intent类型的activity就会被打开。在这种情况下,该intent用来发送邮件,因此,邮件应用中撰写邮件内容的activity启动(如果多个activity都支持同样的intent,那么系统会让用户选择到底使用哪个activity)。当邮件发送完毕后,你的activity被恢复,这样看起来就好像发送邮件的activity是你应用的一部分。即使activity来自于其它应用,但是android系统通过在同样的任务(task)里保存activity的手段来维持无缝的用户体验。
当执行某项工作时,任务(task)是与用户相互交互的activity的集合。这些activity被管理在同一个堆中(回退堆(back stack)),在这个堆里存放的activity是被依次打开过的。
设备的主屏幕是大多数任务(task)开始的地方。当用户在应用程序启动器上点击一个图标时(或是点击主屏幕上的快捷方式),应用的任务(task)就来到了前台。如果不存在应用的任务(应用最近没有被使用过),那么,一个新的任务就会被创建,并且应用的main activity就会作为根activity在堆中打开。
当前的activity启动了另外一个activity时,新的activity就被压入堆顶并获取焦点。前一个activity仍在堆里,但是处于停止状态。当activity停止时,系统会保存它停止时用户界面的状态。如果用户按下了返回键,当前的activity就会从堆中被弹出(activity被销毁了)并且先前的activity被恢复了(先前activity的UI状态被系统记住了)。堆中的activity从不会被重新放置,只会从堆中弹出或是压入--activity启动时被压入堆中,用户使用后退键从activity离开时从堆中弹出。后退堆(back stack)本身就和“后进先出”的对象结构执行的操作一样。图表1展示了多个activity之间的进程,用时间轴的方式使得这些行为变得可视化,并展示了每个时间点上后退堆的情况。
图1 在后退堆(back stack)里,如何添加一个新activity任务的展示。当用户按下返回键,当前的activity被销毁,前一个被恢复。
如果用户继续按下返回键,后退堆(back stack)里的activity依次被弹出并显示前一个activity,直到用户回到主屏幕(或者是任务(task)开始时运行的activity)。当堆里所有的activity都被移除时,任务(task)也就被销毁了。
任务(task)是高内聚(原文是cohesive单词,不知道怎么翻译--译者注)的单元,当用户开始一个新的任务(task)或是通过Home键返回到主屏幕时,任务(task)就来到了后台。当任务(task)在后台时,它里面所有的activity都停止了,但是任务(task)的后退堆(back stack)仍然是完整的--正如图2所示的那样,当其它任务(task)出现时,原先的任务(task)仅仅只是失去了焦点。
图2 两个任务(task):当Task A在后台等待被恢复时,Task B在前台与用户发生交互。
任务(task)可以返回到前台,因此离开时是什么样子的,回来时仍然是什么样子。例如,假设当前任务(task)(Task A)的堆里有三个activity。用户按下Home键,通过应用程序加载器开启了一个新的应用。当主屏幕显示时,Task A进入了后台。当新的应用开启时,系统为新应用创建了一个属于它的新的任务(task)(Task B)。在和新的应用发生完交互后,用户再次回到了主屏幕并打开了先前的应用。这时,Task A回到了前台--它堆里的三个activity都是完整的,并且堆顶部的activity被恢复。此时,用户也可以通过回到主屏幕、点击应用图标来启动Task B(或者从最近应用的屏幕上选择Task B的任务(task))。上面说的就是Android系统里多任务处理的例子。
注意:虽然在后台一次可以拥有多个任务,但是,如果在同一时刻用户在后台运行了很多任务,那么系统为了恢复内存,可能会干掉某些后台activity,这样就会引起activity状态的丢失。更多详情请参见Activity state 段落。
因为后退堆(back stack)里的activity从不重新放置,那么,如果从多个activity里开启了同一个activity,那么,该acitivty的新实例就会被创建被压入堆中(而不是把该activity的原先实例带到堆顶)。应用中的activity本身就可以被实例化多次(即使是来自不同的任务(task)),如图3所示的那样。
图3 同一个activity被实例化多次
如果用户使用返回键返回时,activity的每个实例以它们打开的顺序依次被展示(每个activity都保存着它原先的UI状态)。虽然如此,但是,如果你不想一个activity被实例化多次,你可以改变这种状态。在随后的 Managing Tasks 段落里讨论了如何实现它。
activity的默认行为和任务(task)的总结:
1. 当activity A启动了Activity B时,activity A停止了,但是系统会保持它的状态(例如滚动的位置或是表单里输入的文本等)。如果用户使用返回键从activity B返回到activity A时,activity A会带着被存储的状态一起被恢复。
2. 当用户通过按下Home键返回到主屏幕的方式离开一个任务(task)时,当前的activity停止了,并且它的任务(task)进入了后台。系统会保持任务(task)里每个activity的状态。随后,如果用户通过点击应用图标再次打开应用时,该应用的任务(task)就回到了前台并恢复了任务(task)堆顶部的activity。
3. 如果用户点击了返回键,当前的activity从堆中弹出并销毁了。堆中先前的activity被恢复了。系统不会保持被销毁activity的状态。
4. activity可能会被实例化多次,即使是来自于不同的任务(task)。
Saving Activity State - 保存Activity状态
正如上边讨论的那样,系统默认会保存已经停止的activity的状态。所以当用户回到先前的activity时,它的用户界面仍然和用户离开时的一样。虽然如此,但是,为了防止activity被销毁和重建,你也应该主动地使用回调方法保存activity的状态。
当系统停止你的activity时(例如新的activity启动了,或者是任务(task)进入后台了),如果需要恢复内存,系统可能会完全销毁掉某个activity。如果这样的事情发生了,那么与销毁activity相关的信息也都丢失了。虽然被销毁activity的实例仍然保存在后退堆(back stack)中,但是当它到达堆顶时,系统必须得将它重建(而不是恢复)。为了避免丢失掉用户的操作,你应该在activity里通过实现onSaveInstanceState() 回调方法来主动地保存activity的状态。
关于如何保存activity的更多详情,请参见onSaveInstanceState() 章节。
Managing Tasks - 管理任务(task)
Android中,管理任务(task)和后退堆(back stack)的办法是把所有的activity连续地放置在同一个任务(task),同一个“后进先出”的堆里--对大多数应用来讲,这样做是非常棒的。你不用考虑你的activity如何与任务(task)相联系,也不用考虑它们在后退堆里如何存在。虽然如此,但是,你或许想要中断正常的行为。或者当你应用中启动某个activity时,你想把它放入一个新的任务(task)里(而不是放在当前堆里),或者,当你打开一个activity时,你想把它的一个已经存在的实例带到前台(而不是在后退堆(back stack)里重新实例化一个),再或者,当用户离开任务(task)时,你想把你后退堆(back stack)里除了根activity的所有其它的activity都从后退堆(back stack)里移除掉。
上面所说的你都可以做到,甚至做到更多,方法是使用mainfest文件里<activity> 元素的属性,或者在使用startActivity() 方法打开activity时,给该方法传递带有标记的intent即可。
你可以使用的<activity> 主要的属性如下:
你可以使用的主要intent的标记如下:
在下面的内容里,你将会学习到如何使用这些属性和intent标识来定义activity与任务(task)怎样进行联系,activity在后退堆(back stack)里又有怎样的表现。
警告:大多数应用不应该中断activity和任务(task)的默认行为。如果你决定了你的应用必须改变activity的默认行为,那你一定要小心的使用,并一定要测试当从其它activity返回时、当使用返回键从其它任务(task)返回时它的可用性。一定要测试是否有与用户所期望的行为冲突的导航行为。
Defining launch modes - 定义启动模式
启动模式可以你让来定义一个activity的新实例如何与当前任务(task)进行联系。你可以使用两种方法来定义不同的启动模式:
Using the mainfest file
你在清单文件里申明一个activity时,你就可以定义在它在启动时如何与任务(task)进行联系。
Using Intent flags
当你调用startActivity()方法时,你可以在Intent 参数里包含一个标识来申明当前的activity如何/是否与当前任务(tack)发生联系。
从根本上说 ,如果Activity A启动了Activity B,在清单文件里可以定义Activity B如何与当前任务(task)联系,并且Activity A也可以要求Activity B怎样与当前任务(task)联系,如果两个Activity都定义了Activity B如何与当前任务(task)发生联系,那么Activity A的请求(定义在intent里的)的优先级高于Activity B的要求(定义在清单文件里的)。
注意:在清单文件里可用的启动方式在intent里不一定能找到相应的标识,同样地,在intent标识里可用的启动方式在清单文件里也不一定能找到相应的申明。
Using the mainfest file
你在mainfest里申明一个activity时,你可以使用<activity>元素里的launchMode 属性。
launchMode 属性指定了关于activity如何被加载到任务(task)的说明。有四个可以指定给launchMode 属性的不同的读取模式:
"standard"(默认模式)
默认地,系统在activity开始的任务(task)里给它创建一个新的实例并把intent发送给它。该activity可以被实例化多次,每个实例都可以属于不同的任务(task),并且一个任务(task)里也可以有它的多个实例。
"singleTop"
在当前任务(task)的顶部,如果一个activity的实例已经存在了,那么系统会通过调用该实例的 onNewIntent() 方法来把intent发送给它,而不是再创建一个新的实例。activity可以被实例化多次,每个实例都可以属于不同的任务(task),并且一个任务(task)里也可以有它的多个实例(当activity处于后退堆(back stack)的顶部时,不是它的一个现有实例)。
例如,假设一个任务(task)的后退堆(back stack)由根activity A和activity B、C、D组成(D在顶部)。类型D的intent到达了。如果D是默认的启动模式"standard",那么D的一个新实例被启动了,堆里的内容就变为A-B-C-D-D。虽然如此,但是,如果D的启动模式是"singleTop",那么,已存在的D的实例就会通过onNewIntent() 方法收到该intent,这是因为它在堆顶--堆里的内容仍然为A-B-C-D。但是,如果类型B的intent到达,那么即使B的启动模式是"singleTop",系统也仍然会新实例化一个B的实例并把它添加到 堆里。
注意:activity的新实例被创建时,用户可以通过按下返回键返回到先前的activity。但是,当一个activity已经存在的实例处理了一个新的intent,那么,在一个新的intent进入onNewIntent() 方法之前,用户不能通过按下返回键返回activity的状态。
"singleTask"
系统会创建一个新的任务(task)并把activity的实例做为该任务(task)的根。但是,如果该activity的实例在一个单独的任务(task)里已经存在了,那么系统会通过调用它的onNewIntent() 方法来把intent发送给它,而不是再创建一个新的实例。也就是说,在同一时刻,仅仅只会存在该activity的一个实例。
注意:尽管在新的任务(task)里启动了该activity,但是按下返回按钮时,用户仍然会回到先前的activity。
"singleInstance"
与"singleTask"类似,不一样的是系统不会把其它的activity发送到拥有"singleInstance"启动模式activity的任务(task)里。以该模式启动的activity在任务(task)只会有一个;任何以该模式启动的activity都会在一个单独的任务(task)里被打开。
其它的例子是Android浏览器应用,它通过在清单文件的<activity>元素里指定singleTask模式来申明浏览器里的activity都应该在它自己单独的任务(task)里打开。这就意味着,如果你的应用发布了一个intent来打开网页浏览器,那么它的activity不会和你的应用放置在同一个任务(task)里。要么是一个新的任务(task),要么如果浏览器已经在后台运行了,那它的任务(task)就会被带到前台来处理该intent。
当一个activity启动另外一个时,不管另外一个activity是在新任务(task)里启动还是在原有任务(task)里启动,按下返回键总会使用用户回到先前的activity。但是,如果你使用singleTask模式启动了一个activity,那么,如果它的一个实例已经存在于后台的某个任务(task)中,那这个任务(task)会整个都被带到前台的。如果是这种情况,后退堆(back stack)里包含了任务(task)里的所有activity。图4就描述了这种情况:
图4 描述了以"singleTask"模式启动的activity是如何被添加到后退堆(back stack)里的。如果该activity已经存在于某后台任务(task)(有自己的后退堆(back stack)),那么,当该activity启动到前台时,它所在的整个后退堆都被带到前台了,并放置于当前任务(task)的顶部。
关于在清单文件里使用启动模式的更多详情,请参见<activity>元素文档,在该文档里,launchMode属性与接收的值有更多的讨论。
注意:在清单文件使用 launchMode 属性为activity指定的启动模式会被包含在启动activity的internt的标识重写,下面的段落会讨论这一情况的。
Using Intent flags
当你使用startActivity()方法打开一个activity时,你可以通过给该方法传递包含标识的intent来改变它与任务(task)默认的联系方式。你可以用来改变默认行为的标签是:
在新的任务(task)里开启activity。如果要启动的activity已经存在于某个任务(task)里,那么该任务(task)会以它最后存储的状态出现在前台,并且,该activity会在onNewIntent() 方法里接收到新的intent。
它会和清单文件里申明"singleTask"产生同样的效果。
如果在当前的任务(task)里启动(在后退堆(back stack)的顶部),那么,已经存在的实例会接收到onNewIntent() 方法的调用,而不是创建一个新的实例。
它会和清单文件里申明"singleTop"产生同样的效果。
如果要启动的activity已经存在于当前任务(task)了,那么不是启动一个activity的新实例,而是把任务(task)里在该activity之上的所有其它activity的实例都销毁掉,然后通过onNewIntent() 方法把intent分发给该activity原先的实例(该activity当前在任务(task)的顶部,被恢复了)。
清单文件里没有对应的值。
FLAG_ACTIVITY_CLEAR_TOP经常联合FLAG_ACTIVITY_NEW_TASK一起使用。如果它们俩个一起使用了,那么这是一种可以定位在其它任务(task)里已经存在activity并把它放置到可以响应intent位置的方法。
注意:如果指定activity的加载模式是"standard",那么它也会从堆中移除,并创建一个新的实例来处理到达的intent。这就是为什么当启动模式是"standard"时总是会为新的intent创建一个新的实例。
Handling affinities - 处理亲疏关系
亲疏关系(affinity)标识着activity更愿意属于哪个任务(task)。默认地,来自于同一应用的所有activity都有着相同的亲疏度(affinity)。因此,同一应用的所有activity都更愿意进入同一任务(task)。但是,你也可以更改某个activity的默认行为。定义在不同应用里的activity也可以拥有同样的亲疏度,或者同一应用里的不同activity有着不一样的亲疏度。
你可以使用<activity>元素的taskAffinity 属性来改变activity的亲疏度。
taskAffinity 属性的取值是字符串类型,该值必须是唯一的,它来自于在<manifest> 元素里申明的默认包名(此句翻译感觉有问题,原句是:The taskAffinity attribute takes a string value, which must be unique from the default package name declared in the <manifest> element -- 译者注),因为系统使用该名字来为应用定义默认任务(task)的亲疏度。
亲疏度在两种情况下起作用:
1. 当包含了FLAG_ACTIVITY_NEW_TASK 标识的intent启动某个activity时。
调用startActivity()方法启动的新activity默认被装载进入任务(task)中。它和调用者一样被压入同样的后退堆(back stack)中。但是,如果传递 给startActivity()方法的intent包含了FLAG_ACTIVITY_NEW_TASK 标识,那么系统就会查找一个不同的任务(task)来给新的activity安家。通常,这是一个新的任务(task),但不是必须得是新的。如果已经存在的任务(task)和新的activity有着同样的亲疏度,那么新activity就被加载到这个任务(task)里了。如果没有与新activity有着同样亲疏度的任务(task),那系统就会新开一个任务(task)。
如果FLAG_ACTIVITY_NEW_TASK 标识使得activity新开了一个任务(task),然后用户按下返回键离开了该activity,那么肯定会有某种方式让用户可以返回到这个任务(task)。某些实体(例如通知管理器)总是在一个外部的任务(task)里打开activity,从来不会在自己所在的任务(task)里打开,因此,它们在使用startActivity()方法时总是给intent绑定FLAG_ACTIVITY_NEW_TASK 标识。如果你的activity可能被外部的实体执行,那你可能会用到这个标签,要千万小心,用户可能会使用其它方法返回到打开activity的任务(task)里,例如使用快捷方式图标(任务(task)里的根activity有一个CATEGORY_LAUNCHER 的intent过滤器,下面Starting a task会讲述这些内容)。
2. 当activity的 allowTaskReparenting 属性被设置为true时。
在这种情况下,当任务(task)来到前台时,activity会从启动它的任务(task)里移动到与它有着同样亲疏度的任务(task)中。
例如,假设一个展示被选择城市天气情况的activity被定义在一个旅游应用里。它和应用里其它的activity有着同样的亲疏度(默认的应用亲疏度),并且它设置了允许重复的属性。当你应用的某个activity启动了展示天气情况的activity时,它最初和启动它的activity在同一个任务(task)里。但是,当旅游应用所在的任务(task)回到前台时,展示天气情况的activity会再次指定给这个任务(task)并在它里面进行显示。
提示:如果以用户的角度来看,一个.apk文件里包含了多于一个的应用时,你就可以给与不同应用联系的不同activity使用taskAffinity属性来分配不同的亲疏度。
Clearing this back stack - 清理后退堆
如果用户离开任务(task)很长时间了,系统会清理任务(task)里除了根activity的其它所有activity。当用户再次返回到任务时,仅仅只有根activity被保存下来了。因为在用户长时间的离开后,他很可能不再关心他之前的改变了,重新回到任务里,他很可能想做一些新的事情,系统就是基于这种考虑才会清理 任务的。
你可以使用下面的activity的属性来对这一行为做出改变:
如果任务(task)的根activity的该属性被设置为true,那么上面描述的默认行为就不会发生了。即使经过了很长一段时间后,任务(task)仍然会保存堆里所有activity的状态。
如果任务(task)的根activity的该属性被设置为true,不论用户什么时候离开任务(task),什么时候又回来,堆里面只会留下根activity,其它的都被清理了。
这个属性和clearTaskOnLaunch 属性类似,但是它只会操作一个单独的activity,而不是整个任务(task)。它可以让任何一个activity消失,包括根activity。如果它被设置为true,系统会为当前session保存部分activity。如果用户离开后返回到任务(task)里,它就不再存在了。
Starting a task - 开始一个任务
你如果给一个activity设置intent过滤器的指定action为"android.intent.action.MAIN",指定的category为"android.intent.category.LAUNCHER",那么,这个activity就成为一个任务(task)的入口了。
<activity ... > <intent-filter ... > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> ... </activity>
这种intent过滤器会让该activity的图标和文本显示在应用程序加载器的面板上,这样的话就给用户提供了一种方式来加载activity,并在加载之后的任何时间都可以返回到它创建的任务(task)里。
第二点非常重要:在离开任务(task)以后,用户肯定可以通过activity加载器返回到原先的任务(task)里。因为这个原因,当activity有ACTION_MAIN和CATEGORY_LAUNCHER过滤器时,应该使用"singleTask"和"singleInstance"这两种标识着activity总是实例化同一个任务(task)的启动模式中的某一个。想象一下,如果过滤器丢失了会发生什么:一个intent装载了拥有"singleTask"模式的activity,这样它就会实例化一个新的任务(task),用户还花费了一些时间在这个任务(task)上做了一些工作。然后,用户按下了Home键。这时,任务(task)进入了后台并不可见了。现在,用户再也不能返回到这个任务了,这是因为没有ACTION_MAIN和CATEGORY_LAUNCHER过滤器的应用不会在应用程序加载器上出现的。
你如果不想让用户返回到某个activity,那你就把它<activity>元素的finishOnTaskLaunch 属性设置为true就可以了。
1.2.3 Task and Back Stack - 任务和回退堆的更多相关文章
- Android Task 与 Back Stack
Tasks and Back Stack 一个应用通常包括多个 activity.每个 activity应用设计为围绕针对执行用户特定的行为和可以启动其它 activity. 一个 Activity也 ...
- 安卓Task和Back Stack
概述 一个Activity允许用户完成一些操作,甚至,Android中设计Activity为组件的形式,这样,多个Activity--甚至是其它App的Activity可以一起完成一项任务. Task ...
- java - Stack栈和Heap堆的区别
首先分清楚Stack,Heap的中文翻译:Stack—栈,Heap—堆. 在中文里,Stack可以翻译为“堆栈”,所以我直接查找了计算机术语里面堆和栈开头的词语: 堆存储 ...
- JAVA Stack栈和Heap堆的区别(转)
首先分清楚Stack,Heap的中文翻译:Stack—栈,Heap—堆. 在中文里,Stack可以翻译为“堆栈”,所以我直接查找了计算机术语里面堆和栈开头的词语: ...
- 如何给女朋友讲明白:Java 中 Stack(栈) 与 Heap(堆)
背景 Java 中 Stack(栈) 与 Heap(堆) 是面试中被经常问到的一个话题. 有没有对 Java 中 Stack(栈) 与 Heap(堆) 烂熟于心的童鞋,请举手!!!(怎么没人举手-) ...
- xcode - iPhone Debugging: How to resolve 'failed to get the task for process'? - Stack Overflow
The program being debugged is not being run. Everyone sees this once in a while during Xcode develop ...
- 逻辑运算符、三元运算符、for循环、stack(栈),heap(堆),方法区,静态域
Lesson One 2018-04-17 19:58:39 逻辑运算符(用于逻辑运算,左右两边都是 true 或 false) 逻辑与-& 和 短路与-&& 区别: & ...
- stack栈、heap堆的说明图
上面的两幅图是stm32系列的,但是基本原理可以通用的.
- Activity、Task、应用和进程
http://www.cnblogs.com/franksunny/archive/2012/04/17/2453403.html Activity.Task.应用和进程 为了阅读方便,将文档转成pd ...
随机推荐
- Linux 调度器发展简述
引言 进程调度是操作系统的核心功能.调度器只是是调度过程中的一部分,进程调度是非常复杂的过程,需要多个系统协同工作完成.本文所关注的仅为调度器,它的主要工作是在所有 RUNNING 进程中选择最合适的 ...
- java 过滤器补充
多个Filter按照在配置文件中配置的filter顺序执行. 在web.xml文件中配置该Filter,使用init-param元素为该Filter配置参数,init-param可接受如下两个子元素: ...
- C# 获取父控件容器的属性
C# 获取父控件容器的属性 BindingNavigator bindingNavigator = (sender as ToolStripButton).GetCurrentParent() as ...
- 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何修改某个轴的数值单位
在某个轴上双击,切换到Settings,然后可以再Unit中修改为角度,弧度,mm 更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youku.com/aceta ...
- C# Meta Programming - Let Your Code Generate Code - 利用反射重写自动的ToString()
我们在写一些Model的时候,经常会重写ToString,为了在控制台中进行打印或者更好的单元测试. 但是,如果Model的字段非常多的时候,如此简单的重复劳动经常会变成一件令人头痛的事情,因为大家 ...
- 设计模式在cocos2d-x中的使用--简单工厂模式(Simple Factory)
什么是简单工厂模式? 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式.通过专门定义一个类来负责创建其它类的实例,被创建的实例 ...
- Loadrunner socket协议lrs_receive函数接收到返回数据包 仍然等待服务器返回--解决
前段时间在使用loadrunner socket协议发送数据包到到服务器,使用lrs_receive接收服务器应答数据包,已经接收到数据包,但LR仍然在等待服务器端返回,而且日志打印显示每次接收返回都 ...
- UVALive 6529 Eleven 区间dp
题目链接:option=com_onlinejudge&Itemid=8&page=show_problem&problem=4540">点击打开链接 题意: ...
- Hashtable insert failed. Load factor too high. The most common cause is multiple threads writing to the Hashtable simultaneously
暂时也没准确定位到问题 https://support.microsoft.com/zh-cn/help/2803754/hotfix-rollup-2803754-is-available-for- ...
- 欧洲顶级音频播放软件AIMP
http://zmingcx.com/europes-top-software-aimp-audio-player.html 音频播放软件众多,耳熟能详的Winamp.Foobar2000.千千静听. ...