Android Intent匹配解析

写这篇文章的起因是因为之前看到了一篇关于intent-filter的误区的文章,这篇文章中说明了官方文档中关于Intent-filter的说明中存在的一些问题,具体来说就是,我们都知道Intent的匹配分成Action,Data和Category三种,当Intent-filter中声明了这三种数据,当Intent没有指定Action,而只指定了category的时候,是无法启动目标组件的,而如果Intent没有指定Action,但是Data和category都指定了,那是可以启动目标组件的。这句话说的有点拗口,大家可以去看上面的那篇文章,应该会了解是怎么样一个情况。

看完这篇文章我就想问了,为什么会这样呢?Action,Data和category之间到底是怎么样一种匹配关系呢?于是我去研究了一下这方面源码,首先要说我参考了 Intent匹配规则以及解析框架深入分析,谢谢这位博主细致的分析,让我受益无穷,我也是站在了巨人的肩膀上吧。既然如此,他讲过的我就不再讲了,大家可以去看这篇文章。我就从IntentFilter.match()这个方法开始讲起,这个方法是Intent匹配的核心。

 private final ArrayList<String> mActions;
    private ArrayList<String> mCategories = null;
    private ArrayList<String> mDataSchemes = null;
    private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
    private ArrayList<AuthorityEntry> mDataAuthorities = null;
    private ArrayList<PatternMatcher> mDataPaths = null;
    private ArrayList<String> mDataTypes = null;

首先看这些是IntentFilter类中的成员变量,如果你是在AndroidManifest中配置的IntentFilter,系统会自动解析XML并且生成数据,如果你是在JAVA代码中动态生成的IntentFilter,那么就会像这样。

public final void addAction(String action) {
        if (!mActions.contains(action)) {
            mActions.add(action.intern());
        }
    }

OK,下面就开始讲match方法吧。

public final int match(String action, String type, String scheme,
            Uri data, Set<String> categories, String logTag) {
        if (action != null && !matchAction(action)) {
            if (false) Log.v(
                logTag, "No matching action " + action + " for " + this);
            return NO_MATCH_ACTION;
        }

        int dataMatch = matchData(type, scheme, data);
        if (dataMatch < 0) {
            if (false) {
                if (dataMatch == NO_MATCH_TYPE) {
                    Log.v(logTag, "No matching type " + type
                          + " for " + this);
                }
                if (dataMatch == NO_MATCH_DATA) {
                    Log.v(logTag, "No matching scheme/path " + data
                          + " for " + this);
                }
            }
            return dataMatch;
        }

        String categoryMismatch = matchCategories(categories);
        if (categoryMismatch != null) {
            if (false) {
                Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
            }
            return NO_MATCH_CATEGORY;
        }

        // It would be nice to treat container activities as more
        // important than ones that can be embedded, but this is not the way...
        if (false) {
            if (categories != null) {
                dataMatch -= mCategories.size() - categories.size();
            }
        }

        return dataMatch;
    }

可以看到这个方法会先去判断Action,如果Action不为null且没有匹配到Action,那么就返回类型不匹配。所以由此可以知道,Action是最先匹配的,也是最重要的。接下去是匹配Data,会进入matchData这个方法。

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;
            }

            final ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;
            if (schemeSpecificParts != null) {
                match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart())
                        ? MATCH_CATEGORY_SCHEME_SPECIFIC_PART : NO_MATCH_DATA;
            }
            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;
                        if (paths == null) {
                            match = authMatch;
                        } else if (hasDataPath(data.getPath())) {
                            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;
    }

这个方法比较长,其实就是根据type,scheme和uri进行匹配,具体大家可以自己看,应该没什么难度。

最后匹配category,进入matchCategories方法。

public final String matchCategories(Set<String> categories) {
        if (categories == null) {
            return null;
        }

        Iterator<String> it = categories.iterator();

        if (mCategories == null) {
            return it.hasNext() ? it.next() : null;
        }

        while (it.hasNext()) {
            final String category = it.next();
            if (!mCategories.contains(category)) {
                return category;
            }
        }

        return null;
    }
这个方法的意思就是如果你的Intent带有category,那么它会将这些category和IntentFilter中的category进行比较,发现有不匹配的就直接返回。

if (categoryMismatch != null) {
            if (false) {
                Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
            }
            return NO_MATCH_CATEGORY;
        }
如果存在mismatch,match方法也就直接返回不匹配了。

上面大概的分析了一下匹配的过程,回到文章一开始的问题,为什么Intent没有设置Action,设置了Data和Category就可以匹配到组件,而只设置了Category就不行呢?大家可以先看这行代码

if (action != null && !matchAction(action)) {
            if (false) Log.v(
                logTag, "No matching action " + action + " for " + this);
            return NO_MATCH_ACTION;
        }
&&是短路的,如果Action为null,那么这个if判断已经是false了,所以根本不在乎Action是不是匹配。所以如果IntentFilter设置了Action而Intent不设置Action,理论上是有可能会匹配到的,那么这个可能是什么呢?接下去看。

int dataMatch = matchData(type, scheme, data);
        if (dataMatch < 0) {
            if (false) {
                if (dataMatch == NO_MATCH_TYPE) {
                    Log.v(logTag, "No matching type " + type
                          + " for " + this);
                }
                if (dataMatch == NO_MATCH_DATA) {
                    Log.v(logTag, "No matching scheme/path " + data
                          + " for " + this);
                }
            }
            return dataMatch;
        }

        String categoryMismatch = matchCategories(categories);
        if (categoryMismatch != null) {
            if (false) {
                Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
            }
            return NO_MATCH_CATEGORY;
        }

        // It would be nice to treat container activities as more
        // important than ones that can be embedded, but this is not the way...
        if (false) {
            if (categories != null) {
                dataMatch -= mCategories.size() - categories.size();
            }
        }

        return dataMatch;
    }
大家可以看到match方法返回的是dataMatch,也就是Data匹配的值,从 Intent匹配规则以及解析框架深入分析这个篇文章大家知道只有match方法返回的值是大于0的才会被加入到匹配的list中,所以,如果一个Intent不含有Action,但是含有匹配IntentFilter的Action,并且Category也符合的话,它是会被加入到匹配list中的,至于Category符合的条件前面也说过了,就是Intent不能含有IntentFilter中没有的Category。所以这就说明了为什么没有Action,设置了Data和Category还是可以匹配到。但是为什么只设置了Category就不行了,还是要看 Intent匹配规则以及解析框架深入分析这篇文章,里面有一段代码

public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {  
     String scheme = intent.getScheme();  
     //用来保存查找到的组件信息,如Activity等  
     ArrayList<R> finalList = new ArrayList<R>();  
     //根据关键值去特定集合查询到的一个可能结果  
     ArrayList<F> firstTypeCut = null;  
     ArrayList<F> secondTypeCut = null;  
     ArrayList<F> thirdTypeCut = null;  
     ArrayList<F> schemeCut = null;  
  
     //首先是否制定的数据类型 MimeType  
     // If the intent includes a MIME type, then we want to collect all of  
     // the filters that match that MIME type.  
     <pre name="code" class="java">if (resolvedType != null) {  
         int slashpos = resolvedType.indexOf('/');  
         if (slashpos > 0) {  
             final String baseType = resolvedType.substring(0, slashpos);  
             if (!baseType.equals("*")) {  
                //匹配特定的MimeType  
                 if (resolvedType.length() != slashpos+2|| resolvedType.charAt(slashpos+1) != '*') {  
                     firstTypeCut = mTypeToFilter.get(resolvedType);  
                     secondTypeCut = mWildTypeToFilter.get(baseType);  
                 }   
                 //...  
         }  
     }  
     //根据模式去匹配特定的集合  
     if (scheme != null) {  
         schemeCut = mSchemeToFilter.get(scheme);  
     }  
     //可能的话在去匹配Action所在集合  
     if (resolvedType == null && scheme == null && intent.getAction() != null) {  
         firstTypeCut = mActionToFilter.get(intent.getAction());  
     }  
     //对我们前面通过关键字查询的一个集合,在此循环遍历匹配,将匹配到的结果保存在finalList集合中  
     if (firstTypeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,  
                 resolvedType, scheme, firstTypeCut, finalList);  
     }  
     if (secondTypeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,  
                 resolvedType, scheme, secondTypeCut, finalList);  
     }  
     if (thirdTypeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,resolvedType, scheme, thirdTypeCut, finalList);  
     }  
     if (schemeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,  
                 resolvedType, scheme, schemeCut, finalList);  
     }  
     //根据IntentFilter的一些优先级进行排序  
     sortResults(finalList);  
  
     return finalList;  
 }  

 其中 


if (resolvedType != null) {  
         int slashpos = resolvedType.indexOf('/');  
         if (slashpos > 0) {  
             final String baseType = resolvedType.substring(0, slashpos);  
             if (!baseType.equals("*")) {  
                //匹配特定的MimeType  
                 if (resolvedType.length() != slashpos+2|| resolvedType.charAt(slashpos+1) != '*') {  
                     firstTypeCut = mTypeToFilter.get(resolvedType);  
                     secondTypeCut = mWildTypeToFilter.get(baseType);  
                 }   
                 //...  
         }  
     }  
     //根据模式去匹配特定的集合  
     if (scheme != null) {  
         schemeCut = mSchemeToFilter.get(scheme);  
     }  
     //可能的话在去匹配Action所在集合  
     if (resolvedType == null && scheme == null && intent.getAction() != null) {  
         firstTypeCut = mActionToFilter.get(intent.getAction());  
     }  
     //对我们前面通过关键字查询的一个集合,在此循环遍历匹配,将匹配到的结果保存在finalList集合中  
     if (firstTypeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,  
                 resolvedType, scheme, firstTypeCut, finalList);  
     }  
     if (secondTypeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,  
                 resolvedType, scheme, secondTypeCut, finalList);  
     }  
     if (thirdTypeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,resolvedType, scheme, thirdTypeCut, finalList);  
     }  
     if (schemeCut != null) {  
         buildResolveList(intent, debug, defaultOnly,  
                 resolvedType, scheme, schemeCut, finalList);  
     }  
     //根据IntentFilter的一些优先级进行排序  
     sortResults(finalList);  
  
     return finalList;  
 }  

大家可以看到,完全没有关于Category的代码,也就是说如果只设置了Category,系统根本就不会去进行匹配。

说了这么说,总结一下其实就是,Action,Data,Category这三种匹配数据,Action和Data是比较重要的,Category是一种附属,不能单单在Intent里去设置Category,这样是匹配不到任何IntentFilter的,而如果在没有设置Action的情况下,通过Data和Category还是可以进行匹配的。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值