[关闭]
@linux1s1s 2019-02-14T09:30:44.000000Z 字数 7190 阅读 2930

Android 启动模式

AndroidMechanism 2015-05


首先我们来认识一下App和进程的关系

众所周知,Android下有四大组件:Activity、Service、Receiver、ContentProvider。一般开发一个应用程序,会包含多个Android组件,所以应用程序是一组组件的集合,而进程则是运行这些组件的载体。

由上面的描述,我们知道,App仅仅是静态的概念,它把Android的四大组件打包在一起,而事实上App.apk本质上也是一个ZIP压缩包,当你解压一个App的时候,它一般长这样

此处输入图片的描述

这个压缩包里面分成了几个部分:

AndroidManifest.xml --- > 声明Android四大组件
class.dex --- > App代码逻辑部分
res --- > AppUI部分
assets --- > 保留原有格式不会转换为二进制的原始文件

从上面的文件分布上也能看出Android MVC框架的端倪,最起码形式上已经将UI和代码逻辑分开了,这部分不是本博客的重点,不再展开分析。

一个App会对应多个界面,所以在AppUI部分必然有多个资源文件,这资源文件在代码逻辑部分表现为:Activity,本质上Activity是个轻量级类,在这个Activity中持有Windows的管理类,这才是重量级类,一般的框架都有个原则:一般重量级类不需要开发人员去维护,而开发人员多与轻量级类打交道,所以Android开发人员多与Activity打交道,而用户接触的UI就直接表现在Activity上。所以为了 提高用户体验,不得不提供一个回退栈来有效的维护用户体验。所以本博客讨论的就是为了用户体验而生的Task

回退栈

回退栈(Back Stack)只是针对Activity而言的,它是用来维护用的界面体验的,使一个Task让用户感觉就是一个应用,而无论其中的Activity是否来自同一个应用程序,所以不要把回退栈和进程弄混了。设备的Home页面是大多数Task的起始位置,当用户点击一个应用程序图标的时候,应用的Task就会来到前台,并把应用的主Activity压入BackStack的栈顶,并获得焦点,这个Activity称为根Activity,而在BackStack中的Activity可以通过点击回退键弹出栈并销毁,这时就会使上一个Activity获得焦点,直到用户返回到Home页,而当BackStack中的Activity都被弹出销毁之后,这个Task就不复存在了,但是这个程序的进程还存在(不在此时销毁)。

Task

就像上面介绍的,每个Task都存在一个BackStack,而系统中可以存在多个Task,但是每次只有一个Task获得前台焦点,一般而言,系统允许用户在多个Task中切换,而被置于后台的Task中的Activity,将被置于Stopped状态。实际上,同一个Task中的Activity,只要不存在于栈顶并且获得前台焦点的Activity,那么它就是一个Stopped的状态。下图为官方文档中关于Task前后台的示例图:

此处输入图片的描述

Activity启动模式

根据Activity的不同的启动模式,它在BackStack中的状态是不一样的。Activity可以通过AndroidManifest.xml清单文件配置,在节点中的android:launchMode属性设置。它有四个选项:

standard

标准启动模式,也是默认启动模式,如果不设置android:launchMode属性的话。standard模式下的Activity会依照启动的顺序压入BackStack中。

下图是standard模式下,Activity的压栈和回退操作示意图:
  
此处输入图片的描述

singleTop

单顶模式,这种Activity启动模式,启动一个Activity的时候如果发现BackStack的栈顶已经存在这个Activity了,就不会去重新创建新的Activity,而是复用这个栈顶已经存在的Activity,避免同一个Activity被重复开启。

下图是singleTop模式下,Activity的压栈和回退操作示意图:

此处输入图片的描述

singleTop的应用场景很多,一般适用于可以复用而又有多个开启渠道的Activity,避免当一个Activity已经开启并获得焦点后,再次重复开启。比如说Android系统浏览器的书签页面,就是一个singleTop模式的Activity。Android的浏览器是基于WebKit内核编写的,它是支持JavaScript脚本语言的,可以通过JavaScript脚本设置浏览器书签,这样如果存在多个页面存在保存书签的JavaScript脚本,就会导致书签页面被多次开启,所以书签页面被设置为singleTop模式,这样可以避免在保存多个书签的时候重复开启书签页面。

singleInstance

被标记为singleInstance启动模式的Activity,在启动的时候,会开启一个新的BackStack,这个BackStack里只有一个Activity的实例存在,并且把这个BackStack获得焦点。这是一种很极端的模式,它会导致整个设备的操作系统里,只会存在一个这个Activity示例,无论是从何处被启动的。

下图是singleInstance模式下,Activity的压栈和回退操作示意图:

此处输入图片的描述

singleInstance一般适用于需要在系统中只存在一个实例的场景,比如Android系统的来电页面,多次来电均使用的是一个Activity。

singleTask

上面三个启动模式都比较好理解,剩下的这个singleTask比较难理解,我们放在最后压轴说一下:

先看一下Android developer是如何说的
http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html

The system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.

它明确说明,以"singleTask"方式启动的Activity,全局只有唯一个实例存在,因此,当我们第一次启动这个Activity时,系统便会创建一个新的任务,并且初始化一个这样的Activity的实例,放在新任务的底部,如果下次再启动这个Activity时,系统发现已经存在这样的Activity实例,就会调用这个Activity实例的onNewIntent成员函数,从而把它激活起来。从这句话就可以推断出,以"singleTask"方式启动的Activity总是属于一个任务的根Activity。

但是事实上真的如此吗?

我们举个例子:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="shy.luo.task"
  4. android:versionCode="1"
  5. android:versionName="1.0">
  6. <application android:icon="@drawable/icon" android:label="@string/app_name">
  7. <activity android:name=".MainActivity"
  8. android:label="@string/app_name">
  9. <intent-filter>
  10. <action android:name="android.intent.action.MAIN" />
  11. <category android:name="android.intent.category.LAUNCHER" />
  12. </intent-filter>
  13. </activity>
  14. <activity android:name=".SubActivity"
  15. android:label="@string/sub_activity"
  16. android:launchMode="singleTask">
  17. <intent-filter>
  18. <action android:name="shy.luo.task.subactivity"/>
  19. <category android:name="android.intent.category.DEFAULT"/>
  20. </intent-filter>
  21. </activity>
  22. </application>
  23. </manifest>

上面manifest定义了两个Activity,这里设置了SubActivity的启动模式为singleTask模式,我们来运行这样一个程序,然后再来验证一下,这个SubActivity是不是在一个新的Task中。

当程序运行起来,并且跳转到SubActivity以后在cmd中输入

  1. adb shell dumpsys activity
  1. Running activities (most recent first):
  2. TaskRecord{4070d8f8 #3 A shy.luo.task}
  3. Run #2: HistoryRecord{406a13f8 shy.luo.task/.SubActivity}
  4. Run #1: HistoryRecord{406a0e00 shy.luo.task/.MainActivity}
  5. TaskRecord{4067a510 #2 A com.android.launcher}
  6. Run #0: HistoryRecord{40677518 com.android.launcher/com.android.launcher2.Launcher}

图示是这样的:

此处输入图片的描述

很明显,SubActivity并没有新起一个Task,而是和原先的Activity位于同一个Task中,那么为什么和官方文档说的不同呢,需找答案需要我们阅读源代码,这里不再展开了,如果感兴趣可以阅读一下这篇博客http://blog.csdn.net/luoshengyang/article/details/6714543
接下来,我们修改一下上面的manifest文件如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="shy.luo.task"
  4. android:versionCode="1"
  5. android:versionName="1.0">
  6. <application android:icon="@drawable/icon" android:label="@string/app_name">
  7. <activity android:name=".MainActivity"
  8. android:label="@string/app_name"
  9. android:taskAffinity="shy.luo.task.main.activity">
  10. <intent-filter>
  11. <action android:name="android.intent.action.MAIN" />
  12. <category android:name="android.intent.category.LAUNCHER" />
  13. </intent-filter>
  14. </activity>
  15. <activity android:name=".SubActivity"
  16. android:label="@string/sub_activity"
  17. android:launchMode="singleTask"
  18. android:taskAffinity="shy.luo.task.sub.activity">
  19. <intent-filter>
  20. <action android:name="shy.luo.task.subactivity"/>
  21. <category android:name="android.intent.category.DEFAULT"/>
  22. </intent-filter>
  23. </activity>
  24. </application>
  25. </manifest>

然后按照上面的步骤,查看一下是不是新起了一个Task

  1. Running activities (most recent first):
  2. TaskRecord{4069c020 #4 A shy.luo.task.sub.activity}
  3. Run #2: HistoryRecord{40725040 shy.luo.task/.SubActivity}
  4. TaskRecord{40695220 #3 A shy.luo.task.main.activity}
  5. Run #1: HistoryRecord{406b26b8 shy.luo.task/.MainActivity}
  6. TaskRecord{40599c90 #2 A com.android.launcher}
  7. Run #0: HistoryRecord{40646628 com.android.launcher/com.android.launcher2.Launcher}

可以看到,的确如android 开发文档所说。

所以我们对于signalTask可以小结如下:

Intent与启动模式相关的Flag简介

这里仅仅对几个常用的与启动模式相关的Flag进行介绍。

FLAG_ACTIVITY_NEW_TASK

在google的官方文档中介绍,它与launchMode="singleTask"具有相同的行为。实际上,并不是完全相同!
很少单独使用FLAG_ACTIVITY_NEW_TASK,通常与FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP联合使用。因为单独使用该属性会导致奇怪的现象,通常达不到我们想要的效果!尽管如何,后面还是会通过"FLAG_ACTIVITY_NEW_TASK示例一"和"FLAG_ACTIVITY_NEW_TASK示例二"会向你展示单独使用它的效果。

FLAG_ACTIVITY_SINGLE_TOP

在google的官方文档中介绍,它与launchMode="singleTop"具有相同的行为。实际上,的确如此!单独的使用FLAG_ACTIVITY_SINGLE_TOP,就能达到和launchMode="singleTop"一样的效果。

FLAG_ACTIVITY_CLEAR_TOP

顾名思义,FLAG_ACTIVITY_CLEAR_TOP的作用清除"包含Activity的task"中位于该Activity实例之上的其他Activity实例。FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK两者同时使用,就能达到和launchMode="singleTask"一样的效果!

FLAG_ACTIVITY_CLEAR_TASK

FLAG_ACTIVITY_CLEAR_TASK的作用包含Activity的task。使用FLAG_ACTIVITY_CLEAR_TASK时,通常会包含FLAG_ACTIVITY_NEW_TASK。这样做的目的是启动Activity时,清除之前已经存在的Activity实例所在的task;这自然也就清除了之前存在的Activity实例!

本文参考
1. http://www.cnblogs.com/plokmju/p/android_activitylaunchermode.html
2. http://blog.csdn.net/luoshengyang/article/details/6714543
3. http://wangkuiwu.github.io/2014/06/26/IntentFlag/#anchor3

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注