Activity启动模式 launchMode: standard, singleTop, singleTask and singleInstance

英文原文:Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance 另外关于启动模式还有篇很好的文章:Android中Activity四种启动模式和taskAffinity属性详解

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

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

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

设置一个launchMode

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

1.  <activity
2.      android:name=".SingleTaskActivity"
3.      android:label="singleTask launchMode"
4.      android:launchMode="singleTask">

</pre>

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

standard

这是默认的模式。

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

Lollipop之前设备上的表现

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

standardtopstandard

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

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

standardgallery2

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

gallerystandard

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

Lollipop设备上的表现

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

standardstandardl

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

standardgalleryl

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

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

gallerystandardl1

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

gallerystandardl2

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

singleTop

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

singletop

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

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

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

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

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

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

singleTask

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

在同一个应用中的情况

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

singleTask1

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

singleTaskD

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

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

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

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

1.  Task id #239
2.  TaskRecord{428efe30 #239 A=com.thecheesefactory.lab.launchmode U=0 sz=2}
3.  Intent
4.  { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
5.  flg=0x10000000 
6.  cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
7.  Hist #1: ActivityRecord{429a88d0 u0 com.thecheesefactory.lab.launchmode/.SingleTaskActivity t239}
8.  Intent { cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity }
9.  ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}
10.  Hist #0: ActivityRecord{425fec98 u0 com.thecheesefactory.lab.launchmode/.StandardActivity t239}
11.  Intent
12.  { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
13.  flg=0x10000000 
14.  cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }
15.  ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}

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

1.  <activity
2.  android:name=".SingleTaskActivity"
3.  android:label="singleTask launchMode"
4.  android:launchMode="singleTask"
5.  android:taskAffinity="">

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

singleTaskTaskAffinity
screenshot17

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

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

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

singleTaskAnotherApp1
singletaskfromapp2

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

singleTaskAnotherApp2

*In case that there is an Activity instance existed in any Task, the whole Task would be moved to top and every single Activity placed above the singleTask Activity will be destroyed with lifecycle. *If back button is pressed, user has to travel through the Activities in the stack before going back to the caller Task.

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

singleTaskAnotherApp3

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

singleInstance

这个模式非常接近于singleTask,系统中只允许一个Activity的实例存在。区别在于持有这个Activity的任务中只能有一个Activity:即这个单例本身。If another Activity is called from this kind of Activity, a new Task would be automatically created to place that new Activity. Likewise, if singleInstance Activity is called, new Task would be created to place the Activity.

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

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

singleInstance

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

singleInstances

Since this Task could has only one Activity, we couldn't switch back to Task #1 anymore. Only way to do so is to relaunch the application from launcher but it appears that the singleInstance Task would be hidden in the background instead.

因为这个任务只有一个Activity,我们再也无法切回到任务#1了。唯一的办法是重新在launcher中启动这个应用。 but之后的没有翻译,因为我也不明白作者的意思。

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

1.  <activity
2.  android:name=".SingleInstanceActivity"
3.  android:label="singleInstance launchMode"
4.  android:launchMode="singleInstance"
5.  android:taskAffinity="">

现在科学多了。

screenshot18

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

Intent Flags

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

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

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

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

希望这篇文章对你有用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342