欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-05 11:06:48
前言     这篇文章主要是介绍一下android intent,并且从android源码的角度对intent查询匹配过程进行分析。 in...

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

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

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

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

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