Android启动模式

作者: Utopia_Sen | 来源:发表于2016-04-18 02:11 被阅读468次

    在介绍 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);
    ```

    相关文章

      网友评论

        本文标题:Android启动模式

        本文链接:https://www.haomeiwen.com/subject/nmvolttx.html