@linux1s1s
2019-02-14T17:30:44.000000Z
字数 7190
阅读 3064
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都存在一个BackStack,而系统中可以存在多个Task,但是每次只有一个Task获得前台焦点,一般而言,系统允许用户在多个Task中切换,而被置于后台的Task中的Activity,将被置于Stopped状态。实际上,同一个Task中的Activity,只要不存在于栈顶并且获得前台焦点的Activity,那么它就是一个Stopped的状态。下图为官方文档中关于Task前后台的示例图:
根据Activity的不同的启动模式,它在BackStack中的状态是不一样的。Activity可以通过AndroidManifest.xml清单文件配置,在节点中的android:launchMode属性设置。它有四个选项:
标准启动模式,也是默认启动模式,如果不设置android:launchMode属性的话。standard模式下的Activity会依照启动的顺序压入BackStack中。
下图是standard模式下,Activity的压栈和回退操作示意图:
单顶模式,这种Activity启动模式,启动一个Activity的时候如果发现BackStack的栈顶已经存在这个Activity了,就不会去重新创建新的Activity,而是复用这个栈顶已经存在的Activity,避免同一个Activity被重复开启。
下图是singleTop模式下,Activity的压栈和回退操作示意图:
singleTop的应用场景很多,一般适用于可以复用而又有多个开启渠道的Activity,避免当一个Activity已经开启并获得焦点后,再次重复开启。比如说Android系统浏览器的书签页面,就是一个singleTop模式的Activity。Android的浏览器是基于WebKit内核编写的,它是支持JavaScript脚本语言的,可以通过JavaScript脚本设置浏览器书签,这样如果存在多个页面存在保存书签的JavaScript脚本,就会导致书签页面被多次开启,所以书签页面被设置为singleTop模式,这样可以避免在保存多个书签的时候重复开启书签页面。
被标记为singleInstance启动模式的Activity,在启动的时候,会开启一个新的BackStack,这个BackStack里只有一个Activity的实例存在,并且把这个BackStack获得焦点。这是一种很极端的模式,它会导致整个设备的操作系统里,只会存在一个这个Activity示例,无论是从何处被启动的。
下图是singleInstance模式下,Activity的压栈和回退操作示意图:
singleInstance一般适用于需要在系统中只存在一个实例的场景,比如Android系统的来电页面,多次来电均使用的是一个Activity。
上面三个启动模式都比较好理解,剩下的这个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。
但是事实上真的如此吗?
我们举个例子:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="shy.luo.task"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SubActivity"
android:label="@string/sub_activity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="shy.luo.task.subactivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
上面manifest定义了两个Activity,这里设置了SubActivity的启动模式为singleTask模式,我们来运行这样一个程序,然后再来验证一下,这个SubActivity是不是在一个新的Task中。
当程序运行起来,并且跳转到SubActivity以后在cmd中输入
adb shell dumpsys activity
Running activities (most recent first):
TaskRecord{4070d8f8 #3 A shy.luo.task}
Run #2: HistoryRecord{406a13f8 shy.luo.task/.SubActivity}
Run #1: HistoryRecord{406a0e00 shy.luo.task/.MainActivity}
TaskRecord{4067a510 #2 A com.android.launcher}
Run #0: HistoryRecord{40677518 com.android.launcher/com.android.launcher2.Launcher}
图示是这样的:
很明显,SubActivity并没有新起一个Task,而是和原先的Activity位于同一个Task中,那么为什么和官方文档说的不同呢,需找答案需要我们阅读源代码,这里不再展开了,如果感兴趣可以阅读一下这篇博客http://blog.csdn.net/luoshengyang/article/details/6714543。
接下来,我们修改一下上面的manifest文件如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="shy.luo.task"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:taskAffinity="shy.luo.task.main.activity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SubActivity"
android:label="@string/sub_activity"
android:launchMode="singleTask"
android:taskAffinity="shy.luo.task.sub.activity">
<intent-filter>
<action android:name="shy.luo.task.subactivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
然后按照上面的步骤,查看一下是不是新起了一个Task
Running activities (most recent first):
TaskRecord{4069c020 #4 A shy.luo.task.sub.activity}
Run #2: HistoryRecord{40725040 shy.luo.task/.SubActivity}
TaskRecord{40695220 #3 A shy.luo.task.main.activity}
Run #1: HistoryRecord{406b26b8 shy.luo.task/.MainActivity}
TaskRecord{40599c90 #2 A com.android.launcher}
Run #0: HistoryRecord{40646628 com.android.launcher/com.android.launcher2.Launcher}
可以看到,的确如android 开发文档所说。
所以我们对于signalTask可以小结如下:
这里仅仅对几个常用的与启动模式相关的Flag进行介绍。
在google的官方文档中介绍,它与launchMode="singleTask"具有相同的行为。实际上,并不是完全相同!
很少单独使用FLAG_ACTIVITY_NEW_TASK,通常与FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP联合使用。因为单独使用该属性会导致奇怪的现象,通常达不到我们想要的效果!尽管如何,后面还是会通过"FLAG_ACTIVITY_NEW_TASK示例一"和"FLAG_ACTIVITY_NEW_TASK示例二"会向你展示单独使用它的效果。
在google的官方文档中介绍,它与launchMode="singleTop"具有相同的行为。实际上,的确如此!单独的使用FLAG_ACTIVITY_SINGLE_TOP,就能达到和launchMode="singleTop"一样的效果。
顾名思义,FLAG_ACTIVITY_CLEAR_TOP的作用清除"包含Activity的task"中位于该Activity实例之上的其他Activity实例。FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK两者同时使用,就能达到和launchMode="singleTask"一样的效果!
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