• 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> 主要的属性如下:

            taskAffinity launchMode

            allowTaskReparenting

            clearTaskOnLaunch

            alwaysRetainTaskState

            finishOnTaskLaunch

             你可以使用的主要intent的标记如下:

            FLAG_ACTIVITY_NEW_TASK

            FLAG_ACTIVITY_CLEAR_TOP

            FLAG_ACTIVITY_SINGLE_TOP

             在下面的内容里,你将会学习到如何使用这些属性和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)默认的联系方式。你可以用来改变默认行为的标签是:

             FLAG_ACTIVITY_NEW_TASK

             在新的任务(task)里开启activity。如果要启动的activity已经存在于某个任务(task)里,那么该任务(task)会以它最后存储的状态出现在前台,并且,该activity会在onNewIntent() 方法里接收到新的intent。

             它会和清单文件里申明"singleTask"产生同样的效果。

             FLAG_ACTIVITY_SINGLE_TOP

             如果在当前的任务(task)里启动(在后退堆(back stack)的顶部),那么,已经存在的实例会接收到onNewIntent() 方法的调用,而不是创建一个新的实例。

             它会和清单文件里申明"singleTop"产生同样的效果。

             FLAG_ACTIVITY_CLEAR_TOP

             如果要启动的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的属性来对这一行为做出改变:

            alwaysRetainTaskState

             如果任务(task)的根activity的该属性被设置为true,那么上面描述的默认行为就不会发生了。即使经过了很长一段时间后,任务(task)仍然会保存堆里所有activity的状态。

            clearTaskOnLaunch

             如果任务(task)的根activity的该属性被设置为true,不论用户什么时候离开任务(task),什么时候又回来,堆里面只会留下根activity,其它的都被清理了。

            finishOnTaskLaunch

             这个属性和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)的入口了。

     1 <activity ... >
     2 
     3     <intent-filter ... >
     4 
     5         <action android:name="android.intent.action.MAIN" />
     6 
     7         <category android:name="android.intent.category.LAUNCHER" />
     8 
     9     </intent-filter>
    10 
    11     ...
    12 
    13 </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就可以了。

  • 相关阅读:
    当Django模型迁移时,报No migrations to apply 问题时
    django--各个文件的含义
    django--创建项目
    1013. Battle Over Cities (25)
    1011. World Cup Betting (20)
    1009. Product of Polynomials (25)
    1007. Maximum Subsequence Sum (25)
    1006. Sign In and Sign Out (25)
    1008. Elevator (20)
    1004. Counting Leaves (30)
  • 原文地址:https://www.cnblogs.com/wchhuangya/p/3306453.html
Copyright © 2020-2023  润新知