当前位置: 移动技术网 > IT编程>开发语言>Java > 从Android源码剖析Intent查询匹配的实现

从Android源码剖析Intent查询匹配的实现

2019年07月22日  | 移动技术网IT编程  | 我要评论

前言
    这篇文章主要是介绍一下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"); 
  } 

    上面代码中,有两个比较重要的数据结构,如下图所示。

2015730164435391.png (841×306)

结合代码和上图的数据结构,可知:

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

   

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网