3.2.2 Activity的启动模式大全

1、启动一个Activity的几种方式

在Android中我们可以通过下面两种方式来启动一个新的Activity,注意这里是怎么启动,而非 启动模式!!分为显示启动和隐式启动!

1. 显式启动:通过包名来启动,写法如下:

①最常见的:
startActivity(new Intent(当前Act.this,要启动的Act.class));
②通过Intent的ComponentName:ComponentName cn = new ComponentName("当前Act的全限定类名","启动Act的全限定类名") ;Intent intent = new Intent() ;intent.setComponent(cn) ;startActivity(intent) ;
③初始化Intent时指定包名:Intent intent = new Intent("android.intent.action.MAIN");intent.setClassName("当前Act的全限定类名","启动Act的全限定类名");startActivity(intent);

2.隐式启动:通过Intent-filter的Action,Category或data来实现 这个是通过Intent的intent-filter来实现的。
3. 另外还有一个直接通过包名启动apk的:

Intent intent = getPackageManager().getLaunchIntentForPackage("apk第一个启动的Activity的全限定类名") ;
if(intent != null) startActivity(intent) ;

2、Activity的管理

我们来看下官方文档给出的一个流程图:


流程解析:
应用程序中存在A1,A2,A3三个activity,当用户在Launcher或Home Screen点击应用程序图标时, 启动主A1,接着A1开启A2,A2开启A3,这时栈中有三个Activity,并且这三个Activity默认在 同一个任务(Task)中,当用户按返回时,弹出A3,栈中只剩A1和A2,再按返回键, 弹出A2,栈中只剩A1,再继续按返回键,弹出A1,任务被移除,即程序退出!

3、Task的管理

Task是Activity的集合,是一个概念,实际使用的Back Stack来存储Activity,可以有多个Task,但是 同一时刻只有一个栈在最前面,其他的都在后台!那栈是如何产生的呢?

答:当我们通过主屏幕,点击图标打开一个新的App,此时会创建一个新的Task!举个例子:我们通过点击通信录APP的图标打开APP,这个时候会新建一个栈1,然后开始把新产生的Activity添加进来,可能我们在通讯录的APP中打开了短信APP的页面,但是此时不会新建一个栈,而是继续添加到栈1中,这是 Android推崇一种用户体验方式,即不同应用程序之间的切换能使用户感觉就像是同一个应用程序, 很连贯的用户体验,官方称其为seamless (无缝衔接)! 这个时候假如我们点击Home键,回到主屏幕,此时栈1进入后台,我们可能有下述几种操作:

  • 点击菜单键(正方形那个按钮),点击打开刚刚的程序,然后栈1又回到前台了! 又或者我们点击主屏幕上通信录的图标,打开APP,此时也不会创建新的栈,栈1回到前台!
  • 如果此时我们点击另一个图标打开一个新的APP,那么此时则会创建一个新的栈2,栈2就会到前台, 而栈1继续呆在后台;

后面也是这样,以此类推!

如上面所述,Android会将新成功启动的Activity添加到同一个Task中并且按照以"先进先出"方式管理多个Task 和Back Stack,用户就无需去担心Activites如何与Task任务进行交互又或者它们是如何存在于Back Stack中! 或许,你想改变这种正常的管理方式。比如,你希望你的某个Activity能够在一个新的Task中进行管理; 或者你只想对某个Activity进行实例化,又或者你想在用户离开任务时清理Task中除了根Activity所有Activities。你可以做这些事或者更多,只需要通过修改AndroidManifest.xml中 <activity>的相关属性值或者在代码中通过传递特殊标识的Intent给startActivity()就可以轻松的实现 对Actvitiy的管理了。

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

Activity的启动模式对我们来说应该是个全新的概念,在实际项目中我们应该根据特定的需求为每个Activity指定恰当的启动模式。启动模式一共有四种,分别是 standard、 singleTop、singleTask 和 singleInstance,可以在 AndroidManifest.xml 中通过给 <activity>标签指定android:launchMode 属性来选择启动模式,下面我们来逐个进行学习。

一、standard模式

standard 是Activity默认的启动模式,在不进行显式指定的情况下,所有Activity都会自动使用这种启动模式。因此,到目前为止我们写过的所有Activity都是使用的 standard 模式。经过之前的学习,我们已经知道了 Android 是使用返回栈来管理活动的,在 standard 模式(即默认情况)下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard 模式的活动,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建该Activity的一个新的实例。

也就是说在这种模式下启动的Activity可以被多次实例化,即在同一个任务中可以存在多个Activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A --> A。


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


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

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


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

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


standard6.jpg

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

二、singleTop模式

可能在有些情况下,你会觉得 standard 模式不太合理。Activity明明已经在栈顶了,为什么再次启动的时候还要创建一个新的活动实例呢?别着急,这只是系统默认的一种启动模式而已,我们完全可以根据自己的需要进行修改,比如说使用 singleTop 模式。当Activity的启动模式指定为 singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会创建新的实例,而是重用位于栈顶的那个实例, 并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中。

举例来说,如果A的启动模式为singleTop,并且A的一个实例已经存在于栈顶中, 那么再调用startActivity(new Intent(this,A.class))启动A时, 不会再次创建A的实例,而是重用原来的实例,并且调用原来实例的onNewIntent()方法。 这时任务栈中还是这有一个A的实例。如果以singleTop模式启动的Activity的一个实例 已经存在与任务栈中,但是不在栈顶,那么它的行为和standard模式相同,也会创建多个实例。


三、singleTask模式

使用 singleTop 模式可以很好地解决重复创建栈顶活动的问题,但是正如上面所说的,如果该Activity并没有处于栈顶的位置,还是可能会创建多个Activity实例的。那么有没有什么办法可以让某个Activity在整个应用程序的上下文中只存在一个实例呢?这就要借助singleTask 模式来实现了。

当Activity的启动模式指定为 singleTask,每次启动该Activity时系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有Activity统统出栈,如果没有发现就会创建一个新的Activity实例。

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


官方文档中提到的一个问题:系统会创建一个新的任务,并将这个Activity实例化为新任务的根部(root) 这个则需要我们对taskAffinity进行设置了,使用taskAffinity后的结果:


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


如果这个singleTask Activity 的应用已经存在,那么新建的Activity会置于这个任务的上面(而不是新建一个任务)。


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



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

四、singleInstance模式

singleInstance 模式应该算是四种启动模式中最特殊也最复杂的一个了, 我们也需要多花点功夫来理解这个模式。不同于以上三种启动模式,指定为 singleInstance 模式的Activity会启用一个新的返回栈来管理这个Activity(其实如果 singleTask 模式指定了不同的 taskAffinity,也会启动一个新的返回栈)。那么这样做有什么意义呢?

想象以下场景,假设我们的程序中有一个Activity是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个Activity的实例,应该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然是创建了新的实例。而使用 singleInstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用的同一个返回栈,也就解决了共享Activity实例的问题。

还有一点需要注意:当我们再次启动该Activity的实例时,会重用已存在的任务和实例,并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。


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

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


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


SingleInstance3.jpg

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

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

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

现在就变得正常多了:



这种模式很少被使用,实际使用的案例如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 。

点此进入:GitHub开源项目“爱阅”

感谢优秀的你跋山涉水看到了这里,欢迎关注下让我们永远在一起!

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

推荐阅读更多精彩内容