美文网首页安卓集中营Android 安卓技术分享Android那些事
我打赌你一定没搞明白的Activity启动模式

我打赌你一定没搞明白的Activity启动模式

作者: 尹star | 来源:发表于2016-05-31 01:02 被阅读16139次

    一个应用程序当中通常都会包含很多个Activity,每个Activity都是一个具有特定的功能,并且可以让用户进行操作的组件。另外,Activity之间可以相互启动,当前应用的Activity甚至可以去启动其他应用的Activity。比如你的应用希望去发送一封邮件,你就可以定义一个具有"send"动作的Intent,并且传入一些数据,如对方邮箱地址、邮件内容等。这样,如果另外一个应用程序中的某个Activity声明自己是可以响应这种Intent的,那么这个Activity就会被打开。当邮件发送之后,按下返回键仍然还是会回到你的应用程序当中,这让用户看起来好像刚才那个编写邮件的Activity就是你的应用程序当中的一部分。所以说,即使有很多个Activity分别都是来自于不同应用程序的,Android系统仍然可以将它们无缝地结合到一起。那这一切是怎么实现的呢?这就要讲到本文要介绍的Activity任务栈以及Activity启动模式了。

    任务栈是什么

    任务栈Task,是一种用来放置Activity实例的容器,他是以栈的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈和出栈,其所存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。

    启动一个Application的时候,系统会为它默认创建一个对应的Task,用来放置根Activity。默认启动Activity会放在同一个Task中,新启动的Activity会被压入启动它的那个Activity的栈中,并且显示它。当用户按下回退键时,这个Activity就会被弹出栈,按下Home键回到桌面,再启动另一个应用,这时候之前那个Task就被移到后台,成为后台任务栈,而刚启动的那个Task就被调到前台,成为前台任务栈,Android系统显示的就是前台任务栈中的Top实例Activity。

    任务栈的作用

    以往基于应用(application)的程序开发中,程序具有明确的边界,一个程序就是一个应用,一个应用为了实现功能可以采用开辟新线程甚至新进程来辅助,但是应用与应用之间不能复用资源和功能。而Android引入了基于组件开发的软件架构,虽然我们开发android程序,仍然使用一个apk工程一个Application的开发形式,但是对于Aplication的开发就用到了Activity、service等四大组件,其中的每一个组件,都是可以被跨应用复用的,这就是android的神奇之处。虽然组件可以跨应用被调用,但是一个组件所在的进程必须是在组件所在的Aplication进程中。由于android强化了组件概念,弱化了Aplication的概念,所以在android程序开发中,A应用的A组件想要使用拍照或录像的功能就可以不用去针对Camera类进行开发,直接调用系统自带的摄像头应用(称其B应用)中的组件(称其B组件)就可以了,但是这就引发了一个新问题,A组件运行在A应用中,B组件运行在B应用中,自然都不在同一个进程中,那么从B组件中返回的时候,如何实现正确返回到A组件呢?Task就是来负责实现这个功能的,它是从用户角度来理解应用而建立的一个抽象概念。因为用户所能看到的组件就是Activity,所以Task可以理解为实现一个功能而负责管理所有用到的Activity实例的栈。

    栈是一个先进后出的线性表,根据Activity在当前栈结构中的位置,来决定该Activity的状态。正常情况下,当一个Activity启动了另一个Activity的时候,新启动的Activity就会置于任务栈的顶端,并处于活动状态,而启动它的Activity虽然成功身退,但依然保留在任务栈中,处于停止状态,当用户按下返回键或者调用finish()方法时,系统会移除顶部Activity,让后面的Activity恢复活动状态。当然,世界不可能一直这么“和谐”,可以给Activity设置一些“特权”,来打破这种“和谐”的模式,这种特权,就是通过在AndroidManifest文件中的属性andorid:launchMode来设置或者通过Intent的flag来设置的,下面就先介绍下Activity的几种启动模式。

    standard

    默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。应用场景:绝大多数Activity。

    standard.png

    如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。

    singleTop

    栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。应用场景:在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。

    2(1).png

    同standard模式,如果是外部程序启动singleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。

    singleTask

    栈内复用模式, activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent() 方法,并且清空这个activity任务栈上面所有的activity。应用场景:大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。
    在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。

    1:假如目前有个任务栈T1中的情况是ABC,这个时候ActivityD以singleTask模式请求启动,其所需要的任务栈正是T1,则系统会直接创建D的实例并将其入栈到T1中。

    singleTask1.png

    2:假如DActivity启动所需要的任务栈为T2,由于T2和D的实例均不存在,那么系统会先创建任务栈T2,然后再创建D的实例并将其入栈到T2中。我们可以通过设置Activity的taskAffinity属性来模拟这一场景。

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

    3:如果D所需的任务栈为T3,并且当前任务栈T3的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent()方法,同时由于singleTask默认具有ClearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终T3的情况为AD。

    singleTask3.png

    4:假如目前有两个任务栈,前台任务栈T4的情况为AB,后台任务栈t4里存有CD,假设CD的启动模式均为singleTask,现在由B去启动D,那么整个后台任务都会被切换到前台,这个时候整个栈就变成了ABCD。

    singleTask4.png

    5:假如上面的其他条件不变,B启动的是C而不是D,那么整个栈的情况就变成了ABC,因为D在C上面,会被清理出栈。

    singleTask5.png

    singleInstance

    单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。

    范冰冰.pic.jpg

    设置Intent的Flag

    系统提供了两种方式来设置一个Activity的启动模式,除了在AndroidManifest文件中设置以外,还可以通过Intent的Flag来设置一个Activity的启动模式,下面我们在简单介绍下一些Flag。

    FLAG_ACTIVITY_NEW_TASK

    使用一个新的Task来启动一个Activity,但启动的每个Activity都讲在一个新的Task中。该Flag通常使用在从Service中启动Activity的场景,由于Service中并不存在Activity栈,所以使用该Flag来创建一个新的Activity栈,并创建新的Activity实例。

    FLAG_ACTIVITY_SINGLE_TOP

    使用singletop模式启动一个Activity,与指定android:launchMode=“singleTop”效果相同。

    FLAG_ACTIVITY_CLEAR_TOP

    使用SingleTask模式来启动一个Activity,与指定android:launchMode=“singleTask”效果相同。

    FLAG_ACTIVITY_NO_HISTORY

    Activity使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Activity栈中。

    LaunchMode与StartActivityForResult

    我们在开发过程中经常会用到StartActivityForResult方法启动一个Activity,然后在onActivityResult()方法中可以接收到上个页面的回传值,但你有可能遇到过拿不到返回值的情况,那有可能是因为Activity的LaunchMode设置为了singleTask。5.0之后,android的LaunchMode与StartActivityForResult的关系发生了一些改变。两个Activity,A和B,现在由A页面跳转到B页面,看一下LaunchMode与StartActivityForResult之间的关系:

    before5.0.png after5.0.png

    这是为什么呢?

    这是因为ActivityStackSupervisor类中的startActivityUncheckedLocked方法在5.0中进行了修改。在5.0之前,当启动一个Activity时,系统将首先检查Activity的launchMode,如果为A页面设置为SingleInstance或者B页面设置为singleTask或者singleInstance,则会在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK标志,而如果含有FLAG_ACTIVITY_NEW_TASK标志的话,onActivityResult将会立即接收到一个cancle的信息,而5.0之后这个方法做了修改,修改之后即便启动的页面设置launchMode为singleTask或singleInstance,onActivityResult依旧可以正常工作,也就是说无论设置哪种启动方式,StartActivityForResult和onActivityResult()这一组合都是有效的。所以如果你目前正好基于5.0做相关开发,不要忘了向下兼容,这里有个坑请注意避让。

    总结

    实际开发过程中如果采用比较合理的Activity启动模式来做好任务栈的管理,可以事半功倍。在launchMode的选择上首先要搞清楚当前的Activity的作用,以及实际使用场景来做出合理选择。关于Activity任务栈的相关知识,短短一篇文章也很难涵盖的全,如果想了解更多相关知识,可以去有心课堂(stay4it.com)看我的视频课程。

    本文参考阅读:

    Android开发艺术探索
    Android内核剖析
    Android群英传
    http://droidyue.com/blog/2015/08/16/dive-into-android-activity-launchmode/
    http://blog.csdn.net/dliyuedong/article/details/47172143

    相关文章

      网友评论

      • 33115c73c30a:Android 5.0之后standard启动模式,跨应用启动还是同一个Task任务栈,并不是新建个任务栈,文章里说的是抄的,假的,希望作者以后验证下,再发表 。。。。
        33115c73c30a:@starsight 是啊, 错误的
        starsight:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0520/2897.html 这个链接好像也说的是在不同的任务栈……
      • wenzhihao123:“我打赌”,这个标题起的吊的一匹~
      • 3a9bb1e9e436:我认为还是看官方的文档比较好
        https://developer.android.com/guide/components/tasks-and-back-stack.html?hl=zh-cn
        这个会清晰很多
      • zkxok:灰常好,解释了我以前的很多疑惑
      • d6dbe67eb42d:对于作者能写这么多,我心怀敬意,但是我想问,系统开启任务栈是为了向其里面存放应用,这点我有点疑问,一般我认为一个应用就是一个进程,按照你的意思就是换一句话任务栈里面放进程?我想作者是否在调研调研可好
        011f59b2b0a5::sweat: 是谁告诉你任务栈是放应用的?
      • 2c24f281b158:“FLAG_ACTIVITY_CLEAR_TOP
        使用SingleTask模式来启动一个Activity,与指定android:launchMode=“singleTask”效果相同。”

        写项目的时候正好用到,发现还是有区别的。
        如果Task里面有ABCD四个Activity,A在AndroidManifest.xml里配置启动模式为singleTask,从D启动A,会调用A的onNewIntent()方法。
        但是,从D启动A,使用FLAG_ACTIVITY_CLEAR_TOP方式,并不会调用A的onNewIntent()方法。
        05952020bf36:对,是有区别的。只设置FLAG_ACTIVITY_CLEAR_TOP时,会销毁Activity重建,并且不会执行onNewIntent()方法。但是,如果同时设置FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_SINGLE_TOP时,与指定"singleTask"效果相同。
      • Focus19:在第四种情况下,引用的原文"4:假如目前有两个任务栈,前台任务栈T4的情况为AB,后台任务栈t4里存有CD......",前台后台都是T4,这里有些不明白,一个任务栈还能分成两份吗?
        Focus19:@栗子酱plus 谢谢,这样看来原文就是正确的了!
        CokeNello: @天道酬勤1919 楼主原文说的是,T4为前台,t4为后台,一个大写的,一个小写的,两个不同的任务栈。
      • ea135cb3aa51:想问一下:如果activity A是以singleInstance打开的,那么在A中打开的其他activity会在哪个任务栈呢?
        ea135cb3aa51:@志难酬 thanks:grin:
        _Chance:总之,A 的启动情况需要根据 A 的启动模式、启动 A 的 Activity 的启动模式、 A 的 taskAffinity 属性、安卓版本以及当前 Task 是否存在 Activity A 的其他实例等因素综合考虑。这一块要彻底弄清还是要花点时间实践一下的。不同版本导致程序产生不同行为这一点还是挺烦人的。说到这还要提一下,在 Service 中启动一个 Activity 在 Android 7.0 及以上是可以正常运行的,系统貌似会自动帮你添加 FLAG_NEW_TASK 这个 flag,之前的版本得手动添加,否则就会就会报错。
        _Chance:分情况讨论:
        1.如果在 Android 4.4 及以下:
        无论是 startActivity 还是 startActivityForResult 启动其他的 Activity,首先一定不会在 A 所在的 Task 打开 ;并且一定会在要启动那个 Activity taskAffinity 属性指定的 Task 中启动,如果不存在或者存在但是那个 Task 被另一个以 singleInstance 方式打开的 Activity B 占有的话就会创建一个;
        2.如果在 Android 5.0 及以上:
        如果是 startActivity 方式启动,那么就和上面一样;如果是以 startActivityForResult 方式启动的话,那么就会在 A 的 Task 中启动
      • 最最最最醉人:关于singleTask的时候,假如目前有两个任务栈,前台任务栈T4的情况为AB,后台任务栈t4里存有CD,假设CD的启动模式均为singleTask,现在由B去启动D,那么整个后台任务都会被切换到前台,这个时候整个栈就变成了ABCD。 什么时候会出现这样的应用场景呢?
      • jasonlee3652:关于文章的一部分描述是否有误,还是我理解错。你的描述如下:

        在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中...

        我使用模拟器测试,系统是6.0。APP1启动App2中的Activity。得到如下日志:
        07-19 12:45:57.921 4829-4829/com.example.lxq.myapplication D/MainActivity: Task id:134
        07-19 12:46:02.679 4829-4829/com.example.lxq.myapplication D/SecondActivity: Task id:134
        07-19 12:46:23.107 4829-4829/com.example.lxq.myapplication D/ThirdActivity: Task id:134
        07-19 12:46:26.042 4829-4829/com.example.lxq.myapplication D/FourActivity: Task id:134
        07-19 12:46:27.628 4415-4415/com.example.lxq.app2 D/SecondActivity: Task id:134

        可以说明App2的Activity还是在App1的栈顶,并没有重写创建新的Task。请问我理解错误还是您描述错了?
        Adooooo: 应该是描述错了,作者想表达的意思可能是,Activity会在两个不同的Task中,但是并不一定会启动新的Task。http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0520/2897.html
        android_cyw:启动外部Activity时设置intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);才会出现新建一个任务栈的情况,不解博主的结论。
      • 文淑:楼主太感谢了,这个问题一直困扰中我android的LaunchMode与StartActivityForResult的关系,在4.0以及5.0手机上的不同,问了很多人 都不知道。现在终于明了了。。
        尹star: @文淑 呵呵
      • hackware:singleTask的4、5根本没讲明白
      • xiawe_i:看完之后,对启动模式又清晰了许多:+1:
      • 雨齐:又是标题党
      • 李斯维:先不提这文章,单单这标题就狠狠地装了一逼~~
      • 牛牛技术:博主可否告知这些图是要什么软件画的,真棒
        尹star:@恋简 ps
      • 西门鱼:看了有心课堂的视频,温故知新。
        尹star: @木月师兄9527 👍
      • cuixbo:文中提到的防止按钮双击设置singletop很赞
        existhinker:@崔小波 第一个activity实例还没入栈,第二个实例也跟着进去了
        cuixbo:@existhinker 为什么可以打开多个
        existhinker:@崔小波 千万别这么弄,不靠谱的,照样可以打开多个页面
      • cuixbo:能讲讲文中提到的 前台任务和后台任务是怎么一个情况?
        尹star:@崔小波 目前系统中可见的这个Activity所在的任务栈为前台任务栈,其他的都是后台
      • Clare_:老司机
      • 浮华染流年:你要是把主页设置成singletask,退出的时候回有bug,应为一个引用中存在多个任务栈,主页设置成singletask只能清楚应用默认的那个任务栈的activity,不能清除其他栈的activity
        cuixbo:@浮华染流年 说的有些道理
        浮华染流年: @尹star 主页activity只是应用默认task栈的根节点
        尹star:@浮华染流年 主页是所有任务栈的根Activity,它会被压在栈低。当回到主页的时候,就clearTop了,把所有其他的Activity都出栈了,这个时候只有它自己了。安心退出应用。

      本文标题:我打赌你一定没搞明白的Activity启动模式

      本文链接:https://www.haomeiwen.com/subject/kumqdttx.html