zoukankan      html  css  js  c++  java
  • 深入理解Activity启动模式

    概述

    Android官网介绍Activity的启动模式时比较含糊,介绍Application,Activity,Task,Process,Thread等概念以及它们之间的关系时,也没有说得清楚。大家看了Android官网对Activity启动模式的介绍后,可能会觉得很困惑。官网介绍singleTask启动模式时,说只要启动singleTask启动模式的Activity就会新建Task,但在实际操作中,如果同一个应用的两个Activity,如果其中一个Activity的启动模式为singleTask,一个Activity的启动模式为standard,从其中一个Activity跳转到另外一个Activity,并不会新建Task。

    为了解除这些困惑,对Activity启动模式做了深入研究,因此写了这一系列博客,详细阐述Application,Activity,Task,Process,Thread等概念概念之间的关系,以及启动模式各自的特点,希望能对大家理解这些概念有所帮助。

    一、Application,Activity, Process,Thread之间的关系

    我们知道在AndroidManifest.xml里可声明Application,它可以由4大组件组成:Activity,Service,ContentProvider,BroadcastReceiver。声明Application时可以用android:name声明本应用使用的Application类,如果没有声明,则会直接使用Android框架的Application类建立实例对象。

    应用第一次启动时,会启动一个新进程,该进程用应用的包名作为进程名。该进程会启动主线程ActivityThread,也叫做UI线程,UI的绘制都在该线程里完成。该进程里还有一些Binder服务线程,用于和系统进行通信。

    另外,我们知道Activity跳转时,可以跨应用跳转,也就说应用app1里的Activity A可以跳转到应用app2里的Activity B。如果Activity A和Activity B的启动模式为standard模式,从A跳转到B后,Activity A和Activity B对应的ActivityRecord会放在同一个task里(ActivityRecord,Task都由系统进程管理,下一篇博客会介绍这些概念),但是Acitivity A和Activity B的实例对象会放在不同的进程里。假设app1的包名为com.cloud.app1,app2的包名为com.cloud.app2,那么Activity A的实例对象位于进程com.cloud.app1里,Activity B的实例对象位于进程com.cloud.app2里。

    也就是说,每个应用的组件都会运行在对应的应用进程里,该进程用应用的包名作为进程名。该进程里有一个主线程,也叫做UI线程,应用组件都运行在UI线程里。只有一种情况例外,如果声明组件时用android:process设置了进程名,该组件就会运行在一个新进程里,不是以应用的包名作为进程名,而是用包名+:+设置的值作为进程名

    所以一般情况下service,receiver也会运行在ui线程里,如果在service,receiver的生命周期方法里做一些耗时的操作,系统会提示ANR(Activity Not Responde)错误。

    二、 Activity,回退栈,Task之间的关系

    Activity启动时ActivityManagerService会为其生成对应的ActivityRecord记录,并将其加入到回退栈(back stack)中,另外也会将ActivityRecord记录加入到某个Task中。请记住,ActivityRecord,backstack,Task都是ActivityManagerService的对象,由system_server进程负责维护,而不是由应用进程维护。

    在回退栈里属于同一个task的ActivityRecord会放在一起,也会形成栈的结构,也就是说后启动的Activity对应的ActivityRecord会放在task的栈顶。

    假设Activity的跳转顺序:A–>B–>C,A,B,C对应的ActivityRecord属于同一个Task,此时从C跳转至D,再跳转至E,C和D不属于同一个Task,D和E属于同一个Task,那现在的back stack结构如下所示:

    backstack_base

    现在A,B,C属于task1,C在task1的栈顶,D,E属于task2,E在task2的栈顶。也可以看出来task2位于整个回退栈的栈顶,也就是说task2在task1的上面。如果此时不断按回退键,看到的Activity的顺序会是E–>D–>C–>B–>A。

    另外需注意,ActivityManagerService不仅会往回退栈里添加新的ActivityRecord,还会移动回退栈里的ActivityRecord,移动时以task为单位进行移动,而不会移动单个AcitivityRecord。还是针对上面的例子,假设此时按了Home键,那么会将Home应用程序(也叫做Launcher应用程序)的task移动至栈顶,那么此时回退栈如下所示:

    backstack_H

    可以看到Home应用程序的Activity H对应的Activity Record移动到了回退栈的栈顶。Home应用程序的Activity H对回退按键的响应做了特殊处理,如果此时按回退键,是看不到Activity E的。

    如果此时通过Launcher程序再打开Activity A所在的应用,那么会显示Activity C,因为会将Activity A对应的Activity Record所在的task移动至回退栈的栈顶,此时回退栈如下所示:

    backstack_move

    现在task1移动到了栈顶,Home应用程序的task位于task1的下面,而task2位于Home应用程序的task之下,此时如果按返回键,那么Activity的显示顺序是:C–>B–>A–>H,不会显示E。

    当然我们也可以在Launcher应用程序里打开D所在的应用,这样会将D,E所在的task2移动至栈顶。

    现在应该对task有所理解了,task其实是由ActivityRecord组成的栈,多个task以栈的形式组成了回退栈,ActivityManagerService移动回退栈里的ActivityRecord时以task为单位移动。

    我们知道跨应用跳转Activity时,两个Activity对应的ActivityRecord可属于同一个task,那什么情况下两个ActivityRecord会属于不同的task呢?或者说,Activity跳转时,什么情况下会产生新的task呢?

    这个问题和Activity的启动模式,taskAffinity,启动Activity时为Intent设置的flag等因素相关。

    先说一下taskAffinity,每个Activity的taskAffinity属性值默认为包名,也就是说如果Activity A所在的应用的包名为com.cloud.app1,那么Activity A的taskAffinity属性值为com.cloud.app1,我们可以在AndroidManifest.xml里通过android:taskAffinity属性为Activity设置特殊的taskAffinity,假设我们在AndroidManifest.xml里为Activity A设置了android:taskAffinity=”:test”,那么Activity A的taskAffinity值为com.cloud.app1:test。

    那么我现在可以明白:不同应用的Activity的taskAffinity属性值会不一样。

    假设Activity A和Activity B的启动模式都是standard,二者taskAffinity属性值不一样,从Activity A跳转至Activity B,那么它们对应的ActivityRecord会属于同一个task。

    假设Activity A的启动模式是standard,Activity B的启动模式singleTask,二者taskAffinity属性值一样,此时从Activity A跳转至Activity B,那么它们对应的ActivityRecord会属于同一个task。因为只要两个Activity的taskAffinity属性一致,即使其中有一个Activity的启动模式为singleTask,它们对应的ActivityRecord会放在同一个task里,不管是从某个Activity跳转到singleTask类型的Activity,还是从singleTask类型的Activity跳转到其他Activity都是如此,除非跳转的其他Activity的启动模式是singleInstance。这里的描述和官方文档很不一样,稍后会为大家介绍singleTask启动模式的特点。

    假设Activity A的启动模式是standard,Activity B的启动模式singleTask,二者taskAffinity属性值不 一样,此时从Activity A跳转至Activity B,那么它们对应的ActivityRecord会属于不同的Task。

    还有其他很多情况会产生新的task,大家可以看接下来关于启动模式的特点的介绍。

    三   Activity 启动模式特点

    Activity的启动模式共有4种,默认为standard,其它还有singleTop,singleTask,singleInstance,下面就这4种启动模式分别介绍它们的特点。

    • 1) standard模式

      standard模式的Activity可以有多个ActivityRecord加入不同的task,同一个task也可存在多个ActivityRecord,并且ActivityRecord还可相邻。

      假设Activity A的启动模式为standard,那么可能存在如下图所示的回退栈:

      standard

      假设Activity A启动Activity B,B的启动模式为standard模式

      B的ActivityRecord默认会放在A的ActivityRecord所在的task里,即使B和A的taskAffinity不同也会如此,这也意味着如果B和A属于不同的应用,B的ActivityRecord也会放在A的ActivityRecord所在的task里。

      但是下面两种情况不会将A和B的ActivityRecord放在同一个task里:

      如果Activity A的启动模式为singleInstance,则会查找整个回退栈,直到找到和B相关的task,然后把B的ActivityRecord放到该task里,如果没有找到相关的task,则新建task,将B的ActivityRecord放到新task里。后面会介绍如何判断Activity和某个task相关。

      如果Activity A的启动模式为singleTask,并且Activity A和Activity B的taskAffinity不一样,那么也会查找整个回退栈,直到找到和B相关的task,然后把B的ActivityRecord放到该task里。

    • 2) singleTop模式

      singleTop模式与standard模式比较相似,singleTop模式的Activity可以有多个ActivityRecord加入不同的task,同一个task也可存在多个ActivityRecord,但是同一个task的ActivityRecord不可以相邻。

      假设Activity A的启动模式为singleTop,那么如下图所示的回退栈就是不合理的:

      singleTop_bad

      但是可存在如下图所示的回退栈:

      singleTop_good

      假设Activity A启动了Activity B, 这时B在task的栈顶,B的启动模式为singleTop模式。此时从其它Activity也跳转至Activity B,并且启动的task也是已启动的A和B所在的task,或者A和 B所在的task本身就回退栈的栈顶,那么不会新建B的ActivityRecord,而是会将启动Activity B的Intent传递给栈顶Activity B的ActivityRecrod对应的在应用进程的实例对象,调用它的onNewIntent方法。

      可以这样模拟此种情况:

      假设Activity A和Activity B在同一个应用app1里,A是入口Activity,A可跳转至Activity B,B的启动模式为singleTop。此时已从A跳转至B,通知栏有一个启动B的通知,点击通知后,就出现上述情况。

    • 3) singleTask模式

      singleTask模式和standard模式,singleTop模式区别很大,singleTask模式的Activity在整个回退栈只可以有一个ActivityRecord,也就是说它只能属于某一个task,不可在多个task里存在ActivityRecord。但是在这个task里可以有其它Activity的ActivityRecord。

      假设Activity A的启动模式为singleTask,那么如下图所示的回退栈就是不合理的:

      singleTask_bad

      假设Activity A的启动模式为singleTask,那么如下图所示的回退栈就是合理的:

      singleTask_good

      假设Activity A的启动模式为singleTask,那么和Activity A的ActivityRecord放在同一个task里的ActivityRecord所对应的Activity,必须与Activity A的taskAffinity相同。也就是说,Activity A的ActivityRecord只会和同一应用的其它Activity的ActivityRecord放在同一个task里,并且这些同一应用的其它Activity不能设置特殊的taskAffinity。

      singleTask模式的Activity还有另一个特点:

      假设Activity A的启动模式是singleTask,A所在的task里,A并没有处于栈顶,此时若从别的Activity跳转至Activity A,那么A所在的task里位于A之上的所有ActivityRecord都会被清除掉。

      跳转之前回退栈的示意图如下所示:

      singeleTask_before

      此时从E跳转至A之后,回退栈的示意图如下图所示:

      afterjump

      也就是说位A所在的task里的C被清除了。

      另外需注意:

      只要两个Activity的taskAffinity属性一致,即使其中有一个Activity的启动模式为singleTask,它们对应的ActivityRecord会放在同一个task里,不管是从某个Activity跳转到singleTask类型的Activity,还是从singleTask类型的Activity跳转到其他Activity都是如此,除非跳转的其他Activity的启动模式是singleInstance。Android官方文档对singleTask启动模式的描述不准确。

      举例如下:

      假设某个应用有两个Activity A和Activity B,Activity A已启动,Activity B的启动模式为singleTask,Activity B还从未启动过,在AndroidManifest.xml里没有给这两个Activity设置特殊的taskAffinity。此时从Activity A跳转至Activity B,那么Activity B的ActivityRecord会放在Activity A的ActivityRecord所在的task里。

    • 4) singleInstance模式

      该启动模式和singleTask类似,singleInstance模式的Activity在整个回退栈只可以有一个ActivityRecord,也就是说它只能属于某一个task,不可在多个task里存在ActivityRecord,并且它所在的task不可再有其它Activity的ActivityRecord,即使是同一个应用内的其它Activity,也不可有它们的AcvitityRecord。

      假设Activity A的启动模式为singleInstance,那么如下图所示的回退栈就是不合理的:

      singleInstance_bad

      假设Activity A的启动模式为singleInstance,那么如下图所示的回退栈就是合理的:

      singleInstance_good

    启动Activity时,有时需要查看回退栈,看是否有和这个Activity相关的task。Activity和某个task相关,有两种情况(假设Activity为A,相关的task为task1):

    • 1) 如果A的启动模式为singleInstance,那么task1只能包含1个ActivityRecord,并且ActivityRecord对应的Activity必须是A
    • 2) A的启动模式不是singleInstance,A的taskAffinity属性和task1的taskAffinity属性必须一样。Task的taskAffinity属性由它包含的第1个ActivityRecord的taskAffinity属性决定。

    注意

    • 1) 从Launcher程序启动应用时,会先查找所有task,看是否有相关task,如果已有相关task,则会将相关task移动到回退栈的栈顶,然后显示栈顶Activity。查找相关task时,需看task是否和应用的入口Activity相关,入口Activity是指在AndroidManifest.xml里声明IntentFilter时,注明category为android.intent.category.LAUNCHER的Activity。如果入口Activity的启动模式为singleTask,不仅会将相关task移动到回退栈的栈顶,还会将该task里位于入口Activity之上的其它ActivityRecord全部清除掉
    • 2) 通过最近应用程序,切换应用时,会直接将应用图标对应的task移动到回退栈的栈顶,这样即使task里有singleTask类型的ActivityRecord,在它之上的ActivityRecord也不会被清除
    • 3) 可以通过adb shell dumpsys activity activties查看系统task情况

    思考问题

    相信大家看了这3篇博客以后,可以回答如下关于哪些情况下会产生新task的问题了

    • 1) 首次启动应用,是否会产生新的task?
    • 2) 假设应用app1的入口Activity(Activity A)启动模式为standard,从A可跳转至Acitivity B,Activity B的启动模式为singleTask,那么启动应用后,从ActivityA跳转到ActivityB是否会产生新的task?
    • 3) 假设应用app1的入口Activity是A,从A可跳转至B,从B可跳转至C,B的启动模式为singleTask,A和C的启动模式为standard,Activity的跳转顺序为A->B->C是否会产生新的task? 如果C的启动模式也为singleTask呢? 如果C的启动模式为singleInstance呢?
    • 4) 假设应用app1的入口Activity是A,从A可跳转至B, B的启动模式为singleTask,A的启动模式为standard,另一个应用app2有一个Activity C,C的启动模式为stanard,C也可跳转至B,目前已从A跳转到B,此时再打开应用app2,从C跳转至B,是否会产生新的task呢? 如果应用app1没启动,是否会产生新的task呢?
    • 5) 假设应用app1的入口Activity是A,从A可跳转至B,从B可跳转至C, B的启动模式为singleTask,A,C的启动模式为standard,从A跳转至B后,A会finish,假设此时A已跳转至B,B已跳转至C,此时通知栏有一个通知,可启动Activity B,那么点击通知后,会出现什么情况呢?
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 交换Easy
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法提高 多项式输出
    Java实现 蓝桥杯VIP 算法训练 矩阵乘方
    QT中给各控件增加背景图片(可缩放可旋转)的几种方法
    回调函数实现类似QT中信号机制
    std::string的Copy-on-Write:不如想象中美好(VC不使用这种方式,而使用对小字符串更友好的SSO实现)
  • 原文地址:https://www.cnblogs.com/syjhsgcc/p/4778212.html
Copyright © 2011-2022 走看看