IntentFilter 匹配规则,源码解析

Android 中启动Activity 有两种方式
1.显式启动

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

2.隐式启动

      <activity
            android:name=".ui.activity.TestDemo">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <action android:name="android.intent.action.VIEW" />
                <data
                    android:host="com.gl.demo"
                    android:scheme="example"
                    android:path="/TestDemo1"/>
            </intent-filter>
        </activity>


            String uri ="example://com.gl.demo/TestDemo1";
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
            context.startActivity(intent);
Activity 启动部分流程.png

上述流程图中

显式匹配 getActivityInfo ()

PackageManagerService.java

显式启动
 new Intent(this, SecondActivity.class); 构造的conmponentName
 public Intent(Context packageContext, Class<?> cls) {
        mComponent = new ComponentName(packageContext, cls);
    }


   @Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
           ....
        synchronized (mPackages) {
          //根据class name获取
            PackageParser.Activity a = mActivities.mActivities.get(component);

          //检查是否已安装并且可用
            if (a != null && mSettings.isEnabledAndMatchLPr(a.info, flags, userId)) {
                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                if (ps == null) return null;
             
           // Make shallow copies
                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
                        userId);//生成一个匹配的ActivityInfo
            }
        }
        return null;
    }

上述代码中,重要的是mActivities.mActivities

mActivities : PackageManagerService.ActivityIntentResolver

ActivityIntentResolver.png

mActivities.mActivities 的存储过程就是解析AndroidManifest.xml 中的<activity>标签解析并存储的过程,此处不在扩张这个过程。
下面摘出一些简要代码,便于读者简单了解一下mActivities 的数据来源。

PackageParser.java

private boolean parseBaseApplication(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {
           if (tagName.equals("activity")) {//解析xml 中<activity>标签
                Activity a = parseActivity(owner, res, parser, flags, outError, false,
                        owner.baseHardwareAccelerated);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.activities.add(a);
   }
}

PackageManagerService.java

   private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
            final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
            throws PackageManagerException {
            ....省略代码....
//此处的pkg 对应上段代码中的owner,pkg.activities 就是解析完manifest中所有的<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");
              ....省略代码....
            }
}

public final void addActivity(PackageParser.Activity a, String type) {
            mActivities.put(a.getComponentName(), a);
            ....省略代码....
        }

隐式匹配 queryIntent()

ActivityIntentResolver.png

ActivityIntentResolver.java

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

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

        F[] firstTypeCut = null;
        F[] secondTypeCut = null;
        F[] thirdTypeCut = null;
        F[] schemeCut = null;

       .... 省略了 MIME_TYPE 的匹配

        // 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).
 
   /**
     * All of the URI schemes (such as http) that have been registered.
     * private final ArrayMap<String, F[]> mSchemeToFilter = new ArrayMap<String, F[]>();
     */

        if (scheme != null) {
            schemeCut = mSchemeToFilter.get(scheme);
         }
       .... 省略代码...
        FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
       .... 省略代码...
        if (schemeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly,
                    resolvedType, scheme, schemeCut, finalList, userId);
        }
        sortResults(finalList);
        return finalList;
    }

  private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
            boolean debug, boolean defaultOnly,
            String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
        final String action = intent.getAction();
        final Uri data = intent.getData();
        final String packageName = intent.getPackage();

        final boolean excludingStopped = intent.isExcludingStopped();

        ....省略代码....

        final int N = src != null ? src.length : 0;
        boolean hasNonDefaults = false;
        int i;
        F filter;
        for (i=0; i<N && (filter=src[i]) != null; i++) {
            int match;
           ....省略代码....
          //隐式匹配的关键代码都在  filter.match 方法中  
            match = filter.match(action, resolvedType, scheme, data, categories, TAG);
            if (match >= 0) {
             //defaultOnly
                if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
                    final R oneResult = newResult(filter, match, userId);
                    if (oneResult != null) {
                        dest.add(oneResult);
                    }
                } else {
                    hasNonDefaults = true;
                }
            } 
        }
    }

上述代码中,隐式匹配的关键处理代码都在
filter.match(action, resolvedType, scheme, data, categories, TAG)
此处的filter 指向 F
在ActivityResolver 中F 即 PackageParser.ActivityIntentInfo

ActivityIntentInfo.png

所以filter.match ()方法最终指向了IntentFilter 中,终于看到了熟悉的面孔

IntentFilter.java

public final int match(String action, String type, String scheme,
            Uri data, Set<String> categories, String logTag) {
      
       //<action> 匹配
        if (action != null && !matchAction(action)) {
        }
      //<data> 匹配
        int dataMatch = matchData(type, scheme, data);
        if (dataMatch < 0) {
            return dataMatch;
        }
      //<category>匹配
        String categoryMismatch = matchCategories(categories);
        if (categoryMismatch != null) {
            return NO_MATCH_CATEGORY;
        }
        return dataMatch;
    }
<action>匹配规则

IntentFilter.java

  public final boolean matchAction(String action) {
        return hasAction(action);
    }
  public final boolean hasAction(String action) {
        return action != null && mActions.contains(action);
    }

从代码中可以看出
一个Intent Filter中可声明多个action,Intent中的action与其中的任一个action在字符串形式上完全相同(注意,区分大小写,大小写不同但字符串内容相同也会造成匹配失败),action方面就匹配成功.

<data>匹配规则

IntentFilter.java

  public final int matchData(String type, String scheme, Uri data) {
        final ArrayList<String> types = mDataTypes;//对应<data android:mimeType="*/*">
        final ArrayList<String> schemes = mDataSchemes;//对应<data android:schema="">

        int match = MATCH_CATEGORY_EMPTY;

        if (types == null && schemes == null) {
            return ((type == null && data == null)
                ? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);
        }
      //schema 匹配成功,才继续;匹配失败则直接返回
        if (schemes != null) {
            if (schemes.contains(scheme != null ? scheme : "")) {
                match = MATCH_CATEGORY_SCHEME;
            } else {
                return NO_MATCH_DATA;
            }

            //ssp  android:sspPrefix="" 有ssp 标签的则此数组不为空,用的比较少,此处略
            final ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;
            if (schemeSpecificParts != null && data != 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.
                //auth tag   <data>标签下有<android:host=""> mDataAuthorities 不为空
                final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
                if (authorities != null) {
                    int authMatch = matchDataAuthority(data);//host 匹配继续;失败
                    if (authMatch >= 0) {
                 // <data>标签下有<android:path=""> mDataPaths不为空
                        final ArrayList<PatternMatcher> paths = mDataPaths;
                        if (paths == null) {
                            match = authMatch;
                        } else if (hasDataPath(data.getPath())) {//path 匹配
                            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;
            }
        } 
      ....省略代码....

        return match + MATCH_ADJUSTMENT_NORMAL;
    }

从上述代码可以看出 data 匹配:
匹配 schema 成功,则继续匹配;否则不匹配;
匹配 host 成功,继续匹配;否则不匹配;
匹配 path 成功,继续匹配;否则不匹配

<category>匹配

 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 在IntentFilter 中没有,则匹配失败。

那么AndroidManifest.xml 中的<intent-filter>是如何对应解析到IntentFilter 中的呢,下面展示一些关键代码,便于大家与上面数据来源做对比。

 private boolean parseIntent(Resources res, XmlResourceParser parser,
            boolean allowGlobs, boolean allowAutoVerify, IntentInfo outInfo, String[] outError)
            throws XmlPullParserException, IOException {

//IntentInfo extends IntentFilter ,outInfo  的方法对应到IntentFilter 中
          
          String nodeName = parser.getName();
            if (nodeName.equals("action")) {//<action>解析
                    ....
                outInfo.addAction(value);
            } else if (nodeName.equals("category")) {//<category>解析
                    ....
                outInfo.addCategory(value);
            } else if (nodeName.equals("data")) {
                sa = res.obtainAttributes(parser,
                        com.android.internal.R.styleable.AndroidManifestData);

                String str = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_mimeType, 0);
                if (str != null) {//<android:mimeType="">
                    try {
                        outInfo.addDataType(str);
                    } catch (IntentFilter.MalformedMimeTypeException e) {
                        outError[0] = e.toString();
                        sa.recycle();
                        return false;
                    }
                }

                str = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_scheme, 0);//<android:schema="">
                if (str != null) {
                    outInfo.addDataScheme(str);
                }
                                             ....
                String host = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_host, 0);//<android:host="">
                String port = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_port, 0);//<android:port="">
                if (host != null) {
                    outInfo.addDataAuthority(host, port);
                }

                str = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestData_path, 0);//<android:path="">
                                               ....
            }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,835评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,900评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,481评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,303评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,375评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,729评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,877评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,633评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,088评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,443评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,563评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,251评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,827评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,712评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,943评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,240评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,435评论 2 348

推荐阅读更多精彩内容