从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函数完成了。查询的匹配实现我就不贴代码了,大家自己去查询看就好了。

(0)

相关推荐

  • Android 正则表达式验证手机号、姓名(包含少数民族)、身份证号

    最近项目中新增的功能,需要对手机号.姓名.身份证号等一些信息进行验证,最好的方法是通过正则表达式来验证,网上查了一些资料,写了这几个工具方法. 1.验证手机号 规则:第一位只能是1,第二位为3-8中的数字,3-11位为任意的数字 /** * 手机号号段校验, 第1位:1: 第2位:{3.4.5.6.7.8}任意数字: 第3-11位:0-9任意数字 * @param value * @return */ public static boolean isTelPhoneNumber(String v

  • Android 搜索结果匹配关键字且高亮显示功能

    1. 单关键字 匹配 如果只是单关键字的话,那么我们先计算出他的下标,它的长度,然后就可以标记下标到下标+长度的这一段为特殊颜色即可,代码如下: if (name != null && name.contains(keyWord)) { int index = name.indexOf(keyWord); int len = keyWord.length(); Spanned temp = Html.fromHtml(name.substring(0, index) + "<

  • Android正则表达式

    要严格的验证手机号码,必须先要清楚现在已经开放了哪些数字开头的号码段,目前国内号码段分配如下: 移动:134.135.136.137.138.139.150.151.157(TD).158.159.187.188 联通:130.131.132.152.155.156.185.186 电信:133.153.180.189.(1349卫通) 验证手机号: public class ClassPathResource { public static boolean isMobileNO(String

  • Android实现自动匹配关键字并且标红功能

    本文实例为大家分享了Android匹配关键字标红的具体代码,供大家参考,具体内容如下 1. 单关键字匹配 若只需匹配 搜索内容  可以写的简单一些,代码如下: if (name != null && name.contains(mKeyWord)) { int index = name.indexOf(mKeyWord); int len = mKeyWord.length(); Spanned temp = Html.fromHtml(name.substring(0, index) +

  • Android中手机号、车牌号正则表达式大全

    手机号 手机名称有GSM:表示只支持中国联通或者中国移动2G号段(130.131.132.134.135.136.137.138.139.145.147.150.151.152.155.156.157.158.159.182.185.186.187.188) 手机名称有CDMA:表示只支持中国电信2G号段(133.153.180.181.189) 手机名称有WCDMA/GSM:表示支持中国联通或者中国移动2G号段,以及中国联通3G号段(130.131.132.134.135.136.137.13

  • Android 中使用ContentObserver模式获取短信用正则自动填充验证码

    最近做注册的时候看到很多app在手机接受到短信的时候直接填写验证码到界面省略用户自动输入,感觉这样确实蛮人性化的呵呵,于是自己也做了一个 步骤: 首先我使用了ContentObserver监听短信,(最好知道您的验证码从那个号码发过来) 然后从短信中用正则的分组去拿到验证码(当然验证码必须是什么格式) 贴出关键代码: 注册监听短信数据库的  ContentObserver c=new ContentObserver(han) { @Override public void onChange(bo

  • Android编程中号码匹配位数修改的方法

    本文实例讲述了Android编程中号码匹配位数修改的方法.分享给大家供大家参考,具体如下: Android2.1上默认的号码匹配位数是7位,也就是说从右向左算起,如果两个号码有7位是匹配的,那么就认为是相同的号码,如+86 1234567和01234567是相同的号码:所以判断两个号码是否匹配,简单的用"=="是不行的. 安卓在Sqlite层新增了函数phone_numbers_equal用于号码匹配的判断,这个函数是数据库级的,对应的SQL语句是PHONE_NUMBERS_EQUAL

  • Android编程开发中的正则匹配操作示例

    本文实例讲述了Android编程开发中的正则匹配操作.分享给大家供大家参考,具体如下: 在Android开发中,可能也会遇到一下输入框的合法性验证,这时候最常用的就应该是正则表达式去做一些匹配了,下面就常用的正则匹配做一下介绍 1. 手机号码的验证 根据实际开发于2009年9月7日最新统计: 中国电信发布中国3G号码段:中国联通185,186;中国移动188,187;中国电信189,180共6个号段. 移动:134.135.136.137.138.139.150.151.157(TD).158.

  • Android Java实现余弦匹配算法示例代码

     Java实现余弦匹配算法 最近在做一个通讯交友的项目,项目中有一个这样的需求,通过用户的兴趣爱好,为用户寻找推荐兴趣相近的好友.其实思路好简单,把用户的兴趣爱好和其他用户的兴趣爱好进行一个匹配,当他们的爱好相似度比较高的时候就给双方进行推荐.那么如何进行比较是一个问题,其实我们可以通过余弦匹配算法来对用户的兴趣爱好进行比较,根据计算出来的值来得到一个兴趣爱好相近好友列表,并进行排序. 因为我做的项目是Android端的,所以算法是通过Java实现的,废话不过多说了,下面是算法的实现: pack

  • Android判断11位手机号码的方法(正则表达式)

    项目里头需要做一个判断用户输入的号码是否是正确的手机号码,正确的手机号码应该是11位的,这里我们需要用一个正则表达式来进行判断,正则表达式的定义如下: public final static String PHONE_PATTERN = "^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$"; 然后我们写一个方法,主要有两个参数,1.正则表达式:2.输入的字符串(号码),方法如下: /** * 正则表达式匹配判断 * @param patternS

随机推荐