原创
  • 2020-07-16
  • 浏览 (11)
  • 评论 (0)

UriMatcher 的 match 匹配算法

前言

我们知道创建一个内容提供器 Content Provider 的时候,需要继承 ContentProvider 重写一些方法。外部程序使用我们的内容提供器必须传入一个 URI 对象,一个标准的内容 URI 写法是这样的:

content://cn.blogss.android_study/table1

除此之外,还有以下几种形式:

content://cn.blogss.android_study/table1/1, 调用方期望访问这个应用 table1 表中 id 为 1 的数据
content://cn.blogss.android_study/*, 一个能匹配任意表的内容 URI, (*表示匹配任意长度的任意字符)
content://cn.blogss.android_study/table1/#, 一个能匹配 table1 表中任意一行数据的的内容 URI, (#表示匹配任意长度的数字)

它主要由两部分组成:authority 和 path 。authority 是用于对不同应用程序做区分的。path 则是用于对同一应用程序中不同的表做区分的。通常都会添加到 authority 的后面。如上,cn.blogss.android_study 是应用程序名,table1 是表名。

使用者传入对应的 URI 后,我们需要借助 UriMatcher 的 match(Uri uri) 方法对该 URI 进行匹配,下面对此过程进行分析。

UriMatcher 类结构

image

addURI 过程

分析源码之前,先上一张存储 URI 的数据结构图。便能更好的理解 addURI 的过程。
image

UriMatcher#addURI
public void addURI(String authority, String path, int code)
{
    /*自定义的 code 值必须 >= 0,否则抛出异常*/
    if (code < 0) {
        throw new IllegalArgumentException("code " + code + " is invalid: it must be positive");
    }

    String[] tokens = null;
    if (path != null) {
        String newPath = path;
        // Strip leading slash if present.
        /*
         * 移除字符串头的斜杠 '/'
         * 即 path 的规范传值是不需要以 '/' 开头的,即使你传入的 path 以 '/'开头,该处也会过滤掉
         */
        if (path.length() > 1 && path.charAt(0) == '/') {
            newPath = path.substring(1);
        }
        /*将 newPath 分割为数组*/
        tokens = newPath.split("/");
    }

    /*计算 token 数组的长度,如果传入的 path 是个空字符串,则 numTokens 的长度赋值为 0*/
    int numTokens = tokens != null ? tokens.length : 0;
    UriMatcher node = this;
    /*遍历 tokens 数组,将 tokens 数组的值按层级依次放入 mchildren 中*/
    for (int i = -1; i < numTokens; i++) {/**/
        /*首先处理 authority,i=-1时, token 的值为 authority*/
        String token = i < 0 ? authority : tokens[i];
        ArrayList<UriMatcher> children = node.mChildren;
        int numChildren = children.size();
        UriMatcher child;
        int j;
        /*
        * 遍历当前 UriMatcher 对象的 mChildren, mChildren 是个动态数组,存储的对象类型是UriMatcher
        * 如果当前遍历的 mChildren 中某个 UriMatcher 对象的 mText 与 当前的 token 值相等,说明当前层级该 token
        * 已经存在,直接进入下一个层级比对下一个 token 值
        */
        for (j = 0; j < numChildren; j++) {
            child = children.get(j);
            if (token.equals(child.mText)) {
                /*当前层级有该 token ,node 指向下一个层级*/
                node = child;
                break;
            }
        }

        /*当前层级该 token 不存在,添加这个 token 值*/
        if (j == numChildren) {
            // Child not found, create it
            child = createChild(token);
            node.mChildren.add(child);
            /*node 指向下一个层级*/
            node = child;
        }
    }
    node.mCode = code;
}

match 过程

理解了 addURI 的过程,再回头来看 match 过程就比较简单了。和常规的方法一样,使用两层 for 循环进行遍历,外层循环遍历所有的层级,内层循环遍历当前层级所有的token,进行匹配,时间复杂度是O(n^2)。

UriMatcher#match(Uri uri)
public int match(Uri uri){
    final List<String> pathSegments = uri.getPathSegments();
    final int li = pathSegments.size();

    UriMatcher node = this;

    if (li == 0 && uri.getAuthority() == null) {
        return this.mCode;
    }

    for (int i=-1; i<li; i++) {/*遍历所有层级*/
        String u = i < 0 ? uri.getAuthority() : pathSegments.get(i);
        ArrayList<UriMatcher> list = node.mChildren;
        if (list == null) {
            break;
        }
        node = null;
        int lj = list.size();
        /*
        * 遍历当前层级所有的token与当前传入的token进行匹配,匹配成功 node != null 并进入下一个层级
        * 匹配失败 node == null ,直接返回 -1,结束本方法。
        */
        for (int j=0; j<lj; j++) {
            UriMatcher n = list.get(j);
      which_switch:
            switch (n.mWhich) {
                case EXACT:
                    if (n.mText.equals(u)) {
                        node = n;
                    }
                    break;
                case NUMBER:
                    int lk = u.length();
                    for (int k=0; k<lk; k++) {
                        char c = u.charAt(k);
                        if (c < '0' || c > '9') {
                            break which_switch;
                        }
                    }
                    node = n;
                    break;
                case TEXT:
                    node = n;
                    break;
            }
            if (node != null) {
                break;
            }
        }
        if (node == null) {/*当前层级没有对应的token,匹配失败,返回-1*/
            return NO_MATCH;
        }
    }
    return node.mCode;
}
正文到此结束
本文目录