浅谈Android管理task方式

来源:互联网 发布:业主名录采集软件 编辑:程序博客网 时间:2024/04/30 14:57

之前在研究Android 四种LaunchMode的时候,有看到关于taskAffinity的内容,当时没有细看,现在突然发现同事提交代码的时候有写关于修改taskAffinity的东西,于是就有抽时间详细的研究了一下taskAffinity这个属性,发现还蛮复杂的,把自己的一些实践和见解写下来,供以后查看,涉及到的内容较多,可以重点查看红色的字体。

学习Android最好的教材就是Google官方文档加代码实践,在开始说taskAffinity属性之前,我们可以先看一下官网对于Task的定义:

A task is a collection of activities that users interact with when performing a certain job. The activities are arranged in a stack (the back stack), in the order in which each activity is opened.

简单来说: task就是一组特定的activities集合,这些activities按照每一个被打开的顺序放到一个stack中。

当用户打开一个application的时候,该application的task来到前台,如果没有该application的task存在(application最近没有被打开过),一个新的task将会被创建同时应用程序的主activity作为根activity在stack中打开。
当当前的activity启动另一个activity的时候,新的activity被推到栈顶并获得焦点,之前的activity仍然在stack中,但是被stop了。当一个activity stop了,系统保留其UI的当前状态。当用户按返回键,当前的activity从栈顶被弹出(被destroy了),之前的activity 恢复(resume)。栈中的activities重不会重新被排序,只有进栈和出栈。这样一来,返回stack以一种先进后出的对象结构进行操作。下图通过一个时间轴展现在不同的时间点属于当前返回stack的activities之间的进度来可视化这个行为。
diagram_backstack

如果用户继续点击返回键,直到所有的activities从栈中被移除,task将不再存在。
一个task是一个有机的整体,当用户开始了一个新的task或者通过Home键来到Home screen,可以被移到后台。在后台,所有task中的activities被stop了,但是这个task的返回stack仍然保持不变-改task只是简单地丢失了焦点,被另一个stack取代,如下图所示:
diagram_multitasking

一个task可以返回到前台这样用户就可以在他们离开的地方重新拿起。假设,例如,当前的task(Task A)有三个activities在它的栈中-两个在当前的activity下面。用户按下Home键,然后启动另一个新的应用程序。当回到Home screen的时候,Task A进入到了后台,当新的应用程序启动的时候,系统启动了该应用程序的带有它自己activities栈的task(Task B)。在和新的应用程序交互后,用户在此返回到Home并且选择最初启动的Task A的应用程序。现在,Task A重新来到前台-所有的三个在它在栈中的activities都完好无损并且栈顶的activity恢复了。这个时候,用户可以切换回Task B通过回到Home并选择对应task的应用程序。这就是Android的多任务的例子。

因为返回栈中的activities从来都不会被重新排序,如果你的应用程序允许用户启动一个特定的来自不止一个activity的activity。该activity的一个新的实例被创建并被push盗栈上(而不是将任何改activity之前的实例带到栈顶)。这样一来,你的应用程序中的一个activity可能会被初始化多次(甚至来自不同的tasks),如下图所示:
diagram_multiple_instances

这样一来,如果用户点击返回键,该activity的每一个实例按照它们被打开的顺序显露出来。然而,你可以修改这种行为如果你不想一个activity被实例化多次。

简单总结如下:

  1. 当Activity A启动 Activity B,Activity A is stopped,但是系统仍然保留其状态(比如滚动轴位置和表单中的文字啊)。如果用户在Activity B点击返回键,Activity A从它存储的状态中恢复。
  2. 当用户通过点击Home键离开一个任务,当前的Activity停止了,它的task转到后台。系统保留task中每一个activity的状态。如果用户稍后选择开始该task的应用图标来恢复该task,该task就会来到前台并恢复栈顶的activity。
  3. 如果用户点击返回键,当前的activity被从栈中弹出并被destroy。栈中之前的activity被恢复,当一个activity被destroy,系统将不会保留改activity的状态。
  4. Activities可以被实例化多次,即使来自其它tasks。

下面我们看看官网关于管理Task的内容,这个就是LaunchMode和taskAffinity密切相关了。

Android管理任务的方式和 back stack,如上面描述的一样-通过”先进后出”放置所有在同一个task中陆续打开的activities-对于大部分应用程序都工作的很好,你不必担心你的activities如何与task关联或者他们如何存在于back stack中。然而,你可能决定你想打断正常的方式。或许你想你应用程序中的一个activity开始一个新的task当它启动的时候(而不是被放置在当前task中);或者,当你启动一个activity,你想讲它的一个存在的实例带到前台(而不是在back stack的栈顶重新创建一个它的实例);或者,你想当用户离开task的时候,你的back stack中所有的activities被清除除了根activity。

你可以做这些事情或者更多,使用activity manifest元素中的属性和intent标志你传给 startActivity()方法的。
下面我将使用到的主要activity属性如下: taskAffinity launchMode和一些主要的intent标志:

FLAG_ACTIVITY_NEW_TASKFLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_SINGLE_TOP

根据Google Android官方文档的建议:

  1. 定义启动模式:

    启动模式允许你定义一个activity的实例如何与当前的task关联,有两种方法来定义不同的启动模式:
    使用manifest配置文件
    当你在你的manifest文件中声明一个activity时,你可以指明activity如何和tasks关联当它启动的时候。

  2. 使用intent标志

    当你调用startActivity()的时候,你可以包含一个声明新的activity如何和当前的task关联的intent。
    这样,如果Activity A启动Activity B,Activity B可以在它的manifest文件中定义它如何与当前任务关联而且Activity A也可以请求Activity B应该如何和当前task关联,如果两个activities都定义了Activity B应该如何与一个task关联,那么Activity A的请求(在intent中定义的)优先于Activity B的请求(定义在manifest的属性)。

使用manifest配置文件:
launchMode属性指明activity如何被启动到task的指令,有四种不同的启动模式你可以赋值给launchMode属性:
standard"(默认模式)

    默认地,系统在task中创建一个从它被启动和路由intent到它的activity的实例,activity可以被实例化多次,每一个实例可以属于不同的tasks,并且一个task可以用多个实例。

singleTop

    如果一个activity的实例早已经存在于当前task的顶部,系统将通过调用它的onNewIntent()方法路由该intent到该实例,而不是创建一个该activity的新实例。activity可以被实例化多次,每个实例可以属于不同的tasks并且一个task可以拥有多个实例(但只有在stack栈顶的activity不是该activity的现有实例)。
    假设一个task的返回stack由根activity A,activity B,C和栈顶的activity D组成(栈是A-B-C-D;D在栈顶),一个针对activityD的intent请求到来了。如果D拥有默认的”standard”启动模式,D类的一个新的实例被启动了,back stack就变出了A-B-C-D-D。然而如果Activity D的启动模式是”singleTop”,已经存在的D的实例会通过onNewIntent()方法收到该请求,因为D在栈顶-该栈仍然是A-B-C-D。然后如果一个针对类型B的请求到达了,接着B的一个新实例被添加到stack中,即使它的启动模式是”singleTop”,此时栈是A-B-C-D-B。

singleTask

    系统会创建一个新的task并且实例化该task的root activity,然后如果该activity的一个实例早已经存在一个独立的task中,系统将通过调用onNewIntent()方法路由该intent到存在的实例,而不是创建一个新的实例,一个时间该activity只有一个实例可以存在。
    注意:虽然activity启动在一个新的task中,但是返回键仍然带用户回到先前的activity中。

singleInstance

    和singleTask类似,除了系统不会启动任何其他activities到持有实例的task中,该activity总是它的task的单独和唯一成员,任何其他被这样启动的activities都在一个独立的task中打开。

再看一个例子:Android浏览器应用声明web browser activity应当总是在它自己的task中打开-通过在<activity>元素中指定singleTask启动模式。
这意味着如果你的应用程序发送一个intent请求打开Android Browser,Android Browser的Activity不会放到和你的应用同一个task中。相反,要么一个针对Browser的新的task启动,或者如果早已经有了一个Browser的task在后台运行,该task将会转到前台处理新的intent。
无论一个activity是在一个新的task中启动,还是和启动它的activity处于同一个task,返回键总是可以带用户回到之前的activity。
然而,如果你启动activity的时候指定singleTask启动模式,接着如果该activity的一个实例存在于一个后台task中,那整个task会被带到前台,这个时候,回退栈(back stack)栈顶现在包括带到前台的task中的所有activities。
如下图所示:
diagram_backstack_singletask_multiactivity

    上面的示例图很清晰明了,一个包括声明为singleTask启动模式的Activity X和Activity Y的后台task,当Activity Y被前台activity 2所启动的时候,整个后台task都会添加到back stack中,此时back stack中就有四个activities了,如果此时用户点击返回键回退到的是Activity X而不是想象中的Activity 2.

使用Intent标志:
当启动一个activity时,你可以修改一个activity和它的task的关联性通过在你用startActivity()方法传递的intent中包含标志,你可以包含的标志有:

FLAG_ACTIVITY_NEW_TASK

启动指定activity到一个新的task中,如果一个关联你现在正在启动的activity的task,该task将会以它最后存储的状态被带到前台并且改activity在它的onNewIntent()方法中接收新的intent。相同与上面的singleTask。

FLAG_ACTIVITY_SINGLE_TOP

如果正在启动的activity是当前activity(在back stack栈顶),那么存在的实例将会在收到一个onNewIntent()的调用,而不是新创建一个该activity实例,相同与上面的singleTop。

FLAG_ACTIVITY_CLEAR_TOP

如果正在启动的activity早已经运行在当前的task中,此时不会重新启动一个该activity的实例,而是所有在该activity上面其他的activities会被destroy,并且intent通过onNewIntent()方法会被发送到恢复的activity实例(现在栈顶的activity)。
FLAG_ACTIVITY_CLEAR_TOP是配合 FLAG_ACTIVITY_NEW_TASK最长使用的。当一起使用的时候,这个标志是定位在另一个task中已存在的activity并把它放到可以响应intent的位置的一种方法。
注意:如果特定的activity的启动模式是standard,它也会被从栈顶移除并且一个新的实例在它的位置被启动来处理进来的intent,因为当启动模式是standard时,一个针对新的new intent的新的实例总会被创建。
处理affinities

affinity属性指明一个activity更喜欢属于哪个task。默认地,来自一个应用程序的所有activities彼此都有一个亲和性。所以,一般来说一个相同应用的所有activities倾向于在一个相同的task中。然而,你可以修改一个activity的默认affinity属性。定义在不同应用程序的activities可以共享一个affinity属性,或者定义在同一个应用程序的activities可以被赋予不同的task亲和性。
你可以通过<activity>元素的taskAffinity来修改任何给定的activities的亲和性。

taskAffinity属性带有一个字符串值,该值必须和声明在manifest中的默认包名一样唯一,因为系统使用这个名字来识别应用程序的默认task亲和性。

亲和性使用在两种情况下:
当一个包含FLAG_ACTIVITY_NEW_TASK标志的intent启动一个activity时。

    一个新的activity,默认地启动到调用startActivity()方法的activity的task中。它和调用者放到同样的back stack中。然而,如果传递给startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK标志,系统将会需找一个不同的task来容纳新的activity。通常,它是一个新的task。然而,不是必须都是如此的。如果已经存在一个和新的activity具有相同的affinity的task,新activity会启动到该task中。如果没有,它会启动一个新的task。
    如果该标志造成一个activity去开始一个新的task并且当用户按Home键的离开该task的时候,肯定有一些方法对用户来说去返回该task。一些实体(像通知管理)总是启动activities到一个外部的task,而非作为到他们自己的task的一部分,所以他们总是将FLAG_ACTIVITY_NEW_TASK放到他们传递给startActivity()的intents中。如果你有一个可以被外部实体调用的activity可以使用该标志,确保用户有一个独立的方式返回到已经启动的task中,比如一个启动图标(task的根activity有一个CATEGORY_LAUNCHER intent过滤器)

当一个activity它的allowTaskReparenting属性设置为true

    这种情况,activity可以从它启动的task移到和它有相同affinity的task,当该task来到前台的时候。
    例如,假设一个报告选定的城市的天气情况的activity作为一个旅行应用程序的一部分。它和同应用程序的其他activities有相同的affinity(默认的应用程序affinity)并且它通过allowTaskReparenting属性允许重排。当你的一个activity启动天气报道这个activity时,它最初属于和你的activity相同的task。然而,当旅行应用的task来到前台的时候,天气报道的activity被重新分配到该应用程序的task中并展现它。
    注意:如果一个.apk的文件从用户角度看包含不止一个应用,你可能想使用taskAffinity属性去分配不同的亲和性给和每个应用关联的activities。

继续看官网对于taskAffinity属性的定义(水平有限,翻译的不是很好):

    Activity具有对于特定task的亲和性(affinity),具有相同的亲和性(affinity)的Activities概念上属于相同的task(从用户的角度来看就是相同的application),一个task的亲和性(affinity)由它的根activity的亲和性决定.亲和性决定两件事:activity重新向父的task和task所容纳的activity,当它被具有FLAG_ACTIVITY_NEW_TASK标志所启动。
    默认情况下,同一个application中所有的activities具有相同的亲和性(affinity).你可以设置改属性来把它们分组区分,甚至放置那些被定义在不同的application中的activities到同一个task。要指定activity不具备任何task的亲和性,只需设置该属性为空字符串。
    如果该属性没有被设置,activity默认继承application的亲和性(affinity)设置,一个application的默认亲和性名称在<manifest>元素中被设置为包名。

0 0
原创粉丝点击