美文网首页
Activity的启动模式及IntentFilter匹配规则总结

Activity的启动模式及IntentFilter匹配规则总结

作者: 韩明泽 | 来源:发表于2019-02-07 16:07 被阅读279次

    android的启动模式是在我们日常开发中经常用使用到,这个也是在面试用经常问到的一个问题。虽然我们对他很熟悉,但也会有些地方了解的太全面,因此写篇文章来来总结这方面的知识。文章主要内容来自《android开发艺术探讨》这本书,在文章的最后这本书的网页版本可供查看。


    image

    项目源码

    目录

    • 四中启动模式
    • 什么是任务栈
    • Activity如何指定需要的任务栈
    • TaskAffinity使用场景
    • Activity 的Flags
    • IntentFilter的匹配规则
    • 如何判断隐式启动是否成功

    1. 四中启动模式

    standard: 标准启动模式

    • 每启动一个Activity都会重新创建,不管这个实例是否存在。
    <!--系统默认启动方式,不需要指定launchMode值-->
    <activity 
    android:name=".StandardActivity"/>
    

    下面内容摘自《android开发艺术探讨》第一章16页底部

    在standard模式下,谁启动了这个Activity那么这个Activity就运行在它的任务栈中。例如:ActivityA启动了ActivityB(B为标准模式),那么ActivityB就会进入ActivityA的任务栈中。

    启动Activity的时候传入的Context不要是ApplicationContext。如果一定要传,那么一定要设置intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);否则回报下面异常:

    image

    singleTop: 栈顶复用模式

    • 新启动Activity栈顶已经存在,不会重新创建,同时会调用onNewIntent方法
    • Activity如果存在,但是不再栈顶,则会重新创建,并将新的Activity压入栈顶。
    <activity 
    android:name=".SingleTopActivity"
    android:launchMode="singleTop"/>
    

    singleTask: 栈内复用模式

    • 只要Activity在栈内存在,多次启动此Activity也不会创建新的实例,并且系统会调用onNewIntent方法
    • 如果Activity在栈内存在,但是没有在栈顶,系统会将该Activity之上的Activity全部挤出栈顶,使该Activity位于栈顶。
    <activity 
    android:name=".SingleTaskActivity"
    android:launchMode="singleTask" />
    

    singleInstance: 单实例模式

    • 该Activity只能单独位于一个任务栈中
    <activity 
    android:name=".SingleInstanceActivity"
    android:launchMode="singleInstance" >
    

    生命周期执行:

    • singleTask、singleInstance、singleInstance模式下,如果启动该Activity正好在顶部。那么他的生命周期执行为:
    onPause-->onNewIntent-->onResume
    
    • singleTask、singleInstance模式下,如果栈内有Activity实例,但不在栈顶。那么生命周期执行如下
    onNewIntent-->onRestart-->onStart
    

    2. 什么是任务栈

    查看activity在栈中的情况,可在控制台输入:adb shell dumpsys activity activities 通过搜索关键字 most recent first 快速定位 留意包名

    任务栈(Task):

    Task特点:

    • android的任务栈主要用于存放Activity,遵循先进后出的原则。
    • android的任务栈是一个包含了Activity的集合,我们每次打开新的Activity或者关闭一个Activity任务栈中就会增加或减少一个Activity组件。
    • 任务栈在没有Activity或者App退出的时候都会被销毁。
    • 一个App不止有一个任务栈,任务栈的Activity可以来自不同的App,同一个App的Activity也可以不再一个任务栈中。

    3. Activity如何指定需要的任务栈

    Activity指定需要启动的任务栈可以用过在配置文件中添加taskAffinity属性来实现。

    TaskAffinity特点:

    • 默认情况下,Activity启动的任务栈名称为应用包名。
    • 如果自己指定该属性值,不能与包名相同,否则相当于没指定。
    • 该属于一般配合singleTask或者allowTaskReparenting属性结合使用,在其他情况下没有实际意义。
    <activity android:name=".TestActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.test.singleTask.affinity"/>
    
    <activity android:name=".Test2ActivityC" 
    android:exported="true"
    android:allowTaskReparenting="true"/>
    

    4. TaskAffinity使用场景

    下面这段内容摘自Activity启动模式与任务栈(Task)全面深入记录(下)这篇文章。

    TaskAffinity与singleTask应用场景

    假如现在有这么一个需求,我们的客户端app正处于后台运行,此时我们因为某些需要,让微信调用自己客户端app的某个页面,用户完成相关操作后,我们不做任何处理,按下回退或者当前Activity.finish(),页面都会停留在自己的客户端(此时我们的app回退栈不为空),这显然不符合逻辑的,用户体验也是相当出问题的。我们要求是,回退必须回到微信客户端,而且要保证不杀死自己的app.这时候我们的处理方案就是,设置当前被调起Activity的属性为:
    LaunchMode=""SingleTask" taskAffinity="com.tencent.mm"
    其中com.tencent.mm是借助于工具找到的微信包名,就是把自己的Activity放到微信默认的Task栈里面,这样回退时就会遵循“Task只要有Activity一定从本Task剩余Activity回退”的原则,不会回到自己的客户端;而且也不会影响自己客户端本来的Activity和Task逻辑。

    TaskAffinity与allowTaskReparenting应用场景

    一个e-mail应用消息包含一个网页链接,点击这个链接将出发一个activity来显示这个页面,虽然这个activity是浏览器应用定义的,但是activity由于e-mail应用程序加载的,所以在这个时候该activity也属于e-mail这个task。如果e-mail应用切换到后台,浏览器在下次打开时由于allowTaskReparenting值为true,此时浏览器就会显示该activity而不显示浏览器主界面,同时actvity也将从e-mail的任务栈迁移到浏览器的任务栈,下次打开e-买了时并不会再显示该activity。

    Taskffinity与singleTask实例:

    注: 如果使用我在GitHub上建立的项目测这个功能的时候,请将TestTaskffinityOrAllowTaskRep.zip这个压缩包解压,并导入AS中。这个压缩包是我在测试的时候写的用于跳转androidreview应用的testTask应用。

    testTask应用(简称T应用)MainActivity中有一个按钮A,点击按钮会调用androidreview应用(简称A应用)的SingleTaskActivity。下面是两个应用的主要代码。

    testTask应用代码:

    switch (v.getId()) {
        case R.id.mBnt_ForSingleTask:
        //跳转androidreview应用SingleToak页面的按钮方法
        
        ComponentName cnForSingleTask = new ComponentName(
                "com.hdd.androidreview",
                "com.hdd.androidreview.Patterm.SingleTaskActivity");
        intent.setComponent(cnForSingleTask);
        startActivity(intent);
        break;
    }
    

    androidreview应用代码:

    <activity
        android:name=".Patterm.SingleTaskActivity"
        android:exported="true"
        android:launchMode="singleTask"
        android:taskAffinity="cmom.han.testbt.testTask">
    </activity>
    

    从上面的A应用代码配置信息中可以看到taskAffinity属性配置的是T应用的包名。因此SingleTaskActivity会在T的任务中被创建。假如MainActivity的按钮A点击事件中启动了SingleTaskActivity。那么cmom.han.testbt.testTask任务栈中会存在SingleTaskActivity和MainActivity两个Activity。下面是Activity在栈中的信息。


    image

    这时如果按了hone键返回桌面SingleTaskActivity进入后台,然后点击A应用的图标启动的却是A应用的MainActivity。出现这种情况是因为SingleTaskActivity的taskAffinity属性指定的是T应用包名。在T应用的MainActivity启动的SingleTaskActivy是在T应用的任务栈中。

    TaskAffinity与allowTaskReparenting实例
    T应用的B按钮点击事件代码:

    case R.id.mBnt_ForAllowTaskRep:
    //allowTaskReparenting模式跳转PattermActivity
    
    intent.setAction("com.hdd.androidreview.PattermActivity");
    ComponentName cnForAllowTaskRep = new ComponentName(
            "com.hdd.androidreview",
            "com.hdd.androidreview.Patterm.PattermActivity");
    intent.setComponent(cnForAllowTaskRep);
    
    break;
    

    A应用的PattermActivity(简称PActivity)的配置信息:

    <activity
        android:name=".Patterm.PattermActivity"
        android:allowTaskReparenting="true"
        android:theme="@style/AppTheme.NoActionBar" >
        <intent-filter>
            <action android:name="com.hdd.androidreview.PattermActivity"/>
        </intent-filter>
    </activity>
    

    点击T用于的B按钮,会启动A用的PActivity。该Activity的allowTaskReparenting属性为true,那么Activity会被移动到T应用的任务中站创建。当T应用按home返回桌面,再点击A应用;PActivity会被移回A的任务栈中。

    T应用调用A应用的PActivity栈内信息


    image

    T应用按home返回桌面,在启动A应用栈内信息:


    image

    allowTaskReparenting仅限于以standard 和singleTop启动的activity使用

    5. Activity 的Flags

    指定Activity的启动模式有两种,一种是在AndroidMenifest.xml中指定

    <activity
        android:name=".Patterm.SingleInstanceActivity"
        android:launchMode="singleInstance">
    

    另外一种是通过Intent来指定

    Intent intent = new Intent();
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
    intent.setClass(context, CycleActivity.class);
    context.startActivity(intent);
    

    注:intent指定的优先级大于xml中指定的优先级;如果两个方式都指定了启动方式,那么系统会以intent指定的启动方式为准。

    FLAG_ACTIVITY_NEW_TASK

    等同于在xml中配置了singleTask启动码模式

    FLAG_ACTIVITY_SINGLE_TOP

    等同于在xml中配置了singleTop启动码模式

    FLAG_ACTIVITY_CLEAR_TOP

    singTask自带该标记。这个标记会清除同一个任务栈中目标Activity之上的Activity。
    如果目标Activity采用了standard启动模式,但是任务栈中已经存在了Activity的实例。那么系统会清除任务中该实例以及它上面的Activity,并且会重新创建一个目标Activity实例放入栈顶。

    6. IntentFilter的匹配规则

    启动Activity有两种,一种为显示调用

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

    另一种为隐式调用要配置清单文件

    <!--隐式调用-->
    <activity
        android:name=".Patterm.PattermActivity"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="com.hdd.androidreview.asdf" />
            <category android:name="com.hdd.123456" />
            <!--比就加上,否则会报错-->
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    
    

    Activity跳转代码

    Intent intent = new Intent();
    intent.setAction("com.hdd.androidreview.asdf");
    //category非必须指定。如果要指定,一点要和清单文件中填写的一至
    // intent.addCategory("com.hdd.123456");
    context.startActivity(intent);
    

    1. action

    • 可以再清单文件中配置多个
    • intent指定的action值只要和清单文件中的其中一个字符串值一样,即可匹配成功。
    • 如果清单文件中配置了action,那么在intent跳转中必须至少指定且配对成功一个。
    <activity
        android:name=".Patterm.PattermActivity"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="com.hdd.androidreview.asdf" />
            <action android:name="com.hdd.androidreview.qwer" />
            <action android:name="12345678" />
            <!--必须加上,否则会报错-->
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    
    //java代码
    Intent intent = new Intent();
    intent.setAction("12345678");
    context.startActivity(intent);
    

    2. category

    • category可以再清单文件中添加多个
    • intent的addCategory()字符串有一个相同就可以匹配成功。
    • 他和action区别是,action是要在intent必须要指定的,切至少和一个匹配成功。category可以不用指定。如果指定也是至少和一个匹配成功。
    Intent intent = new Intent();
    //必须指定一个并匹配成功
    intent.setAction("com.hdd.androidreview.asdf");
    //category非必须指定。如果要指定,一点要和清单文件中填写的一至
    // intent.addCategory("com.hdd.123456");
    context.startActivity(intent);
    

    3. data

    • data的匹配规则和action类似,如果过滤规则中定义了data,那么intent中必须要匹配data。

    • data有mimeType和URL两部分组成

    mimeType为媒体类型: image/jpeg、audio/mpeg4-generic以及video/*等。

    URL数据结构为:

    <scheme>://<host>:<prot>/[<path>|<pathPrefix>|<pathPattern>]
    

    例如:

    content://com.example.project:200/folder/subfolder/etc
    http://www.baidu.com:80/search/info
    

    Scheme:URL的模式,比如http、file、content等;如果URL没有指定scheme,这个URL是无效的。

    Host:URL主机名,比如www.baidu.com,如果未指定,URL无效。

    Port:RUL端口号,比如80,只有scheme和host指定了port才有意义。

    Path:表示完整的路径信息。
    PathPattern:表示完整路径信息,里面可以包含通配符。
    PathPrefix:表示路径的前缀信息

    data在定义的时候有两种情况需要注意

    1. 第一种情况

    非完整写法,及只指定了mimeType或者只指定了URL。

    注:如果只指定了URL,系统会默认设置mimeType的值为content和file

    <!-- 隐式调用,只配置URL -->
    <activity android:name=".RegulationActivity">
        <intent-filter>
            <action android:name="12345678" />
            <!-- 必须加上,否则会报错 -->
            <category android:name="android.intent.category.DEFAULT" />
            <data
                android:host="www.baidu.com"
                android:scheme="http" />
        </intent-filter>
    </activity>
    
    
     <!-- 隐式调用,只配置mimeType· -->
    <activity android:name=".RegulationActivity">
        <intent-filter>
            <action android:name="12345678" />
            <!-- 必须加上,否则会报错 -->
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="audio/mpeg" />
        </intent-filter>
    </activity>
    
    

    java代码:

    //intent指定只配置了URL的data
    intent.setAction("12345678");
    intent.setData(Uri.parse("http://www.baidu.com"));
    context.startActivity(intent);
    
    //intent指定只配置了mimeType的data
    intent.setAction("12345678");
    intent.setType("audio/mpeg");
    context.startActivity(intent);
    
    1. 第二种情况

    完整写法

    注:如果intent指定的为完整的data,必须要使用setDataAndType(),因为setData()和setType()会彼此清楚对方的值。

    <!-- 隐式调用 -->
    <activity android:name=".RegulationActivity">
        <intent-filter>
            <action android:name="12345678" />
            <!-- 必须加上,否则会报错 -->
            <category android:name="android.intent.category.DEFAULT" />
            <data
                android:mimeType="audio/mpeg"
                android:host="www.baidu.com"
                android:scheme="http" />
        </intent-filter>
    </activity>
    

    java代码:

    //intent完整的data
    intent.setAction("12345678");
    intent.setDataAndType(Uri.parse("http://www.baidu.com"), "audio/mpeg");
    context.startActivity(intent);
    

    还有一种特殊写法:

    //写法1
    <intent-filter>
        <data
            android:mimeType="audio/mpeg"
            android:host="www.baidu.com"
            android:scheme="http" />
    </intent-filter>
    
    //写法2
    <intent-filter>
        <data android:mimeType="audio/mpeg" />
        <data android:scheme="http" />
        <data android:host="www.baidu.com" />
    </intent-filter>
    
    

    上面的两个写法上在使用效果上是一样的。

    7. 如何判断隐式启动是否成功

    第一种,使用intent的resolveActivity

    ComponentName componentName = intent.resolveActivity(context.getPackageManager());
    if (componentName != null)
        context.startActivity(intent);
    else
        Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();
    

    第二种,使用PackageManager的resolveActivity

    PackageManager packageManager=context.getPackageManager();
    ResolveInfo resolveInfo = packageManager.resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY);
    //判断是否匹配成功
    if (resolveInfo != null)
        context.startActivity(intent);
    else
        Toast.makeText(context, "匹配不成功", Toast.LENGTH_SHORT).show();
    

    上面的代码中返回ResolveInfo不是最佳的Activity信息,而是所有匹配成功的Activity信息。resolveActivity()填写的第二个参数必须是MATCH_DEFAULT_ONLY。具体原因可以查看《android开发艺术探索》第一章34页,这里就不解释了。

    终结:

    用了3天终于写完了!其实写这篇文章是为了复习android的基础知识,在复习过程中增加了对Activity启动模式以及Activity任务栈中的状态的了解。文章中四个启动模式中最麻烦的模式个人认为是singTask,最有意思的为Taskffinerty这个标签的使用,自己可以写个demo或者使用我在GitHub上的项目测试。

    参考

    Android开发艺术探索完结篇——天道酬勤

    Activity启动模式与任务栈(Task)全面深入记录(下)

    Activity启动模式与任务栈(Task)全面深入记录(上)

    相关文章

      网友评论

          本文标题:Activity的启动模式及IntentFilter匹配规则总结

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