5.4 任务和界面组件栈

Android的应用都是基于组件的,每个组件都可跨应用跨进程被调用,一项功能可能需要来自不同应用的多个组件共同完成。这样一来,以apk为单位的应用概念并不能真实地体现组件在应用中的运行状况。

因此,在Android中引入了任务(Task)的概念。任务相当于运行概念上的“应用”,由一组为了完成某项工作而聚集在一起的界面组件对象共同构成。它不受应用和进程的约束,体现的是界面组件对象之间的调用和执行关系。

任务中的界面组件对象,是按照栈的形式进行组织的,称为界面组件栈(Activity Stack)。栈底界面组件是整个任务的发起者,称为任务的根组件(Root Activity),可以通过Activity.isTaskRoot函数来判断该界面组件对象是否为一个任务的根组件。

而栈顶的界面组件,是该任务与用户进行交互的当前组件。它可以通过Activity.startActivity等函数调用与用户交互的下一个界面组件,新的界面组件对象会压入栈中,成为栈顶组件。

栈顶的界面组件执行完成后会退栈销毁,在栈中位于它前面的界面组件对象会作为新的栈顶组件与用户进行交互。当任务的根组件退栈,就意味着整个任务已经执行完成。

图5-11展示了一个任务的执行过程,以及执行过程中界面组件栈的变化。整个任务由来自于邮件应用和联系人应用中的组件共同完成,其界面组件栈的变化步骤如下:

1)在邮件应用中,设定的入口组件是邮件列表界面组件,当用户从应用列表中点击邮件应用的图标时,系统会构造邮件列表界面组件对象A,作为整个任务的根组件。

2)当用户从列表中选择打开一封邮件时,便会构建邮件详情界面组件的对象B,并将其作为任务的栈顶组件与用户进行交互,此时,任务的界面组件栈中的元素从底到顶依次是:邮件列表界面组件的对象A,邮件详情界面组件的对象B。

3)与之类似,当选择回复邮件时,会构建并进入写邮件界面组件的对象C,此时的栈顶组件变成了C。

4)而用户在写邮件时,可能需要选择收件人,这时候,联系人选择的界面组件对象D会被构造。联系人选择组件,属于联系人应用,它会在联系人应用进程中被构造运行。此时,任务的组件栈从底到顶依次包含了A、B、C、D四个组件对象,跨越两个应用两个进程。

5)当用户完成联系人选择后,联系人选择界面组件会将选择的联系人信息传递给其调用组件C,并退出该任务的界面组件栈、销毁该对象。此时,写邮件界面组件C重新回到栈顶与用户进行交互。

6)用户完成发送邮件的操作后,写邮件界面组件C退栈,回到邮件详情界面组件B。

7)当处理完该邮件所有相关操作后,用户离开邮件详情界面,回到邮件列表界面组件A。

8)在浏览处理完所有邮件后,用户退出邮件列表界面,此时,列表组件A退栈,任务的界面组件栈被清空,任务结束。

5.4 任务和界面组件栈 - 图1

图 5-11 任务和界面组件栈示例

5.4.1 界面组件的运行模式

每个任务都由一个界面组件栈构成,在标准的栈模型下,组件对象只能够进行进栈和出栈操作。虽然在大部分场景下,这能够满足界面组件调用的需求,但也可能存在一些问题:

1)对象数量过多,内存开销太大。在界面组件栈中,每一个栈中的元素,都是一个界面组件的对象。在标准的栈模型下,每一次组件的调用,都会产生一个新的对象。如果反复调用一个界面组件就会产生该组件的多个对象,从而耗费大量的内存空间。

2)调用模型过于简单,不在栈中相邻的组件对象,无法相互调用。比如,位于组件栈顶的组件对象如果想将数据传递给栈底的元素,只能通过栈中间的各个组件逐级传递,增加了耦合性和复杂性,耗费了时间和空间。

为了解决这个问题,Android为界面组件提供了可配置的运行模式(Launch Mode)。在不同的运行模式下,该界面组件对象的任务组件栈会有所变化。开发者可以通过android:launchMode属性来改变该组件对象的运行模型,比如:


<activity android:lauchMode="singleTop"/>


Android界面组件的运行模式,包括:standard、singleTop、singleTask、singleInstance。在默认情况下,界面组件的运行模式是standard。在不同的运行模式下,组件所在的任务和界面组件栈都略有不同,最重要的区别有两点[1]

❑组件对象复用

在standard模式下,系统每次调用组件时都会产生新的组件对象,如果对象占用空间太多,可能导致系统内存开销过大,降低整体的运行效率。而在其他界面组件的运行模式下,系统都会不同程度地复用已有的组件对象。比如在singleTop模式下,如果被调用的界面组件与该任务栈顶组件的类型一致,那么系统就不会再为该界面组件构造新的对象,而是通过Activity.onNewIntent函数,将调用者发出的Intent对象传递给栈顶的组件对象。

如图5-12所示,如果ABC三种界面组件的运行模式都为standard,那么按照图示顺序进行组件调用后,界面组件栈为ABCBB;而如果界面组件B的运行模式为singleTop,同样的调用关系下界面组件栈为ABCB,栈顶组件对象B,在最后一次组件调用中被复用了。

singleTop模式适合于与用户交互时保持信息更新的界面组件,比如浏览器的书签界面,联系人搜索结果界面,等等。

运行模式为singleTop的界面组件仅在作为栈顶组件时才可能被复用,因此,同一界面组件在系统中还是会存在多个组件对象。而运行模式为singleTask或singleIntance的界面组件,在整个系统中仅会有一个组件对象存在,所有对该组件的调用,都会交由该对象来处理,来进一步节省内存开销。

如果运行模式不是默认的standard模式,那该组件需要重载Activity.onNewIntent函数来处理组件调用请求,常见的编程模式如下:


protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

//处理收到的Intent

handleIntent(getIntent());

}

protected void onNewIntent(Intent intent){

//修改现有的Intent,并同样处理收到的intent对象

setIntent(intent);

handleIntent(intent);

}


❑任务的跳转

如果一个组件的运行模式为standard或者是singleTop,那么对它的调用,默认不会引起任务的切换,而如果其运行模式为singleTask或singleInstance的界面组件,其行为恰恰相反。换句话说,运行模式为singleTask或singleInstance的界面组件,其组件对象仅可能作为任务的根组件而存在,而在standard或singleTop模式下,组件对象则可能存在与界面组件栈的任何位置。

如图5-12所示,当B组件的模式为singleTask或singleInstance时,所有对B组件的调用,都会跳转到以B组件对象为根的任务中进行,而与调用组件所在的任务毫无关联。通过增加任务数量和切换执行任务,从一定程度上改变了基于栈的调用模式。比如,如果B组件的运行模式为singleTask,当C组件再次调用B组件时,会直接将以B组件对象为根的任务界面组件栈清空,回到其根组件B。也就是说,原有的由ABC到ABCB的调用次序,变成了从ABC回到AB的次序,从最终效果呈现上,改变了栈形式的调用结构。

singleTask和singleInstance唯一的不同是:模式为singleInstance的组件,其所在的任务中包含且只包含一个该组件对象,不会再有其他的组件对象;而singleTask模式的组件,在其任务中则可能包含多个其他相关的组件对象。

singleTask和singleInstance适用于消耗内存较多的单实例界面组件,比如浏览器界面、音乐播放器界面等。

5.4 任务和界面组件栈 - 图2

图 5-12 不同运行模型下的任务和界面组件栈

对于开发者而言,需要根据组件的使用场景,合理地配置组件的运行模型,一方面是为了提高系统资源的利用率,另一方面也是为了保证组件间的切换逻辑更为合理。

小贴士 除了可以通过组件的配置项来设置组件的运行模式,还可以通过Intent对象的flags标志位进行设定。比如,Activity.FLAG_ACTIVITY_SINGLE_TOP标志位的作用是将实现组件作为singleTop模式进行调用,Activity.FLAG_ACTIVITY_NEW_TAST标志位是将实现组件作为singleTask模式进行调用。

[1]本部分内容参考了SDK文档:http://developer.android.com/guide/topics/fundamentals.html#lmodes,但是与文档中对不同运行模式组件的归类有区别。