从Android源码剖析Intent查询匹配的实现
前言
这篇文章主要是介绍一下android intent,并且从android源码的角度对intent查询匹配过程进行分析。
intent介绍
intent的中文是“意图”的意思,而意图是一个非常抽象的概念,那么在android的编码设计中,如何实例化意图呢?因此android系统明确指定一个intent可由两方面属性来衡量。
主要属性:包括action和data。其中action用于表示该intent所表达的动作意图,data用于表示该action所操作的数据。
次要属性:包括category、type、component和extras。其中category表示类别,type表示数据的mime类型,component可用于指定特定的intent的响应者(例如指定intent为某个包下的某个class类),extras用于承载其他的信息。
android系统中主要有两种类型的intent,显示intent(explicit intent)和隐式intent(implicit intent)。
explicit intent:这类intent明确指明了要找哪个component。在代码中可以通过setclassname或者setcomponent来锁定目标对象。
implicit intent:这类intent不明确指明要启动哪个component,而是设置action、data、category让系统来筛选出合适的component。
接下来,写两个代码示例,来介绍一下explicit intent和implict inent。首先是explicit intent:
private void startexplicitintentwithcomponent() { intent intent = new intent(); componentname component = new componentname("com.example.photocrop", "com.example.photocrop.mainactivity"); intent.setcomponent(component); startactivity(intent); } private void startexplicitintentwithclassname() { intent intent = new intent(); intent.setclassname("com.example.photocrop", "com.example.photocrop.mainactivity"); startactivity(intent); }
但是,从源码里面去看,发现setclassname也是借助了componentname实现了explicit intent。源码如下:
public intent setclassname(string packagename, string classname) { mcomponent = new componentname(packagename, classname); return this; }
然后,在给出一个implict intent的代码示例。我这里用一个activity标注一些intent filter为例,然后在写一个intent用于启动它。
<activity android:name=".sendintenttype"> <intent-filter > <action android:name="justtest"/> <category android:name="justcategory"/> </intent-filter> </activity>
在当前应用的androidmanifest.xml中,给sendintenttype类增加了intent-filter,action的名字为“justtest”,category的名字为“justcategory”。启动该activity的代码如下:
private void startimplictintent() { intent intent = new intent(); intent.setaction("justaction"); intent.addcategory("justcategory"); startactivity(intent); }
系统在匹配implict intent的过程中,将以intent filter列出的3项内容为参考标准,具体步骤如下:
- 首先匹配intentfilter的action,如果intent设置的action不满足intentfilter的action,则匹配失败。如果intentfilter未设定action或者设定的action相同,则匹配成功。
- 然后检查intentfilter的category,匹配方法同action的匹配相同,唯一例外的是当category为category_default的情况。
- 最后检查data。
activityi信息的管理
从上面的分析可以看出,系统的匹配intent的过程中,首先需要管理当前系统中所有activity信息。activity的信息是packagemanagerservice在扫描apk的时候进行收集和管理的。相关源码如下:
// 处理该package的activity信息 n = pkg.activities.size(); r = null; for (i = 0; i < n; i++) { packageparser.activity a = pkg.activities.get(i); a.info.processname = fixprocessname(pkg.applicationinfo.processname, a.info.processname, pkg.applicationinfo.uid); mactivities.addactivity(a, "activity"); }
上面代码中,有两个比较重要的数据结构,如下图所示。
结合代码和上图的数据结构,可知:
macitivitys为activityintentresolver类型,是pkms的成员变量,用于保存系统中所有与activity相关的信息。此数据结构内部也有一个mactivities变量,它以componentname为key,保存packageparser.activity对象。
从apk中解析得到的所有和acitivity相关的信息(包括xml中声明的intentfilter标签)都由packageparser.activity来保存。
前面代码中调用addactivity函数完成了私有信息的公有化。addactivity函数的代码如下:
public final void addactivity(packageparser.activity a, string type) { final boolean systemapp = issystemapp(a.info.applicationinfo); mactivities.put(a.getcomponentname(), a); final int ni = a.intents.size(); for (int j = 0; j < ni; j++) { packageparser.activityintentinfo intent = a.intents.get(j); if (!systemapp && intent.getpriority() > 0 && "activity".equals(type)) { // 非系统apk的priority必须为0 intent.setpriority(0); } addfilter(intent); } }
接下来看一下addfilter函数。函数源码如下:
public void addfilter(f f) { // mfilters保存所有intentfilter信息 mfilters.add(f); int nums = register_intent_filter(f, f.schemesiterator(), mschemetofilter, " scheme: "); int numt = register_mime_types(f, " type: "); if (nums == 0 && numt == 0) { register_intent_filter(f, f.actionsiterator(), mactiontofilter, " action: "); } if (numt != 0) { register_intent_filter(f, f.actionsiterator(), mtypedactiontofilter, " typedaction: "); } }
这里又出现了几种数据结构,它们的类似都是arraymap<string, f[ ]>,其中f为模板参数。
- mschemetofilter:用于保存uri中与scheme相关的intentfilter信息。
- mactiontofilter:用于保存仅设置action条件的intentfilter信息。
- mtypedactiontofilter:用于保存既设置了action又设置了data的mime类型的intentfilter信息。
了解了大概的数据结构之后,我们来看一下register_intent_filter的函数实现:
private final int register_intent_filter(f filter, iterator<string> i, arraymap<string, f[]> dest, string prefix) { if (i == null) { return 0; } int num = 0; while (i.hasnext()) { string name = i.next(); num++; addfilter(dest, name, filter); } return num; }
然后又是一个addfilter函数,明显是一个函数重载,我们来看一下这个addfilter的实现:
private final void addfilter(arraymap<string, f[]> map, string name, f filter) { f[] array = map.get(name); if (array == null) { array = newarray(2); map.put(name, array); array[0] = filter; } else { final int n = array.length; int i = n; while (i > 0 && array[i-1] == null) { i--; } if (i < n) { array[i] = filter; } else { f[] newa = newarray((n*3)/2); system.arraycopy(array, 0, newa, 0, n); newa[n] = filter; map.put(name, newa); } } }
其实代码还是很简单的,如果f数组存在,则判断容量,不够则扩容,够的话就找到位置插入。如果f数组不存在,则创建一个容量为2的数组,将0号元素赋值为该filter。
intent匹配查询分析
客户端通过applicationpackagemanager输出的queryintentactivities函数向packagemanagerservice发起一次查询请求,代码如下:
@override public list<resolveinfo> queryintentactivities(intent intent, int flags) { return queryintentactivitiesasuser(intent, flags, mcontext.getuserid()); } /** @hide same as above but for a specific user */ @override public list<resolveinfo> queryintentactivitiesasuser(intent intent, int flags, int userid) { try { return mpm.queryintentactivities( intent, intent.resolvetypeifneeded(mcontext.getcontentresolver()), flags, userid); } catch (remoteexception e) { throw new runtimeexception("package manager has died", e); } }
可以看到,queryintentactivities的真正实现是在packagemanagerservice.java中,函数代码如下:
public list<resolveinfo> queryintentactivities(intent intent, string resolvedtype, int flags, int userid) { if (!susermanager.exists(userid)) return collections.emptylist(); enforcecrossuserpermission(binder.getcallinguid(), userid, false, "query intent activities"); componentname comp = intent.getcomponent(); if (comp == null) { if (intent.getselector() != null) { intent = intent.getselector(); comp = intent.getcomponent(); } } if (comp != null) { // explicit的intent,直接根据component得到对应的activityinfo final list<resolveinfo> list = new arraylist<resolveinfo>(1); final activityinfo ai = getactivityinfo(comp, flags, userid); if (ai != null) { final resolveinfo ri = new resolveinfo(); ri.activityinfo = ai; list.add(ri); } return list; } // reader synchronized (mpackages) { final string pkgname = intent.getpackage(); if (pkgname == null) { // implicit intent return mactivities.queryintent(intent, resolvedtype, flags, userid); } final packageparser.package pkg = mpackages.get(pkgname); if (pkg != null) { // 指定了包名的intent return mactivities.queryintentforpackage(intent, resolvedtype, flags, pkg.activities, userid); } return new arraylist<resolveinfo>(); } }
可以看到,explicit intent的实现较为简单,我们重点来看一下implict intent实现。implicit intent调用了queryintent方法,我们来看一下queryintent的实现代码:
public list<resolveinfo> queryintent(intent intent, string resolvedtype, int flags, int userid) { if (!susermanager.exists(userid)) return null; mflags = flags; return super.queryintent(intent, resolvedtype, (flags & packagemanager.match_default_only) != 0, userid); }
继续跟踪到intentresolver.java的queryintent方法,源码如下:
public list<r> queryintent(intent intent, string resolvedtype, boolean defaultonly, int userid) { string scheme = intent.getscheme(); arraylist<r> finallist = new arraylist<r>(); // 最多有4轮匹配操作 f[] firsttypecut = null; f[] secondtypecut = null; f[] thirdtypecut = null; f[] schemecut = null; // if the intent includes a mime type, then we want to collect all of // the filters that match that mime type. if (resolvedtype != null) { int slashpos = resolvedtype.indexof('/'); if (slashpos > 0) { final string basetype = resolvedtype.substring(0, slashpos); if (!basetype.equals("*")) { if (resolvedtype.length() != slashpos+2 || resolvedtype.charat(slashpos+1) != '*') { // not a wild card, so we can just look for all filters that // completely match or wildcards whose base type matches. firsttypecut = mtypetofilter.get(resolvedtype); secondtypecut = mwildtypetofilter.get(basetype); } else { // we can match anything with our base type. firsttypecut = mbasetypetofilter.get(basetype); secondtypecut = mwildtypetofilter.get(basetype); } // any */* types always apply, but we only need to do this // if the intent type was not already */*. thirdtypecut = mwildtypetofilter.get("*"); } else if (intent.getaction() != null) { // the intent specified any type ({@literal *}/*). this // can be a whole heck of a lot of things, so as a first // cut let's use the action instead. firsttypecut = mtypedactiontofilter.get(intent.getaction()); } } } // if the intent includes a data uri, then we want to collect all of // the filters that match its scheme (we will further refine matches // on the authority and path by directly matching each resulting filter). if (scheme != null) { schemecut = mschemetofilter.get(scheme); } // if the intent does not specify any data -- either a mime type or // a uri -- then we will only be looking for matches against empty // data. if (resolvedtype == null && scheme == null && intent.getaction() != null) { firsttypecut = mactiontofilter.get(intent.getaction()); } fastimmutablearrayset<string> categories = getfastintentcategories(intent); 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); } sortresults(finallist); return finallist; }
具体的查询匹配过程是由buildresolvelist函数完成了。查询的匹配实现我就不贴代码了,大家自己去查询看就好了。