android开发之Activity启动模式图文详解

Activity是安卓上最聪明的设计之一,优秀的内存管理让多任务完美运行在最流行的操作系统之上。并不是让Activity在屏幕上启动就完事了,其启动方式也是需要关注的。这个话题的内容很多,其中很重要的就是启动模式(launchMode)。这也是我们这篇博客要讨论的内容。

因为不同的Activity有不同的目的。有些被设计成每发送一个intent都单独一个Activity工作,比如邮件客户端中撰写邮件的Activity,而有些则被设计成单例的,比如邮件收件箱的Activity。

这就是为什么指明一个Activity是否需要新建还是使用现有Activity是很有必要的,否则可能导致糟糕的用户体验。多亏了安卓的核心工程师,让launchMode可以帮助你专门应对这种情况。

设置一个launchMode

一般地,我们可以直接在AndroidManifest.xml 标签的一个属性中设置launchMode,如下:

<activity  android:name=".SingleTaskActivity" android:label="singleTask launchMode" android:launchMode="singleTask">

有4种类型的launchMode,我们一个一个的看。

1.standard,这是默认的模式。

这种模式下,当Intent发送的时候,Activity总是被创建一个新的出来单独工作。想象一下,如果有发送10个撰写邮箱的Intent,那么将有10个不同的Activity启动。

  • 在Lollipop之前设备上的表现:

这种Activity将被创建并置于栈顶,和发送intent的Activity处于同一个任务中。注:一般来讲,安卓第三个虚拟键所列出的那些就是任务。

下面的图片显示了向标准启动模式的Activity分享照片时的情况。虽然分别来自不同的应用,但仍然它会和发送intent的Activity处于同一个任务中。

注:从图中可以看出分享图片的是Gallery应用。

同时你会看到此时任务管理器是这样的(有一点怪异)。

如果我们切换到另外一个应用然后再切回到Gallery,你会发现standard launchMode启动的Activity仍然在Gallery任务的上面,导致在操作Gallery之前,我们必须首先结束这个额外的Activity。

  • 在Lollipop设备上的表现:

如果Activity都是来自同一个应用,其表现和Lollipop之前的设备一样,在任务的顶端。

android开发之Activity启动模式图文详解_第1张图片

但是如果intent来自其他应用,将创建一个新的任务,同时新创建的Activity会被作为一个根Activity,如下:

android开发之Activity启动模式图文详解_第2张图片

注:图片中的Task#2和Task#3分别表示两个任务,序号大的比序号小的后启动。

下面是任务管理器中的样子:

android开发之Activity启动模式图文详解_第3张图片

发生这种情况的原因是Lollipop中任务管理系统做了修改,让它看起来更合理了。因为它们在不同的任务中,你可以直接切回Gallery,你还可以触发另一个Intent,创建新的与之前相同的任务。

android开发之Activity启动模式图文详解_第4张图片

撰写邮件的Activity或者发布社交网络状态的Activity都是采用这种Activity的例子。如果你希望Activity单独服务于一个Intent,就可以考虑standard启动模式。

2.SingleTop

它的表现几乎和standard模式一模一样,一个singleTop Activity 的实例可以无限多,唯一的区别是如果在栈顶已经有一个相同类型的Activity实例,Intent不会再创建一个Activity,而是通过onNewIntent()被发送到现有的Activity。

在singleTop模式下我们需要同时在onCreate() 和 onNewIntent()中处理发来的intent,以满足不同情况。

这种启动模式的用例之一就是搜索功能。假设我们创建了一个搜索框,点击搜索的时候将导航到一个显示搜索结果列表的SearchActivity中,为了更好的用户体验,这个搜索框一般也会被放到SearchActivity中,这样用户想要再次搜索就不需要按返回键。

想像一下,如果每次显示搜索结果的时候我们都启动一个新的activity,10次搜索10个activity,那样当我们想返回最初的那个activity的时候需要按10次返回。

所以我们应该这样,如果栈顶已经有一个SearchActivity,我们将Intent发送给现有的activity,让它来更新搜索结果。这样就只会有一个在栈顶的SearchActivity,只需点一次back就可以回到之前的activity。

不管怎样,singleTop和它的调用者处在一个任务中。如果你想要让intent发送给另一个任务中处于栈顶的Activity,是不行的。

而当Intent来自于另外一个应用的时候,新的Activity的启动方式和standard模式是一致的(Lollipop之前:处于调用者任务的栈顶,Lollipop后:会创建一个新的任务)。

3.singleTask

这种模式和standard以及singleTop有很大不同。singleTask模式的Activity只允许在系统中有一个实例。如果系统中已经有了一个实例,持有这个实例的任务将移动到顶部,同时intent将被通过onNewIntent()发送。如果没有,则会创建一个新的Activity并置放在合适的任务中。

  • 在同一个应用中的情况

如果系统中还没有singleTask的Activity,会新创建一个,并放在同一任务的栈顶。

android开发之Activity启动模式图文详解_第5张图片

但是如果已经存在,singleTask Activity上面的所有Activity将以合适的方式自动销毁,让我们想要显示的Activity处于栈顶。同时Intent也会通过onNewIntent()方法发送到这个singleTask Activity。

android开发之Activity启动模式图文详解_第6张图片

在用户体验方面,可能不是很合理,但是它就是这样设计的…

你可能注意到了 官方文档 中提到的一个问题:

系统会创建一个新的任务,并将这个Activity实例化为新任务的根部(root)。

但从实验结果来看,并不是这么回事。singleTask Activity仍然在任务的Activity栈顶,我们可以从dumpsys activity 命令显示上看出来:

Task id #239
  TaskRecord{428efe30 #239 A=com.thecheesefactory.lab.launchmode U=0 sz=2}
  Intent
 { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
 flg=0x10000000 
cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
    Hist #1: ActivityRecord{429a88d0 u0 com.thecheesefactory.lab.launchmode/.SingleTaskActivity t239}
      Intent { cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity }
      ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}
    Hist #0: ActivityRecord{425fec98 u0 com.thecheesefactory.lab.launchmode/.StandardActivity t239}
      Intent
 { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
 flg=0x10000000 
cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
      ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}

如果你希望singleTask Activity表现的和文档中描述的一致,你需要为singleTask Activity设置taskAffinity属性

<activity  android:name=".SingleTaskActivity" android:label="singleTask launchMode" android:launchMode="singleTask" android:taskAffinity="">

这里是启动SingleTask Activity的的结果(使用了taskAffinity之后)。

android开发之Activity启动模式图文详解_第7张图片

android开发之Activity启动模式图文详解_第8张图片

是否使用taskAffinity取决于你自己。

  • 和其他应用一起工作的情况

一旦intent是从另外的应用发送过来,并且系统中也没有任何Activity的实例,则会创建一个新的任务,并且新的Activity被作为根Activity创建。

android开发之Activity启动模式图文详解_第9张图片

除非拥有这个singleTask Activity 的应用已经存在,那样的话,新建的Activity会置于这个任务的上面(而不是新建一个任务)。

android开发之Activity启动模式图文详解_第10张图片

假设已经有了一个Activity的实例,不管它是在哪个任务中(包括上面的那种情况,在用于这个Activity的应用中),整个任务将被移到顶端,而singleTask Activity上面的所有 Activity 都将被销毁, 用户需要按back键遍历玩栈中的Activity才能回到调用者任务。

android开发之Activity启动模式图文详解_第11张图片

这种模式的应用案例有。邮件客户端的收件箱或者社交网络的时间轴。这些Activity一般不会设计成拥有多个实例,singleTask可以满足。但是在使用这种模式的时候必须要明智,因为有些Activity会在用户不知情的情况下被销毁。

3.singleInstance

这个模式非常接近于singleTask,系统中只允许一个Activity的实例存在。区别在于持有这个Activity的任务中只能有一个Activity:即这个单例本身。

不过结果却很怪异,从dumpsys提供的信息来看,似乎系统中有两个任务但任务管理器中只显示一个,即最后被移到顶部的那个。导致虽然后台有一个任务在运行,我们却无法切换回去,这一点也不科学。

下面是当singleInstance Activity被调用的同时栈中已经有一些Activity的情况下所发生的事情:

android开发之Activity启动模式图文详解_第12张图片

本来有两个任务,但是任务管理器中却只显示一个任务:

因为这个任务只有一个Activity,我们再也无法切回到任务#1了。唯一的办法是重新在launcher中启动这个应用。

不过这个问题也有解决方案,就像我们在singleTask Acvity中做的,只要为singleInstance Activity设置taskAffinity属性就可以了

<activity  android:name=".SingleInstanceActivity" android:label="singleInstance launchMode" android:launchMode="singleInstance" android:taskAffinity="">

现在科学多了。

android开发之Activity启动模式图文详解_第13张图片

这种模式很少被使用。实际使用的案例如Launcher的Activity或者100%确定只有一个Activity的应用。总之除非完全有必要,不然我不建议使用这种模式。

Intent Flags

除了在AndroidManifest.xml中直接设置launch mode,我们还可以通过叫做 Intent Flags的东西设置更多的行为,比如:

Intent intent = new Intent(StandardActivity.this, StandardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);

这段代码将会启动一个singleTop启动模式的的StandardActivity 。

有许多种Flag可以使用,更多的请参考Intent。

原文:http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en

你可能感兴趣的:(android,启动模式)