Android 启动模式

Android 启动模式


首先我们来认识一下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
  • singleTop
  • singleTask
  • singleInstance

###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。

但是事实上真的如此吗?

我们举个例子:

<?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可以小结如下:

  • 设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。
  • 如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。

本文参考

  1. http://www.cnblogs.com/plokmju/p/android_activitylaunchermode.html
  2. http://blog.csdn.net/luoshengyang/article/details/6714543
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值