admin管理员组文章数量:1122855
Intent
一、问题复现
Intent隐式启动Activity,我们在manifest文件中定义了如下intent-filter:
// 出现问题的写法
<intent-filter><action android:name="android.intent.action.VIEW"/><category android:name="android.intent.category.DEFAULT"/><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/><dataandroid:scheme="xu"android:host="www.landon"/>
</intent-filter>
这样,我们可以直接用xu://www.landon启动这个activity,但问题出现了,启动的时候,会报错:
Error: Activity not started, unable to resolve Intent { act=android.intent.action.VIEW dat=xu://www.landon flg=0x10000000 }
但在manifest文件中,我们已经定义了scheme和host,只不过有一个是多path,另外一个没有path,我们都知道data是可以存在多个的,这样看不应该找不到activity。
难道data是不允许多个同时在intent-filter中吗?抱着试试看的心态,我把两个data拆分出来:
// 正常work的写法<intent-filter><action android:name="android.intent.action.VIEW"/><category android:name="android.intent.category.DEFAULT"/><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/></intent-filter><intent-filter><action android:name="android.intent.action.VIEW"/><category android:name="android.intent.category.DEFAULT"/><dataandroid:scheme="xu"android:host="www.landon"/></intent-filter>
拆分出来,还真是就可以识别了:
Starting: Intent { act=android.intent.action.VIEW dat=xu://www.landon }
于是我又去翻了一下官方网站:您可以在 内放置任意数量的 元素,为其提供多个数据选项。它的属性都没有默认值。()的确是可以放多个data元素的,那为啥第一种就是不行呢
二、源码追溯
为了探究这个问题,不得不查看Android源码。
从错误提示unable to resolve Intent可以找到入手点:
// Intent.javapublic ComponentName resolveActivity(@NonNull PackageManager pm) {if (mComponent != null) {return mComponent;}ResolveInfo info = pm.resolveActivity(this, PackageManager.MATCH_DEFAULT_ONLY);if (info != null) {return new ComponentName(info.activityInfo.applicationInfo.packageName,info.activityInfo.name);}return null;}
从Intent.resolveActivity可以看到,最终调用的是PackageManager的resolveActivity,而PackageManager是通过ContextImpl的getPackageManager获取到的:
// ContextImpl@Overridepublic PackageManager getPackageManager() {if (mPackageManager != null) {return mPackageManager;}IPackageManager pm = ActivityThread.getPackageManager();if (pm != null) {// Doesn't matter if we make more than one instance.return (mPackageManager = new ApplicationPackageManager(this, pm));}return null;}
ActivityThread.getPackageManager()就是获取了PackageManagerService,在ContextImpl中用ApplicationPackageManager代理PMS:
ApplicationPackageManager.java@Overridepublic ResolveInfo resolveActivity(Intent intent, int flags) {return resolveActivityAsUser(intent, flags, getUserId());}@Overridepublic ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {try {return mPM.resolveIntent(intent,intent.resolveTypeIfNeeded(mContext.getContentResolver()),updateFlagsForComponent(flags, userId, intent),userId);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
调用了PMS的resolveIntent方法:
// PackageManagerService.java@Overridepublic ResolveInfo resolveIntent(Intent intent, String resolvedType,int flags, int userId) {return resolveIntentInternal(intent, resolvedType, flags, userId, false,Binder.getCallingUid());}/*** Normally instant apps can only be resolved when they're visible to the caller.* However, if {@code resolveForStart} is {@code true}, all instant apps are visible* since we need to allow the system to start any installed application.*/private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,int flags, int userId, boolean resolveForStart, int filterCallingUid) {.....final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,flags, filterCallingUid, userId, resolveForStart, true /*allowDynamicSplits*/);.......}private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,String resolvedType, int flags, int filterCallingUid, int userId,boolean resolveForStart, boolean allowDynamicSplits) {....List<CrossProfileIntentFilter> matchingFilters =getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);.....}private List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent,String resolvedType, int userId) {CrossProfileIntentResolver resolver = mSettings.mCrossProfileIntentResolvers.get(userId);if (resolver != null) {return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId);}return null;}
然后调用到IntentResolver:
// IntentResolver<F extends IntentFilter, R extends Object> public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,int userId) {........if (firstTypeCut != null) {buildResolveList(intent, categories, debug, defaultOnly, resolvedType,scheme, firstTypeCut, finalList, userId);}if (secondTypeCut != null) {buildResolveList(intent, categories, debug, defaultOnly, resolvedType,scheme, secondTypeCut, finalList, userId);}if (thirdTypeCut != null) {buildResolveList(intent, categories, debug, defaultOnly, resolvedType,scheme, thirdTypeCut, finalList, userId);}if (schemeCut != null) {buildResolveList(intent, categories, debug, defaultOnly, resolvedType,scheme, schemeCut, finalList, userId);}......}private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,boolean debug, boolean defaultOnly, String resolvedType, String scheme,F[] src, List<R> dest, int userId) {.....match = filter.match(action, resolvedType, scheme, data, categories, TAG);......}
最终调用到IntentFilter的match和matchData方法:
public final int match(String action, String type, String scheme,Uri data, Set<String> categories, String logTag) {......int dataMatch = matchData(type, scheme, data);.....}/**
* 核心方法
*
*/
public final int matchData(String type, String scheme, Uri data) {final ArrayList<String> types = mDataTypes;final ArrayList<String> schemes = mDataSchemes;int match = MATCH_CATEGORY_EMPTY;if (types == null && schemes == null) {return ((type == null && data == null)? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);}if (schemes != null) {if (schemes.contains(scheme != null ? scheme : "")) {match = MATCH_CATEGORY_SCHEME;} else {return NO_MATCH_DATA;}// mDataSchemeSpecificParts这个是ssp字段,目前这里没有用到,此处为nullfinal ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;// 这里if不走,因为schemeSpecificParts为空if (schemeSpecificParts != null && data != null) {match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart())? MATCH_CATEGORY_SCHEME_SPECIFIC_PART : NO_MATCH_DATA;}// 进入到此if中if (match != MATCH_CATEGORY_SCHEME_SPECIFIC_PART) {// If there isn't any matching ssp, we need to match an authority.final ArrayList<AuthorityEntry> authorities = mDataAuthorities;if (authorities != null) {int authMatch = matchDataAuthority(data);if (authMatch >= 0) {final ArrayList<PatternMatcher> paths = mDataPaths;// 如果定义的manifest文件中的intent-filter中没有定义path,就直接匹配成功if (paths == null) {match = authMatch;} else if (hasDataPath(data.getPath())) {如果定义在manifest文件中的intent-filter中定义了path,就需要再到hasDataPath中对比match = MATCH_CATEGORY_PATH;} else {return NO_MATCH_DATA;}} else {return NO_MATCH_DATA;}}}// If neither an ssp nor an authority matched, we're done.if (match == NO_MATCH_DATA) {return NO_MATCH_DATA;}} else {// Special case: match either an Intent with no data URI,// or with a scheme: URI. This is to give a convenience for// the common case where you want to deal with data in a// content provider, which is done by type, and we don't want// to force everyone to say they handle content: or file: URIs.if (scheme != null && !"".equals(scheme)&& !"content".equals(scheme)&& !"file".equals(scheme)) {return NO_MATCH_DATA;}}if (types != null) {if (findMimeType(type)) {match = MATCH_CATEGORY_TYPE;} else {return NO_MATCH_TYPE;}} else {// If no MIME types are specified, then we will only match against// an Intent that does not have a MIME type.if (type != null) {return NO_MATCH_TYPE;}}return match + MATCH_ADJUSTMENT_NORMAL;}/*** Is the given data path included in the filter? Note that if the* filter does not include any paths, false will <em>always</em> be* returned.** @param data The data path to look for. This is without the scheme* prefix.** @return True if the data string matches a path listed in the* filter.*/public final boolean hasDataPath(String data) {if (mDataPaths == null) {return false;}// 当manifest文件中intent-filter定义了path,需要和目标对比final int numDataPaths = mDataPaths.size();for (int i = 0; i < numDataPaths; i++) {final PatternMatcher pe = mDataPaths.get(i);if (pe.match(data)) {return true;}}return false;}
三、原因分析
根据上述源码分析,可以找到原因:
- manifest文件中的intent-filter中如果没有定义path,那么当scheme和host都匹配成功,就不需要再去看path字段了:比如在intent-filter中定义了:
<!--这样定义想相当于要匹配所有xu://www.landon,而不管后续path是啥,都匹配上-->
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"/>
</intent-filter>
- manifest文件中的intent-filter中如果定义了path,那么只会按照path来匹配,因为intent-filter中只要存在path,就会走到hasDataPath
<!--这样定义想相当于要匹配xu://www.landon/path,必须要有path,xu://www.landon这种就无法匹配>
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/>
</intent-filter>
- manifest文件中的一个intent-filter可以存在多个data,如果这些data中存在有path,也存在没有path的,依旧会按照path来匹配,因为IntentFilter在解析xml文件中时,如果一旦发现path,mDataPaths就不为空,就会走到path匹配里
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"/><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/>
</intent-filter>
所以,当按照两个data一个有path一个没有path的都在同一个intent-filter的时候,没有path的是无法匹配的,例如上面的xu://www.landon就没有办法匹配上。
如果将其中没有path的添加一个path=“”,这样就能匹配成功了
<intent-filter><dataandroid:scheme="xu"android:path=""android:host="www.landon"/><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/>
</intent-filter>
四、问题解决
既然在同一个intent-filter可以定义多个data,但要注意有path和没有path的不要混合在一起用
- 有path的定义在同一个intent-filter中
- 没有path只有scheme和host的,需要单独定义在一个intent-filter中,不与其他有path的混合在一起
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"/>
</intent-filter>
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/>
</intent-filter>
五、反思
对于intent-filter的写法
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"/><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/>
</intent-filter>
这种写法本身就存在歧义:
是只匹配xu://www.landon和xu://www.landon/test吗,添加其他的path不匹配?
还是本意想要匹配所有xu://www.landon,不论path是啥?
如果是前者,那么可以使用:
<intent-filter><dataandroid:scheme="xu"android:path=""android:host="www.landon"/><dataandroid:scheme="xu"android:host="www.landon"android:path="/test"/>
</intent-filter>
如果是后者,那么直接使用一个就可以:
<intent-filter><dataandroid:scheme="xu"android:host="www.landon"/>
</intent-filter>
所以,问题的根本是在于对于intent-filter要过滤的协议标准,标准确认了,写法上也不会这样存在模糊有问题。
本文标签: Intent
版权声明:本文标题:Intent 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1711346300a776025.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论