Activity的启动模式
启动模式分为四种,standard、singleTop、singleTask、singleInstance;详情如下图: 启动模式详情.png在singleTask详解中,有提到Activity所需任务栈,那么什么是Activity所需要的任务栈呢?
要从TaskAffinity参数说起,可以翻译为任务相关性。这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需任务栈的名字为应用的包名。当然,我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相当于没有指定。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下,没有意义。另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
当TaskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。
当TaskAffinity和allowTaskReparenting结合的时候,比较复杂,会产生特殊效果。举个例子,现在又两个应用A、B,B的allowTaskReparenting为true,A 启动了B的一个Activity C,然后按Home回到了桌面,然后再单击B的桌面图标,这个时候并不是启动了B的主Activity,而是重新显示了已经被A启动的Activity C,C从A任务栈转移到了B任务栈。当A启动B的时,B会创建自己的任务栈,当C发现原本需要的任务栈已经被创建了,所以C就转移了。
如何给Activity指定启动模式?
1.通过AndroidMenifest。
<activity
android:name=".view.PainActivity"
android:label="@string/title_activity_pain"
android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar"/>
2.通过Intent中设置标志位来为Activity指定启动模式。
intent = new Intent(MainActivity.this,Handler_01.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
两者区别:
优先级:第二种方式的优先级要高于第一种;
限定范围:第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种无法为Activity指定singleInstance模式。
查看Activity任务栈分配情况的命令:
adb shell dumpsys activity
Activity的Flags
下面介绍几款比较常用的标记位:
1.FLAG_ACTIVITY_NEW_TASK
这个标记位的作用是为Activity指定singleTask启动模式,其效果和在XML中指定该启动模式相同。
2.FLAG_ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定singleTop启动模式,其效果和在XML中指定该启动模式相同。
3.FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。
4.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定activity的属性android:excludeFromRecents="true"。
IntentFilter的匹配规则
启动Activity分为两种,隐式调用和显式调用,显式调用需要明确指定被启动对象的组件信息,包括包名和类名。而隐式调用则不需要明确指定组件信息。如果两者共存以显式调用为主。下面介绍一下隐式调用,隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。
为了匹配过滤列表,需要同时匹配过滤列表中的action、category、data信息,否则匹配失败。一个Activity可以有多个IntentFilter,一个Intent只要能匹配任何一组intent-filter即可成功启动应用的Activity。
下面分析各种属性的匹配规则。
1.action的匹配规则
action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同,action区分大小写。
2.category的匹配规则
category的匹配要求Intent中可以没有category,但是一旦有,不管几个,每个都要能够和过滤规则中的任何一个category相同。为什么不设置也能匹配成功呢?因为系统在调用startActivity或者startActivityForResult的时候会默认加为Intent加上“android.intent.category.DEFAULT”这个category。所以为了我们activity能接受隐式调用,必须在intent-filter中指定“android.intent.category.DEFAULT”这个category。
3.data的匹配规则
data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。在介绍data的匹配规则之前,我们需要先了解一下data的结构,因为data稍微有些复杂。
data的语法:
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string"/>
data由两部分组成,mimeType和URI。mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/等,可以表示图片、文本、视频等不同媒体格式,而URI中包含的数据比较多,下面是URI的结构:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
实际例子:
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
下面介绍下每个数据的含义:
Scheme:URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效,这也意味着URI是无效的。
Host:URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI无效。
Port:URI的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
Path、pathPattern和pathPrefix:这三个参数表述路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“”,“”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“”要写成“\*",""要写成"\\";pathPrefix表示路径的前缀信息。
data的过滤规则:
(1)如下过滤规则:
<intent-filter>
<data android:mimeType="image/*"/>
...
</intent-filter>
这种规则指定了媒体类型为所有类型的图片,那么Intent中的minmeType属性必须为"image/*"才可以匹配,这种情况,虽然过滤规则没有指定URI,但是却有默认值,URI的默认值为content和file。也就是说,虽然没有指定URI,但是需要注意:Intent中的URI部分的schema必须为content或者file才能匹配。为了匹配以上规则,我们可以写:
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
注意:
如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法批次会清除对方的值,源码:
public Intent setData(Uri data){
mData = data;
mType = null;
return this;
}
可以发现setData会把mimeType值清空为null,同理setType也是如此。
(2)如下过滤规则
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" .../>
<data android:mimeType="audio/mpeg" android:scheme="http" .../>
...
</intent-filter>
这种指定了两种data规则,且每个data都指定了完整的属性值,既有URI又有mimeType。为了匹配(2)中规则,我们可以写出如下示例:
intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg")
或者
intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg")
(3)特殊情况(与action不同的地方)
<intent-filter>
<data android:scheme="file" android:host="www.baidu.com" .../>
...
</intent-filter>
和
<intent-filter>
<data android:scheme="file"/>
<data android:host="www.baidu.com"/>
...
</intent-filter>
以上两种写法是一样的。
IntentFilter匹配规则对于Service和BroadCastReceiver也是同样道理,不过系统建议对于Service还是尽量使用显示调用方式来启动服务。
最后,当我们通过隐式启动一个Activity时,可以做一下判断,看是否有Activity能够匹配我们的隐式Intent,如果不做判断就有可能出错。
判断的方法:
-PackageManager的resolveActivity方法
-Intent的resolveActivity方法
如果他们找不到匹配的Activity就会返回空。
PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity方法的区别是:它不是返回最佳匹配的Activity信息,而是返回所有成功匹配的Activity信息,方法如下:
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);
public abstract ResolveInfo resolveActivities(Intent intent,int flags);
上述方法第二个参数,我们使用MATCH_DEFAULT_ONLY这个标记位,含义是:仅仅匹配那些在intent-filter中声明了
`<catgory android:name="android.intent.category.LAUNCHER" />`这个category的Activity,使用这个标记的意义在于,只要上述两个方法都不返回空,那么startActivity一定可以成功。否则会把不含category的那些Activity匹配出来,可能会导致startActivity失败。因为不含无法接受隐式调用。
<action android:name="android.intent.action.MAIN" />
<catgory android:name="android.intent.category.LAUNCHER" />
这两比较重要,共同作用是用来标记一个入口Activity,并且会出现在系统的应用列表中,少了任何一个都没有实际意义,也无法出现在系统的应用列表中,也就是二者缺一不可。
同样,针对Service和BroadCastReceiver,PackageManager同样提供了类似的方法去获取成功匹配的组件信息。
网友评论