细谈Activity四种启动模式

细谈Activity四种启动模式nbsp nbsp nbsp nbsp 嗨大家好 又和大家见面了 上一次我们一起搞清楚了 Handler 的源码机制 现在回想起来是不是感觉也就那么回事 当时看的头皮发麻 今天我们谈一谈我们在 Android 开发中必不可缺少的一个组件 Activity nbsp nbsp nbsp nbsp Activity 作为四大组件之一 也可以说是四大组件中最重要的一个组件 它负责 App 的视图 还负责用户交互 而且有时候还经常其他组件绑定使用 可以说非常的重要 nbsp

    嗨大家好,又和大家见面了,上一次我们一起搞清楚了Handler的源码机制(现在回想起来是不是感觉也就那么回事,当时看的头皮发麻-。+!!),今天我们谈一谈我们在Android开发中必不可缺少的一个组件——Activity:

    Activity作为四大组件之一,也可以说是四大组件中最重要的一个组件,它负责App的视图,还负责用户交互,而且有时候还经常其他组件绑定使用,可以说非常的重要。

    虽然说我们天天都在使用Activity,但是你真的对Activity的生命机制烂熟于心,完全了解了吗?的确,Activity的生命周期方法只有七个(自己数-。+),但是其实那只是最平常的情况,或者说是默认的情况。也就是说在其他情况下,Activity的生命周期可能不会是按照我们以前所知道的流程,这就要说到我们今天的重点了——Activity的启动模式:我们的Activity会根据自身不同的启动模式,自身的生命周期方法会进行不同的调用。

一、在将启动模式之前必须了解的一些知识:

    在正式的介绍Activity的启动模式之前,我们首先要了解一些旁边的知识,这些知识如果说模糊不清,那么在讨论启动模式的时候会一头雾水(笔者亲身感悟)。

1.一个应用程序通常会有多个Activity,这些Activity都有一个对应的action(如MainActivity的action),我们可以通过action来启动对应Activity(隐式启动)。

 
  

2.一个应用程序可以说由一系列组件组成,这些组件以进程为载体,相互协作实现App功能。

3.任务栈(Task Stack)或者叫退回栈(Back Stack)介绍:

3.1.任务栈用来存放用户开启的Activity。

3.2.在应用程序创建之初,系统会默认分配给其一个任务栈(默认一个),并存储根Activity。

3.3.同一个Task Stack,只要不在栈顶,就是onStop状态:

细谈Activity四种启动模式

3.4.任务栈的id自增长型,是Integer类型。

3.5.新创建Activity会被压入栈顶。点击back会将栈顶Activity弹出,并产生新的栈顶元素作为显示界面(onResume状态)。

3.6.当Task最后一个Activity被销毁时,对应的应用程序被关闭,清除Task栈,但是还会保留应用程序进程(狂点Back退出到Home界面后点击Menu会发现还有这个App的框框。个人理解应该是这个意思),再次点击进入应用会创建新的Task栈。

4.Activity的affinity:

4.1.affinity是Activity内的一个属性(在ManiFest中对应属性为taskAffinity)。默认情况下,拥有相同affinity的Activity属于同一个Task中。

4.2.Task也有affinity属性,它的affinity属性由根Activity(创建Task时第一个被压入栈的Activity)决定。

4.3.在默认情况下(我们什么都不设置),所有的Activity的affinity都从Application继承。也就是说Application同样有taskAffinity属性。

 
   

4.4.Application默认的affinity属性为Manifest的包名。

暂时就是这么多了,如果还有不妥的地方我会补充的。接下来我们来正式看Activity的启动模式:

二、Activity启动模式:

1.默认启动模式standard:

细谈Activity四种启动模式

上面这张图讲的已经很清楚了,我想应该不用做什么实验来论证了吧,这个是最简单的一个,我们过。

2.栈顶复用模式singleTop:

    在该模式下,如果栈顶Activity为我们要新建的Activity(目标Activity),那么就不会重复创建新的Activity。



细谈Activity四种启动模式

这次我来用代码举例:


  
     
     
     
     
     

     
  
     
     
     
     
      
       
        
         
          
          
         
        
        
         
          
          
         
        
        
         
          
          
         
        
       
     

这是我的第一个应用OneText的Mainfest结构,里面创建了三个Activity,我们把第二个Activity的模式设置为singleTop。

每个Activity界面都只有一个显示当前界面名称的TextView和一个用来组跳转的Button,所以应用OneText的功能就是从活动1跳转到活动2,活动2继续跳转活动2,代码就不给大家展示了,都能写出来。

细谈Activity四种启动模式

我们发现在我们跳转到TwoActivity之后,点击跳转新的TwoActivity时候,他没有响应。

为了作对比,我们再把TwoActivity设置为standard,看一看效果:

细谈Activity四种启动模式

我们发现创建了很多的TwoActivity。

同时我们打印上task的Id(我没有把所有周期方法都打印log):

细谈Activity四种启动模式

发现他们全部都是来自一个Task。这个可以过。

应用场景:

开启渠道多,适合多应用开启调用的Activity:通过这种设置可以避免已经创建过的Activity被重复创建(多数通过动态设置使用,关于动态设置下面会详细介绍)

3.栈内复用模式singleTask:

  1. 将task内的对应Activity实例之上的所有Activity弹出栈。
  2. 将对应Activity置于栈顶,获得焦点。

细谈Activity四种启动模式

同样我们也用代码来实现一下这个过程:

还是刚才的那一坨代码,只是我们修改一下Activity1的模式为singleTask,然后让Activity2跳转到Activity3,让Activity3跳转到Activity1:

细谈Activity四种启动模式

在跳回MainActivity之后点击back键发现直接退出引用了,这说明此时的MainActivity为task内的最后一个Activity。所以这个模式过。

应用场景:

程序主界面,我们肯定不希望主界面被多创建,而且在主界面退出的时候退出整个App是最好的设想。

耗费系统资源的Activity:对于那些及其耗费系统资源的Activity,我们可以考虑将其设为singleTask模式,减少资源耗费(在创建阶段耗费资源的情况,个人理解-。+)。

4.全局唯一模式singleInstance:

    这是我们最后的一种启动模式,也是我们最恶心的一种模式:在该模式下,我们会为目标Activity分配一个新的affinity,并创建一个新的Task栈,将目标Activity放入新的Task,并让目标Activity获得焦点。新的Task有且只有这一个Activity实例。       如果已经创建过目标Activity实例,则不会创建新的Task,而是将以前创建过的Activity唤醒(对应Task设为Foreground状态)

细谈Activity四种启动模式

我们为了看的更明确,这次不按照上图的步骤设计程序了(没错,这几张图都不是我画的-。+!)。

我们先指定一下这次的程序:还是这三个Activity,这次Activity3设置为singleInstance,1和2默认(standard)。

然后我们看一下这个效果:

细谈Activity四种启动模式

说一下我们做了什么操作:

首先由1创建2,2创建3,然后又由3创建2,2创建3,3创建2,然后一直back,图如下:

细谈Activity四种启动模式

还请各位别嫌弃我-。+,图虽然不好看,但是很生动形象。。。。具体说一下:这张图对应着我们上面的程序流程,黄色的代表Background的Task,蓝色的代表Foreground的Task。

我们发现back的时候会先把Foreground的Task中的Activity弹出,直到Task销毁,然后才将Background的Task唤到前台,所以最后将Activity3销毁之后,会直接退出应用。

但是有没有想过这样会出现一个问题,什么问题我们直接看图就好:

细谈Activity四种启动模式

我简单说一下这个案例:1,2,3三个Activity,2是singleInstance模式,然后1->2,2->3,之后狂点back,在回到Home界面后点击菜单键,发现首先启动的是2Activity。

简单解释一下:1和3是一个task,2是单独的一个task,在我们2->3后,前台的task又从2的回到了1和3的。所以最后退出的task是2的线程,而如果不是重新启动App。上一次最后关闭的Task是2的,所以。。

以上表示我们关于四种模式的最基本理解,其实有了前面的知识了解之后,我们发现这些其实也不是很难对吧。。。真正比较绕的在后面-。+,注意前方高能:

三、动态设置启动模式

    在上述所有情况,都是我们在Manifest中设置的(通过launchMode属性设置),这个被称为静态设置(我们写程序写多了会发现有静态就有动态,而且静态多数在xml设置,动态在java代码设置-。+),接下来我们来看一下如何动态的设置Activity启动方式。

注):如果同时有动态和静态设置,那么动态的优先级更高。

1.关于动态设置与静态设置的理解:

    关于这个理解我是看过一篇文章,比较认同里面的思想,所以在这里也总结一下:

    静态设置,可以理解为通知别人:就是当我被创建的时候,我告诉你我是通过这种模式启动的。

    动态设置,可以理解为别人的要求:别人给我设一个Flag,我就以这种Flag的方式启动。

    可能这个没什么用哈,但是仔细想一下这种对程序的思想理解应该是正确的。

2.几种常见的Flag:

    我们说的动态设置,其实是通过Intent。对与Intent这个类大家应该不陌生吧,我们刚才在启动Activity的时候就用到这个类了。

如果我们要设置要启动的Activity的启动模式的话,只需要这样:

intent.setFlags(、、、、、);

然后在里面添加对应的Flag就好,那么接下来我们介绍几个常见的Flag(他的Flag太多了,头皮发麻。):

2.1._NEW_TASK

他对应的Flag如下:

Intent.FLAG_ACTIVITY_NEW_TASK

这个Flag跟我们的singleInstance很相似:在给目标Activity设立此Flag后,会根据目标Activity的affinity进行匹配:如果已经存在与其affinity相同的task,则将目标Activity压入此Task。    反之没有的话,则新建一个task,新建的task的affinity值与目标Activity相同。然后将目标Activity压入此栈。

其实简单来说,就是先看看需不需要创建一个新的Task,根据就是有没有相同的affinity。然后把Activity放进去。

但是此情况和singleInstance有不同,有两点注意的地方:

  1. 新的Task没有说只能存放一个目标Activity。只是说决定是否新建一个Task。而singleInstance模式下新的Task只能放置一个目标Activity。
  2. 在同一应用下,如果Activity都是默认的affinity,那么此Flag无效。而singleInstance默认情况也会创建新的Task。

这个东西理解起来可能有一些抽象,我们通过一个实例来证明他:

在之前的一些例子中,我们都是在同一应用之间进行跳转,而现在我们进行不同App的Activity相互跳转(其实就是创造一个不同taskAffinity的情况。。。忘了的话见一、4)。

首先,我们需要创建一个新的App——TwoApp,这个App目前只需要一个MainActivity就够了,我们在MainActivity放置一个button,让他跳转到OneApp的TwoActivity。

public void onClick(View v) {
    Intent intent = new Intent("ONETEXT_TWOACTIVITY");
    
    startActivity(intent);
}

这是跳转的代码。

现在我们先概述一下我们的流程:我们先打开TwoApp,然后在TwoApp的MainActivity界面跳转到OneApp的TwoActivity。

对于OneApp的设定,我们已经将三个Activity都设置成了standard模式。还是1->2,2->3,3->2。

代码就不上了,这么简单,大家自己也能写出来。效果如下:

为了看的清清楚楚,最开始清空了所有的进程。细谈Activity四种启动模式

现在我们点开TwoApp,细谈Activity四种启动模式

现在只有TwoApp一个进程,

现在我们点开了 OneApp的TwoActivity,但是我们发现他还是只有一个进程,

细谈Activity四种启动模式

现在我们在TwoApp的MainActivity跳转到OneApp的TwoActivity,添加_NEW_TASK的Flag。

public void onClick(View v) {
    Intent intent = new Intent("ONETEXT_TWOACTIVITY");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

我们再看一下效果,还是跟刚才一样先清空所有的进程,这次效果直接连起来看了:

细谈Activity四种启动模式

与上面不同的地方在于,我们新的界面创建在了新的进程中——其实就是OneApp被唤醒了,我们来分析一下为什么会这样:

首先我们会想一下我们上面所学过的一个东西,affinity:我们说这个东西在默认情况下就是App的包名packageName,而OneApp中的TwoActivity默认的affinity就是OneApp的包名。这里能够理解吧。

然后我们说_NEW_TASK情况下,会先查找是否有对应的affinity的task,如果有就不在创建,直接将其放入,反之新建task,所以新建的task就是我们的OneApp的task,我们可以再做一个测试,我们先唤醒OneApp,然后再让TwoApp跳转到OneApp的TwoActivity(有点绕啊。。。),我们看是什么情况:

首先启动OneApp,并跳转到ThreeActivity。细谈Activity四种启动模式

然后启动TwoApp,并跳转到OneApp的TwoActivity。细谈Activity四种启动模式

然后一直点击Back,细谈Activity四种启动模式

我们发现在Two中唤醒One的TwoActivity,同样是被放入了OneApp的默认Task中。

关于_NEW_TASK我们就说这么多吧。

2.2._SINGLE_TOP

该模式比较简单,对应Flag如下:

Intent.FLAG_ACTIVITY_SINGLE_TOP

次Flag与静态设置中的singleTop效果相同,所以请见二、2.

2.3._CLEAR_TOP

这个模式对应的Flag如下:

Intent.FLAG_ACTIVITY_CLEAR_TOP

当设置此Flag时,目标Activity会检查Task中是否存在此实例,如果没有则添加压入栈,

如果有,就将位于Task中的对应Activity其上的所有Activity弹出栈,此时有以下两种情况:

  • 如果同时设置Flag_ACTIVITY_SINGLE_TOP,则直接使用栈内的对应Activity,
  • 没有设置。。。。。。。,则将栈内的对应Activity销毁重新创建。

关于这个Flag,我们发现他和singleTask很像,准确的说,是在_CLEAR_TOP和_SINGLE_TOP同时设置的情况下,就是singleTask模式。

而唯一不同的一点就在于:他会销毁已存在的目标实例,再重新创建。这个我们通过打印一下生命周期就好。

这次我们只用OneApp就好了,还是1->2,2->3,3->2,这次我们将2的Flag设置为_CLEAR_TOP,看一下TwoActivity的生命周期。

细谈Activity四种启动模式

我们的流程如下:1->2       2->3    3->2     2->3    3->2    back    back   然后就退出了,这说明在Task内2上面的3的确被弹出栈了。

然后我们再看一下2的日志:

细谈Activity四种启动模式

我想在日志图片上面标注的很清楚了,我只截取了一部分日志,我们质疑3->2时候先销毁,后创建。

好,现在我们同时加上_SINGLE_TOP的Flag。

效果相同,我们只看log:

细谈Activity四种启动模式

很明显,在3->2的时候,TwoActivity调用了onRestart方法,也就是栈顶复用了。

这个Flag过。

其实还有一点点东西想提一下的,但是感觉 篇幅已经很恶心了。。。。再写下去可能更没人看了吧/笑着哭,所以决定找机会穿插到下一篇文章中:Activity标签中task相关属性

喜欢的话可以关注一波,有建议和不同观点的欢迎下方留言!感谢大家的支持!




版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/217856.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午8:36
下一篇 2026年3月18日 上午8:37


相关推荐

  • 怎样把cursor卸载干净,重新配置

    怎样把cursor卸载干净,重新配置

    2026年3月16日
    2
  • mqttnet 详解_vs2017通过mqttnet创建mqtt服务端 客户端

    mqttnet 详解_vs2017通过mqttnet创建mqtt服务端 客户端服务端:usingMQTTnet;usingMQTTnet.Server;usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Diagnostics;usingSystem.IO;usingSystem.Linq;usingSyst…

    2022年6月25日
    42
  • 用ESC/P控制码,怎样向喷墨打印机传送图形数据啊,包括颜色信息?

    用ESC/P控制码,怎样向喷墨打印机传送图形数据啊,包括颜色信息?

    2021年7月28日
    54
  • Tween 问题

    Tween 问题最近在研究 AS3 的 Tween 类 其程序结构和 AS2 的基本一样 不同的是事件绑定机制 不过我遇到了一个很奇怪的问题 写个帖子和大家分享一下体会 核心代码如下 30 帧每秒 importflash events MouseEvent importfl transitions

    2026年3月16日
    2
  • 数列所有公式大全_splay树

    数列所有公式大全_splay树请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线 _ 表示实际输入文件中的空格)输入格式第 1 行包含两个数 N 和 M,N 表示初始时数列中数的个数,M 表示要进行的操作数目。第 2 行包含 N 个数字,描述初始时的数列。以下 M 行,每行一条命令,格式参见问题描述中的表格。输出格式对于输入数据中的 GET-SUM 和 MAX-SUM 操作,向输出文件依次打印结果,每个答案(数字)占一行。数据范围与约定你可以认为在任何时刻,数列中至少有 1 个数。输入

    2022年8月10日
    8
  • python 网格搜索_Python机器学习笔记:Grid SearchCV(网格搜索)

    python 网格搜索_Python机器学习笔记:Grid SearchCV(网格搜索)在机器学习模型中 需要人工选择的参数称为超参数 比如随机森林中决策树的个数 人工神经网络模型中隐藏层层数和每层的节点个数 正则项中常数大小等等 他们都需要事先指定 超参数选择不恰当 就会出现欠拟合或者过拟合的问题 而在选择超参数的时候 有两个途径 一个是凭经验微调 另一个就是选择不同大小的参数 带入模型中 挑选表现最好的参数 微调的一种方法是手工调制超参数 直到找到一个好的超参数组合 这么做的话会

    2026年3月26日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号