Android启动模式

在介绍 Android 启动模式之前,先介绍两个概念,一个是 task ,另一个是在 Androidmanifest 文件中 <activity> 的一个重要属性, taskAffinity

  • task:翻译过来就是“任务”,是一组相互有关联的 activity 集合,可以理解为 Activity 是在 task 里面活动的。 task 存在于一个称为 back stack 的数据结构中,也就是说, task 是以栈的形式去管理 activity 的,所以也叫可以称为“任务栈”。
  • taskAffinity:官方文档解释是:"The task that the activity has an affinity for.",可以翻译为 activity 相关或者亲和的任务,这个参数标识了一个 Activity 所需要的任务栈的名字。默认情况下,所有Activity所需的任务栈的名字为应用的包名。 taskAffinity 属性主要和 singleTask 启动模式或者 allowTaskReparenting 属性配对使用。

先初浅地了解一下这两个概念,接下来会对它们进行详细的解读。

Android有以下四种启动模式:

  • standard:标准模式,也是系统默认的启动模式。在该模式下,
    1. 假如 activity A 启动了 activity B , activity B 则会运行在 activity A 所在的任务栈中。
    2. 而且每次启动一个 Activity ,都会重新创建新的实例,不管这个实例在任务中是否已经存在。
    3. 非 Activity 类型的 context (如 ApplicationContext )启动 standard 模式的 Activity 时会报错。非 Activity 类型的 context 并没有所谓的任务栈,由于上面第 1 点的原因所以系统会报错。此解决办法就是为待启动 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候系统就会为它创建一个新的任务栈。这个时候待启动 Activity 其实是以 singleTask 模式启动的。
      示例如下,
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.chen.activitylaunchtest"
  xmlns:android="http://schemas.android.com/apk/res/android">       
  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity"
              android:taskAffinity="com.chen.main">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
    <activity android:name=".SecondActivity"
              android:launchMode="standard"></activity>
    <activity android:name=".ThirdActivity"></activity>
  </application>
</manifest>
  • 示例程序的包名为: com.chen.activitylaunchtest ,所以所有 Activity 默认任务栈是应该就是 com.chen.activitylaunchtest 。
  • MainActivity 的 taskAffinity 的属性值为 com.chen.main ,

我们打开 MainActivity ,再从 MainActivity 跳转到 SecondActivity ,并启动 SecondActivity 两次。得到任务栈活动如下图:


standard.png

可以看出,
- 当 MainActivity 启动时, MainActivity 的 taskAffinity 指定为 com.chen.main ,所以上图任务栈的名称为 com.chen.main 。
- 又从 MainActivity 中启动 SecondActivity ,上面有说过, activity A 启动了 activity B , activity B 则会运行在 activity A 所在的任务栈中。所以 SecondActivity 会在 MainActivity 的任务栈中启动。
- 因为在 standard 模式下,每次启动一个 Activity ,都会重新创建新的实例,不管这个实例在任务中是否已经存在,所以任务栈中有三个 SecondActivity 的实例。

  • singleTop: 栈顶复用模式。假如 activity A 启动了 activity B ,就会判断 A 所在的任务栈栈顶是否是 B 的实例。如果是,则不创建新的 activity B 实例而是直接引用这个栈顶实例,同时 onNewIntent 方法会被回调,通过该方法的参数可以取得当前请求的信息;如果不是,则创建新的 activity B 实例。

  • singleTask:栈内复用模式。

    singleTask.jpg
    还有,官方文档上称:"The system creates the activity at the root of a new task and routes the intent to it. ",也就是说在第一次启动这个 Activity 时,系统便会创建一个新的任务,并且初始化 Activity 的实例,放在新任务的底部。但是,
    singleTask.png
    在上图示例中, MainActivity 和 SecondActivity 的 taskAffinity 属性都是默认,并设置 SecondActivity 的启动模式为 singleTask 。由示例可知,在 SecondActivity 第一次启动时,并不是在任务栈的底部。
    其实要达到官方所说的效果,需要满足一定条件的。那就是需要设置 taskAffinity 属性。前面也说过了, taskAffinity 属性是和 singleTask 模式搭配使用的。具体分析请参考解开Android应用程序组件Activity的"singleTask"之谜

  • singleInstance:单实例模式。这个是 singleTask 模式的加强版,它除了具有 singleTask 模式的所有特性外,它还有一点独特的特性,那就是此模式的 Activity 只能单独地位于一个任务栈,不与其他 Activity 共存于同一个任务栈。

在一开始还提到, taskAffinity 属性一般和 singleTask 模式或者 allowTaskReparenting 属性配对使用。 taskAffinity 属性和 allowTaskReparenting 属性配对使用是一个怎样的工作过程呢?场景如下:

比如现在有 2 个应用 A 和 B , A 启动了 B 的一个 Activity C , Activity C 的 allowTaskReparenting 属性为 true ,然后按 Home 键回到桌面,然后再单击 B 的桌面图标,这个时候并不是启动了 B 的主 Activity ,而是重新显示了已经被应用 A 启动的 Activity C ,或者说, C 从 A 的任务栈转移到了 B 的任务栈中。
可以这么理解,由于 A 启动了 C ,这个时候 C 只能运行在 A 的任务栈中,但 C 属于 B 应用,正常情况下,它的 taskAffinity 属性值肯定不可能和 A 的任务栈相同(因为包名不同)。所以当 B 启动后, B 会创建自己的任务栈,这个时候系统发现 C 的原本所想要的任务栈已经被创建了,所以把 C 从 A 的任务栈中转移过来。

两种方法能给 Activity 指定启动模式

  1. 通过 AndroidMenifest 给 Activity 指定启动模式:
<activity android:name=".SecondActivity"
           android:launchMode="singleTask"></activity>
  1. 通过在 Intent 中设置标志位来为 Activity 指定启动模式:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

常用的 Activity 的 Flags 有

  • FLAG_ACTIVITY_NEW_TASK:
    这个标记位的作用是为 Activity 指定 “singleTask” 启动模式,其效果和在 XML 中指定该启动模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP:
    这个标记位的作用是为 Activity 指定 “singleTop” 启动模式,其效果和在 XML 中指定该启动模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP:
    具有此标志位的 Activity ,当它启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈。这个模式一般需要和 FLAG_ACTIVITY_NEW_TASK 配合使用。 singleTask 启动模式默认具有此标志位的效果。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:
    具有这个标记的 Activity 不会出现在历史 Activity 的列表中,它等同于在 XML 中指定 Activity 的属性 android:excludeFromRecents="true" 。

**显式启动 Activity **

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);

**隐式启动 Activity **
需要在 AndroidMenifest 里面为 Activity 设置 IntentFilter 匹配规则,并且intent也要添加过滤器。

<activity android:name=".SecondActivity"          
          android:launchMode="singleTask">    
  <intent-filter>        
    <action android:name="android.intent.action.VIEW"></action>        
    <category android:name="android.intent.category.DEFAULT"></category>        
    <data android:scheme="http"              
          android:pathPattern=".*\\.jpg"              
          android:mimeType="image/jpg">        
    </data>    
  </intent-filter>
</activity>
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.setDataAndType(Uri.parse("http://www.baidu.com/search/info/hello.jpg"), "image/jpg");
startActivity(intent);

** IntentFilter 的匹配规则**

  • IntentFilter 中的过滤信息有 action 、 category 、 data 。一个过滤列表中的 action 、 category 、 data 可以有多个。
  • 一个 Activity 中可以有多个 intent-filter ,一个 Intent 只要能匹配任何一组 intent-filter 即可成功启动对应的 Activity 。
    1. ** action 的匹配规则**:
      action 是字符串,系统预定义了一些 action ,同时我们也可以在应用中自定义自己的 action 。 action 的匹配规则是:
      要求 Intent 中的 action 必须存在,而且必须和 <intent-filter> 过滤规则中的其中一个 action 相同; action 还区分大小写。
    2. ** category 的匹配规则**:
      category 是字符串,系统预定义了一些 category ,同时我们也可以在应用中自定义自己的 category 。 category 的匹配规则是:
    • 要求 Intent 可以没有 category ,如果没有 category 的话,系统会默认为 intent 加上 "android.intent.category.DEFAULT" 这个 category 。
    • 但是如果一旦有 category ,不管有几个,每个都要能够和过滤规则中的任何一个 category 相同。
    • 为了 activity 能够接收隐式调用,就必须在 intent-filter 中指定 "android.intent.category.DEFAULT" 这个 category 。
    1. ** data 的匹配规则**:
      data 的匹配规则与 action 类似, data 的语法如下:
    <data android:scheme="string"
             android:host="string"
             android:post="string"
             android:path="string"
             android:pathPattern="string"
             android:pathPrefix="string"
             android:mimeType="string" />
    

data 由两部分组成, mimeType 和 URL 。
- mimeType 指媒体类型,比如 image/jpeg 和 video 等,可以表示图片、文本、视频等不同的媒体格式。
- URL 的结构
><scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

  例如:**```http://www.baidu.com:80/search/info/hello.jpg```**
  Scheme: URI 的模式,比如 http ,file,content 等,如果 URI 中没有指定 scheme ,那么整个 URI 无效。在上述例子里,scheme="http"。
  Host:URI 的主机名,比如 www.baidu.com,如果 URI 没有指定 host,那么整个 URI 无效。在上述例子里,host="www.baidu.com"。
  Port:URI 中的端口号,比如 80。在上述例子里,port="80"。
  Path:表示完整的路径信息。在上述例子里,path="/search/info"。
  pathPrefix:表示路径的前缀信息。在上述例子里,pathPrifix="/search"。
  pathPattern:也可以表示完整的路径信息,但是它里面可以包含通配符。在上述例子里,pathPattern="\.\*\\\\.jpg"
    - 匹配符号:"\*" 用来匹配0次或更多,如:"a\*" 可以匹配"a"、"aa"、"aaa"...
    - "\." 用来匹配任意字符,如:"\." 可以匹配 "a"、"b","c"...
    - "\.\*" 就是用来匹配任意字符0次或更多,如:"\.\*chen" 可以匹配 "chen"、"androidchen" ...
    - 转义:因为当读取 Xml 的时候,"\\" 是被当作转义字符的(当它被用作 pathPattern 转义之前),因此这里需要两次转义,读取 Xml 是一次,在 pathPattern 中使用又是一次。如:"\*" 这个字符就应该写成 "\\\\*","\\" 这个字符就应该写成 “\\\\\\\\”。

- 例子:  如果我们想要匹配 http 以 ".jpg" 结尾的路径,使得别的程序想要打开网络 jpg 时,用户能够可以选择我们的程序进行下载查看。我们可以将 scheme 设置为 "http",pathPattern 设置为 "\.\*\\\\.jpg",整个 intent-filter 设置为:
```XML

<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<data android:scheme="http" android:pathPattern=".*\.jpg"></data>
</intent-filter>
如果你只想处理某个站点的 jpg,那么在 data 标签里增加android:host="yoursite.com"则只会匹配http://yoursite.com/xxx/xxx.jpg```,但这不会匹配www.yoursite.com,如果你也想匹配这个站点的话,你就需要再添加一个 data 标签,除了 android:host 改为 "www.yoursite.com" 其他都一样。

  1. 要注意的事项
- 如果要为 Intent 指定完整的 data,必须要调用 setDataAndType 方法,不能用 setData 再调用 setType,因为这两个方法彼此会清除对方的值。
```java
intent.setDataAndType (Uri.parse ("http://www.baidu.com/search/info/hello.jpg"), "image/jpg");
```
- Intent-filter 的匹配规则对于 Service 和 BroadcastReceiver 也是同样的道理,不过系统对于 Service 的建议是尽量使用显式调用方式来启动服务。
- 通过隐式方式启动一个 Activity 时,可以采用 PackageManager 的 resolveActivity 方法或者 Intent 的 resolveActivity 方法先做一下判断,判断是否有 Activity 能匹配隐式 Intent。
```java

Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.setDataAndType(Uri.parse("http://www.baidu.com/search/info/hello.jpg"), "image/jpg");
if (intent.resolveActivity(getPackageManager()) == null)
Log.d("LaunchTest", "Go to SecondActivity unsuccessfully.");
else
startActivity(intent);
```

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容