深入分析Intent匹配查询

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/42243583

在前面的一篇文章中,我们详细分析了PackageManagerService的启动过程(在后面的文章中,为了方便,我会将PackageManagerService简称PMS),PMS在启动的过程中,会去扫描系统app和用户安装的app,然后将这些app的信息保存到一些数据结构中,在这篇文章中,我们会接着前面一篇文章继续分析Intent匹配查询过程,如果对PMS不是很熟悉的同学建议先去阅读前面一篇文章PackageManagerService启动过程分析

作为一名Android App开发着,我相信你对Intent的使用是再熟悉不过了,例如我想在一个Activity中启动另外一个Activity,会使用如下代码:

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

以上方式称为显示Intent调用,当然有些时候我们会使用隐式Intent,例如:

Intent intent=new Intent("com.android.demo");
this.startActivity(intent);

由于Intent的使用非常简单,所以在这里我不想再去花太多时间去描述它了,我们这里是要从源码的角度去理解通过Intent是如何匹配Acitivity的(Service,Receiver原理也是差不多的)。

我们直接从startActivity函数开始吧(提示:我使用的是4.1源码,不同版本的源码会有些出入),在这里,先给出一张时序图,然后跟着时序图看源码。

图1-1

根据图1-1,当我们调用Activity的startActivity方法时,其实调用的就是调用ContextImpl的startActivity方法

    public void startActivity(Intent intent, Bundle options) {
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intent, -1, options);
    }

在ContextImple的startActivity方法中,会调用Instrumentation的execStartActivity方法,这个方法我就不贴出源码了,它里面其实就是调用了ActivityManagerService的startActivity方法,这个方法里面其实就是调用了ActivityStack的startActivityMayWait方法,该方法又调用了自身的resolveActivity方法,最后调用了PMS的resolveIntent方法了,到这里终于见到了PMS了,在resolveIntent方法里面就是调用了自身的queryIntentActivities方法,queryIntentActivities会返回一个ActivityInfo对象,我们知道一个ActivityInfo对象就是一个Activity的档案对象,记录了一个Acitivity的所有的信息。这里给出queryIntentActivities的源码

public List<ResolveInfo> queryIntentActivities(Intent intent,
            String resolvedType, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }
        if (comp != null) {
            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;
        }
        synchronized (mPackages) {
            final String pkgName = intent.getPackage();
            if (pkgName == null) {
                return mActivities.queryIntent(intent, resolvedType, flags, userId);
            }
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
				// C
                return mActivities.queryIntentForPackage(intent, resolvedType, flags,
                        pkg.activities, userId);
            }
            return new ArrayList<ResolveInfo>();
        }
    }

对于上面的代码,可以看出:

如果Intent 指明了Componet,那么直接通过Componet就可以找到ActivityInfo

如果指定了packagename,那么可以通过packagename找到Package,然后通过Package包含的Activities中进行匹配

如果都不满足,那么需要全系统进行匹配。

写到这里,我们有必要对上一篇文章中的一些重要数据结构进行回忆。

回忆1:PackageManagerService中有两个scanPackageLI,第一个scanPackageLI的第一个参数是File,它的工作就是将指定的文件(apk)的AndroidManifest.xml文件解析成PackageParser.Package对象。我们看看这个对象有哪些字段吧

图1-2

这里我仅仅列出了比较重要的字段,相信大家看了就会明白,Package里面存储了一个apk中的所有信息,其中包括所有的Activity,所有的Service等等。并且在PMS中有一个HashMap保存了所有的Pacakge,其中key就是包名

回忆2:在第二个scanPackageLI中,会将指定Package中的一些信息进行公有化,例如会将activities中的所有Activity加入ActivityIntentResolver类型的变量mActivities变量中。注意这里说的Activity和我们平时用的Activity不是一个类型,它的继承结构如下:

图1-3

回忆3:在scanPackageLI中,通过调用ActivityIntentResolver的addActivity方法,将指定的PackageParser.Activity保存起来,我们看看addActivity做了什么。

/**
     * @param a
     * 		要被保存的Activity
     * @param type
     * 		"activity" or "recevier"
     */
    public final void addActivity(PackageParser.Activity a, String type) {
        final boolean systemApp = isSystemApp(a.info.applicationInfo);
        //保存到一个HashMap中
        mActivities.put(a.getComponentName(), a);
        final int NI = a.intents.size();
        //遍历Activity中所有的IntentFilter,然后调用addFilter方法进行保存
        for (int j=0; j<NI; j++) {
            PackageParser.ActivityIntentInfo intent = a.intents.get(j);
            if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) {
                intent.setPriority(0);
                Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity "
                        + a.className + " with priority > 0, forcing to 0");
            }
            if (DEBUG_SHOW_INFO) {
                Log.v(TAG, "    IntentFilter:");
                intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
            }
            if (!intent.debugCheck()) {
                Log.w(TAG, "==> For Activity " + a.info.name);
            }
            addFilter(intent);
        }
    }

逻辑比较简单,直接进入addFilter函数,看看做了些什么

public void addFilter(F f) {
        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: ");
        }
    }

其中mFilters是一个HashSet类型变量,这个方法首先将ActivityIntentInfo类型变量保存到mFilters中,接着调用了register_intent_filter方法

private final int register_intent_filter(F filter, Iterator<String> i,
            HashMap<String, ArrayList<F>> dest, String prefix) {
        if (i == null) {
            return 0;
        }

        int num = 0;
        while (i.hasNext()) {
            String name = i.next();
            num++;
            if (localLOGV) Slog.v(TAG, prefix + name);
            ArrayList<F> array = dest.get(name);
            if (array == null) {
                //Slog.v(TAG, "Creating new array for " + name);
                array = new ArrayList<F>();
                dest.put(name, array);
            }
            array.add(filter);
        }
        return num;
    }

在看代码之前,需要熟悉这里的数据结构,filter就相当于一个IntentFilter,i 是一个迭代器,通过它我们可以遍历filter所有的scheme,dest就是一个HashMap,key是filter的scheme,值就是一个ArrayList<F>,其实就是通过遍历一个IntentFilter的所有scheme,根据这个scheme找到对应的ArrayList<F>,然后将这个Filter放入ArrayList<F>,然后返回scheme的个数。

现在回到addFilter方法,接着会带调用register_mime_types方法,同样,看看这个方法做了什么

  private final int register_mime_types(F filter, String prefix) {
        final Iterator<String> i = filter.typesIterator();
        if (i == null) {
            return 0;
        }

        int num = 0;
        while (i.hasNext()) {
            String name = i.next();
            num++;
            if (localLOGV) Slog.v(TAG, prefix + name);
            String baseName = name;
            final int slashpos = name.indexOf(‘/‘);
            if (slashpos > 0) {
                baseName = name.substring(0, slashpos).intern();
            } else {
                name = name + "/*";
            }

            ArrayList<F> array = mTypeToFilter.get(name);
            if (array == null) {
                //Slog.v(TAG, "Creating new array for " + name);
                array = new ArrayList<F>();
                mTypeToFilter.put(name, array);
            }
            array.add(filter);

            if (slashpos > 0) {
                array = mBaseTypeToFilter.get(baseName);
                if (array == null) {
                    //Slog.v(TAG, "Creating new array for " + name);
                    array = new ArrayList<F>();
                    mBaseTypeToFilter.put(baseName, array);
                }
                array.add(filter);
            } else {
                array = mWildTypeToFilter.get(baseName);
                if (array == null) {
                    //Slog.v(TAG, "Creating new array for " + name);
                    array = new ArrayList<F>();
                    mWildTypeToFilter.put(baseName, array);
                }
                array.add(filter);
            }
        }

        return num;
    }

这个方法的功能和register_intent_filter方法功能是一样的,只不过register_intent_filter是处理scheme的,这里是处理type的,type的逻辑比scheme复杂。scheme只用了一个mSchemeToFilter存储,而type用了三个,他们分别是:

mWildTypeToFilter:用于保存设置了Data类型“image/*”的IntentFilter,但是设置“image/jpeg”的不算在内

mTypeToFilter:包含了mWildTypeToFilter以及指明了Data类型为确定参数的IntentFilter信息,如“image/jpeg”和“image/*”类型

mBaseTypeToFilter:保存MIME中Base类型的IntentFilter,但是不包含Sub type为"*"的IntentFilter

其实上面回忆的3点都是前面一篇文章的内容,下面就开始研究一下queryIntentActivities的逻辑吧。

public List<ResolveInfo> queryIntentActivities(Intent intent,
            String resolvedType, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }

        if (comp != null) {
            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) {
                return mActivities.queryIntent(intent, resolvedType, flags, userId);
            }
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                return mActivities.queryIntentForPackage(intent, resolvedType, flags,
                        pkg.activities, userId);
            }
            return new ArrayList<ResolveInfo>();
        }
    }

这部分代码逻辑其实也不算复杂,通过Intent拿到ComponetName,如果ComponetName不为null(说明使用的是显示调用),那么通过调用getActivityInfo方法拿到ActivityInfo。getActivityInfo其实就是到mActivities里面根据ComponetName拿到PackageParser.Activity对象,并通过调用PackageParser.generateActivityInfo方法将PackageParser.Activity对象变为ActivityInfo对象。如果ComponetName为null(隐式调用),那么就要分为两种情况:

第一种情况:通过intent拿到包名为Null,那么调用ActivityIntentResolver的queryIntent方法

      public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
                boolean defaultOnly, int userId) {
            if (!sUserManager.exists(userId)) return null;
            mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
        }

代码很少,调用了IntentResolver的queryIntent,直接看queryIntent的源码吧

public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
            int userId) {
        String scheme = intent.getScheme();

        ArrayList<R> finalList = new ArrayList<R>();

        final boolean debug = localLOGV ||
                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);

        if (debug) Slog.v(
            TAG, "Resolving type " + resolvedType + " scheme " + scheme
            + " of intent " + intent);

        ArrayList<F> firstTypeCut = null;
        ArrayList<F> secondTypeCut = null;
        ArrayList<F> thirdTypeCut = null;
        ArrayList<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);
                        if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut);
                        secondTypeCut = mWildTypeToFilter.get(baseType);
                        if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut);
                    } else {
                        // We can match anything with our base type.
                        firstTypeCut = mBaseTypeToFilter.get(baseType);
                        if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut);
                        secondTypeCut = mWildTypeToFilter.get(baseType);
                        if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut);
                    }
                    // Any */* types always apply, but we only need to do this
                    // if the intent type was not already */*.
                    thirdTypeCut = mWildTypeToFilter.get("*");
                    if (debug) Slog.v(TAG, "Third type cut: " + thirdTypeCut);
                } 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 (debug) Slog.v(TAG, "Typed Action list: " + firstTypeCut);
                }
            }
        }

        // 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 (debug) Slog.v(TAG, "Scheme list: " + schemeCut);
        }

        // 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());
            if (debug) Slog.v(TAG, "Action list: " + firstTypeCut);
        }

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

        if (debug) {
            Slog.v(TAG, "Final result list:");
            for (R r : finalList) {
                Slog.v(TAG, "  " + r);
            }
        }
        return finalList;
    }

这个函数看起来很复杂,但是逻辑很简单,我在这里简单的描述一下。

首先如果给定的Intent包含MIME,就到上面(mTypeToFilter,mWildTypeToFilter,mBaseTypeToFilter)里面匹配符合条件的IntentFilter,将结果分别保存到firstTypeCut,secondTypeCut,thirdTypeCut中,然后根据scheme进行匹配,将结果保存到schemeCut,最后调用buildResolveList方法,将action,scheme,categories等因素随后匹配,将结果保存到finalList中去,最后对finalList进行排序。这种情况分析完了。

第二种情况:如果intent中的包名不为Null,根据包名拿到PackageParser.Package对象,调用ActivityIntentResolver的queryIntentForPackage方法即可,此方法中遍历PackageParsr.Package中的mactivities对象,将每个PackageParser.Activity中的所有IntentFilter加入listCut(一个ArrayList)中,然后调用IntentResolve的queryIntentFromList方法,在queryIntentFromList方法中,根据给定的Intent的action,categories,scheme,type等信息匹配listCut中的IntentFilter对象。

好了关于Intent的匹配过程就写到这里。

时间: 2024-08-24 09:20:10

深入分析Intent匹配查询的相关文章

Excel 中使用SQL 语句查询数据(七)-----用LIKE 运算符进行模糊匹配查询

这篇博文要和大家分享的是用LIKE 运算符进行模糊匹配查询下图数据源商品代号包含数字的数据. 我们用Microsoft query连接数据源,步骤请参考本系列第一篇博文.语句如下图 其中 LIKE '%[0-9]%' 执行结果如下 然后将结果导入excel  的sheet中

ElasticSearch查询 第四篇:匹配查询(Match)

匹配(Match)查询属于全文(Fulltext)查询,不同于词条查询,ElasticSearch引擎在处理全文搜索时,首先分析(analyze)查询字符串,然后根据分词构建查询,最终返回查询结果.匹配查询共有三种类型,分别是布尔(boolean).短语(phrase)和短语前缀(phrase_prefix),默认的匹配查询是布尔类型,这意味着,ElasticSearch引擎首先分析查询字符串,根据分析器对其进行分词,例如,对于以下match查询: "query":{ "ma

mysql like 匹配查询出不正确中文的解决办法

本文讲述mysql使用like语句时,匹配查询出不正确中文的解决办法 mysql like 搜索的时候发现,用 select title from tb_name where title like '%a%' 的时候出来的结果除了包含a的名字外连包含中文“新”的名字也出现在搜索结果里面,这令我想弄清楚mysql的匹配模式和规则到底是怎么样的,另外在匹配的时候正则表达式也很常用! 出现这个问题的原因是:MySQL在查询字符串时是大小写不敏感的,在编绎MySQL时一般以ISO-8859字符集作为默认

mysql模糊匹配查询like,regexp,in

mysql模糊匹配查询like,regexp,in 摘要 内容比较简单,无摘要. 关键词 模糊查询  like  regexp  in  contact 正文 下图是示例用到的数据表信息 MySQL提供标准的SQL模式匹配,以及一种基于象Unix实用程序如vi.grep和sed的扩展正则表达式模式匹配的格式 一.SQL模式 SQL的模式匹配允许你使用"_"匹配任何单个字符,而"%"匹配任意数目字符(包括零个字符).在 MySQL中,SQL的模式缺省是忽略大小写的.

easyui-combogrid匹配查询

用到easyui-combogrid,数据比较少的情况,可以一页就显示完毕,然后直接下拉选择.但是对于数据量比较大的情况,一页显示全部显然不合适,好在从easyui-combogrid的数据加载方式可以知道,下拉表格继承自easyui-datagrid,属性和方法也继承自easyui-datagrid,那么当然可以利用表格的分页属性: $('#textYear').combogrid({ panelWidth: 500, idField: 'num', textField: 'id', url:

mysql高级排序&amp;高级匹配查询示例

在大多数应用场景下,我们使用mysql进行查询时只会用到'=', '>' , '<' , in, like 等常用的方法,看起来,大多数情况下,已经足以应付我们的小型应用了.不过,在一些特殊场景,则需要特殊的查询方式了. 1. 根据状态来排序的查询 假设现在一个记录有四种状态,未处理(0).正在处理(2).处理成功(1).处理失败(4),之所以他们的值是这个样子,是因为我们一般情况下是不会用它去排序,所以自然的就想到这样的一些值赋予意义.但是,在排序的时候怎么处理呢? 假如要求的先后顺序是这样

Intent 匹配规则

1.在AndroidManifest.xml中可以为 每个 Activity,Service 设置多个Intent-Filter; 在系统启动和程序安装之后,android会收集AndroidManifest.xml 中配置的 Intent-Filter. 每个intent-filter 从action category data三个量来过滤 intent. Intent-Filter和Intent的设置规则 1.每个intent-filter对象(这里是intent-filter不是 Andr

Activity 中 intent 匹配规则

要想使用隐式 Intent 成功启动 Activity, 必须保证 Intent 中 action.category.data 的设置和要启动的 Activity 的 IntentFilter 相匹配. Intent 和 IntentFilter 的使用方法: IntentFilter 中可以设置上的 1-n 个 action.0-n 个 category.0-n 个data Intent 上可以设置 1-n 个 action.0-n 个 category.1 个 data IntentFilt

Mongodb内嵌数组的完全匹配查询

样例数据: {      "cNo" : "11",     "Details" : [         {              "dDate" : ISODate("2017-04-01T00:00:00.000+0800"),              "bNo" : "No00000000497"         },         {