Uri与UriMatcher

一.URI简介

概念:统一资源标识符(Uniform Resource Identifier)
组成部分:
1.访问资源的命名机制(scheme)
2.存放资源的主机名(authority)
3.资源自身的名称,由路径表示(path)

格式:scheme:// authority//path,其中authority中又包括了host和port两部分。

content://com.example.project:200/folder/subfolder/etc
 \---------/ \-------------------/ \----------------------------/
 Scheme          host        port              path
            \-------------------/
                   Authority

用处:uri主要用来表示一个资源。这个资源有很多种类,包括图片,视频,文件等。

针对资源的种类,uri用以下几种scheme标识:

1.Content:主要操作的是ContentProvider,所以它代表的是数据库中的某个资源
2.http:一个网站资源
3.file:本地机器上的某个资源
4.git:git仓库中某个资源
5.ftp:服务器上的某个资源
6.ed2k:(电驴协议)
7.等等……….

http://write.blog.csdn.net/postedit/7313543,  
file:///c:/WINDOWS/clock.avi
git://github.com/user/project-name.git
ftp://user1:1234@地址
ed2k://|file|%5BMAC%E7%89%88%E6%9E%81%E5%93%81%E9%A3%9E%E8%BD%A69%EF%BC%9A%E6%9C%80%E9%AB%98%E9%80%9A%E7%BC%89%5D.%5BMACGAME%5DNeed.For.Speed.Most.Wanted.dmg|4096933888|2c55f0ad2cb7f6b296db94090b63e88e|h=ltcxuvnp24ufx25h2x7ugfaxfchjkwxa|/

二.Uri源码

位置:frameworks\base\core\java\android\net\Uri.java

android中对Uri分为两大类:Hierarchical和opaque
Hierarchical:以”//”分级的Uri,比如:http://google.com,content://com.example.project:200/
opaque:没有使用”//”分级的Uri,比如:mailto:nobody@google.com

根据这个分类设定,Uri这个抽象类的实现类和继承关系如下:
Uri与UriMatcher_第1张图片

//Uri是一个抽象类,支持序列化。提供了各种函数用于获取组成Uri的各个部分(scheme,authority,path)
public abstract class Uri implements Parcelable, Comparable<Uri> {
   //私有构造函数
   private Uri() {}
    /**
         * Returns true if this URI is hierarchical like "http://google.com".
     * Absolute URIs are hierarchical if the scheme-specific part starts with
     * a '/'. Relative URIs are always hierarchical.
     */
    //当前uri是否是Hierarchical类型
    public abstract boolean isHierarchical();   
    /**
     * Returns true if this URI is opaque like "mailto:nobody@google.com". The
     * scheme-specific part of an opaque URI cannot start with a '/'.
     */
    //当前uri是否是Opaque类型
    public boolean isOpaque() {
        return !isHierarchical();
    }    
    /**
     * Gets the scheme of this URI. Example: "http"
     *
     * @return the scheme or null if this is a relative URI
     */
    @Nullable
    public abstract String getScheme();    
    /**
     * Gets the decoded authority part of this URI. For
     * server addresses, the authority is structured as follows:
     * {@code [ userinfo '@' ] host [ ':' port ]}
     *
     * 

Examples: "google.com", "bob@google.com:80" * * @return the authority for this URI or null if not present */ @Nullable public abstract String getAuthority(); /** * Gets the decoded user information from the authority. * For example, if the authority is "nobody@google.com", this method will * return "nobody". * * @return the user info for this URI or null if not present */ @Nullable public abstract String getUserInfo(); /** * Gets the encoded host from the authority for this URI. For example, * if the authority is "bob@google.com", this method will return * "google.com". * * @return the host for this URI or null if not present */ @Nullable public abstract String getHost(); /** * Gets the port from the authority for this URI. For example, * if the authority is "google.com:80", this method will return 80. * * @return the port for this URI or -1 if invalid or not present */ public abstract int getPort(); /** * Gets the decoded path. * * @return the decoded path, or null if this is not a hierarchical URI * (like "mailto:nobody@google.com") or the URI is invalid */ @Nullable public abstract String getPath(); /** * Creates a Uri from a file. The URI has the form * "file://". Encodes path characters with the exception of * '/'. * *

Example: "file:///tmp/android.txt" * * @throws NullPointerException if file is null * @return a Uri for the given file */ //提供获取Uri对象的静态函数。返回代表File的uri public static Uri fromFile(File file) { if (file == null) { throw new NullPointerException("file"); } PathPart path = PathPart.fromDecoded(file.getAbsolutePath()); return new HierarchicalUri( "file", Part.EMPTY, path, Part.NULL, Part.NULL); } /** * Creates a Uri which parses the given encoded URI string. * * @param uriString an RFC 2396-compliant, encoded URI * @throws NullPointerException if uriString is null * @return Uri for this given uri string */ //提供获取Uri对象的静态函数。返回传入的string包装成uri public static Uri parse(String uriString) { return new StringUri(uriString); } /** * Creates an opaque Uri from the given components. Encodes the ssp * which means this method cannot be used to create hierarchical URIs. * * @param scheme of the URI * @param ssp scheme-specific-part, everything between the * scheme separator (':') and the fragment separator ('#'), which will * get encoded * @param fragment fragment, everything after the '#', null if undefined, * will get encoded * * @throws NullPointerException if scheme or ssp is null * @return Uri composed of the given scheme, ssp, and fragment * * @see Builder if you don't want the ssp and fragment to be encoded */ //提供获取Uri对象的静态函数。根据提供的参数返回Opaque类型的uri public static Uri fromParts(String scheme, String ssp, String fragment) { if (scheme == null) { throw new NullPointerException("scheme"); } if (ssp == null) { throw new NullPointerException("ssp"); } return new OpaqueUri(scheme, Part.fromDecoded(ssp), Part.fromDecoded(fragment)); }

以上是Uri.java中比较常用的几个函数。至于其子类OpaqueUri,HierarchicalUri ,StringUri,实现上并不是太难,不做展开。这里需要特殊说明的是Uri的内部类Builder,这个Builder是一个辅助类,用于Uri对象的构建,并发不安全。

    public static final class Builder {

        private String scheme;
        private Part opaquePart;
        private Part authority;
        private PathPart path;
        private Part query;
        private Part fragment;

        /**
         * Constructs a new Builder.
         */
        //Builder的构造函数
        public Builder() {}
        //提供了众多的函数去获取Builder对象。
        public Builder scheme(String scheme) {
            this.scheme = scheme;
            return this;
        }    
        ......
        /**
         * Constructs a Uri with the current attributes.
         *
         * @throws UnsupportedOperationException if the URI is opaque and the
         *  scheme is null
         */
        //获取Uri对象
        public Uri build() {
            if (opaquePart != null) {
                if (this.scheme == null) {
                    throw new UnsupportedOperationException(
                            "An opaque URI must have a scheme.");
                }

                return new OpaqueUri(scheme, opaquePart, fragment);
            } else {
                // Hierarchical URIs should not return null for getPath().
                PathPart path = this.path;
                if (path == null || path == PathPart.NULL) {
                    path = PathPart.EMPTY;
                } else {
                    // If we have a scheme and/or authority, the path must
                    // be absolute. Prepend it with a '/' if necessary.
                    if (hasSchemeOrAuthority()) {
                        path = PathPart.makeAbsolute(path);
                    }
                }

                return new HierarchicalUri(
                        scheme, authority, path, query, fragment);
            }
        }         
        ......
   }   

Builder 对象的构建有多种方式,可以根据组成Uri的各个部分花式获取Builder 对象,具体可以详细看源码。之后使用Builder 对象调用build()函数即可获得Uri对象。至于获取的Uri对象的类型是Hierarchical,还是opaque,或者是content,file,git等等,就看你是传入什么参数去获取Builder 对象了。


三. UriMatcher的源码

位置:frameworks\base\core\java\android\content\UriMatcher.java

Uri与UriMatcher的关系:
Uri保存了资源文件的路径,代表了资源文件。一个应用内有很多资源,如果我们想把其中一部分资源共享给其他应用,并集中起来管理,方便查询和匹配,这就需要另外一个类来协调,这个类就是UriMatcher。

public class UriMatcher
{
    public static final int NO_MATCH = -1; //没有匹配到uri,返回-1
    private static final int EXACT = 0;
    private static final int NUMBER = 1; //此处用来代表通配符"#"
    private static final int TEXT = 2; //此处用来代表任意字符"*"
    //mCode是UriMatcher用来标识Uri的,如果匹配到Uri,则返回此mCode,否则返回NO_MATCH
    private int mCode;
    private int mWhich;//用来标识特殊字符的,比如通配符"#",任意字符"*"
    private String mText;//用来保存Uri的某个组成部分的。比如Scheme等
    private ArrayList mChildren;//使用ArrayList来保存完整的Uri
    /**
     * Creates the root node of the URI tree.
     *
     * @param code the code to match for the root URI
     */
    public UriMatcher(int code)
    {
        mCode = code;
        mWhich = -1;
        mChildren = new ArrayList();
        mText = null;
    }

    private UriMatcher()
    {
        mCode = NO_MATCH;
        mWhich = -1;
        mChildren = new ArrayList();
        mText = null;
    }

    /**
     * Add a URI to match, and the code to return when this URI is
     * matched. URI nodes may be exact match string, the token "*"
     * that matches any text, or the token "#" that matches only
     * numbers.
     * 

* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, * this method will accept a leading slash in the path. * * @param authority the authority to match * @param path the path to match. * may be used as a wild card for * any text, and # may be used as a wild card for numbers. * @param code the code that is returned when a URI is matched * against the given components. Must be positive. */ /*保存Uri。把Uri分段成authority和path两部分,当成参数传入addURI()函数中。 *必须传入code,且不小于0,否则抛出异常。 */ public void addURI(String authority, String path, int code) { if (code < 0) { throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); } //把path按照"/"分割并保存到string数组tokens 中。 String[] tokens = null; if (path != null) { String newPath = path; // Strip leading slash if present. if (path.length() > 1 && path.charAt(0) == '/') { newPath = path.substring(1); } tokens = newPath.split("/"); } int numTokens = tokens != null ? tokens.length : 0; UriMatcher node = this; //循环tokens数组 for (int i = -1; i < numTokens; i++) { //先把authority保存到当前UriMatcher对象的ArrayList集合mChildren中 String token = i < 0 ? authority : tokens[i]; ArrayList children = node.mChildren; int numChildren = children.size(); UriMatcher child; int j; for (j = 0; j < numChildren; j++) { child = children.get(j); if (token.equals(child.mText)) { node = child; break; } } //新构建UriMatcher对象,把分割的path分别保存进去,形成单链表结构 if (j == numChildren) { // Child not found, create it child = new UriMatcher(); //如果path分割后得到通配符"#",mWhich赋值为NUMBER if (token.equals("#")) { child.mWhich = NUMBER; //如果path分割后得到通配符"*",mWhich赋值为TEXT } else if (token.equals("*")) { child.mWhich = TEXT; } else { child.mWhich = EXACT; } child.mText = token;//保存分割的path或者authority //新构建的UriMatcher对象保存到当前操作节点的ArrayList集合中 node.mChildren.add(child); node = child;//操作节点后移 } } node.mCode = code; } /** * Try to match against the path in a url. * * @param uri The url whose path we will match against. * * @return The code for the matched node (added using addURI), * or -1 if there is no matched node. */ //循环链表结构的UriMatcher去匹配Uri,匹配到返回mCode,否则返回NO_MATCH public int match(Uri uri) { final List pathSegments = uri.getPathSegments(); final int li = pathSegments.size(); UriMatcher node = this; if (li == 0 && uri.getAuthority() == null) { return this.mCode; } //循环Uri的构成(authority+path) for (int i=-1; i//获取authority或者path的分割部分 String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); ArrayList list = node.mChildren; if (list == null) { break; } node = null; int lj = list.size(); //循环当前UriMatcher的ArrayList集合 for (int j=0; j//匹配authority或者path的分割部分 switch (n.mWhich) { case EXACT: if (n.mText.equals(u)) { node = n; } break; case NUMBER: int lk = u.length(); for (int k=0; kchar 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) { return NO_MATCH; } } return node.mCode; } }

从代码上看,UriMatcher的存储结构如下:
Uri与UriMatcher_第2张图片


四. UriMatcher的实际使用

位置:packages\providers\TelephonyProvider\src\com\android\providers\telephony\TelephonyProvider.java

TelephonyProvider借助SQLiteOpenHelper创建了telephony.db数据库,并新建了一些table。希望通过TelephonyProvider共享一些数据给其他应用。这些共享数据的Uri被保存到了UriMatcher中,当其他应用使用Uri通过TelephonyProvider访问这些共享数据时,需要先匹配一下Uri是否在UriMatcher中,如果存在,则允许访问,如果不存在,则不允许访问。避免了该应用内所有数据都被公开的可能。

public class TelephonyProvider extends ContentProvider
{
    //数据库与表
    private static final String DATABASE_NAME = "telephony.db";
    private static final String CARRIERS_TABLE = "carriers";
    private static final String CARRIERS_TABLE_TMP = "carriers_tmp";
    private static final String SIMINFO_TABLE = "siminfo";
    private static final String SIMINFO_TABLE_TMP = "siminfo_tmp";  
    private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    ...... 
    //Uri加入到UriMatcher中
    static {
        s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
        s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
        s_urlMatcher.addURI("telephony", "carriers/#", URL_ID);
        s_urlMatcher.addURI("telephony", "carriers/restore", URL_RESTOREAPN);
        s_urlMatcher.addURI("telephony", "carriers/preferapn", URL_PREFERAPN);
        s_urlMatcher.addURI("telephony", "carriers/preferapn_no_update", URL_PREFERAPN_NO_UPDATE);
        s_urlMatcher.addURI("telephony", "carriers/preferapnset", URL_PREFERAPNSET);

        s_urlMatcher.addURI("telephony", "siminfo", URL_SIMINFO);

        s_urlMatcher.addURI("telephony", "carriers/subId/*", URL_TELEPHONY_USING_SUBID);
        s_urlMatcher.addURI("telephony", "carriers/current/subId/*", URL_CURRENT_USING_SUBID);
        s_urlMatcher.addURI("telephony", "carriers/restore/subId/*", URL_RESTOREAPN_USING_SUBID);
        s_urlMatcher.addURI("telephony", "carriers/preferapn/subId/*", URL_PREFERAPN_USING_SUBID);
        s_urlMatcher.addURI("telephony", "carriers/preferapn_no_update/subId/*",
                URL_PREFERAPN_NO_UPDATE_USING_SUBID);
        s_urlMatcher.addURI("telephony", "carriers/preferapnset/subId/*",
                URL_PREFERAPNSET_USING_SUBID);

        s_urlMatcher.addURI("telephony", "carriers/update_db", URL_UPDATE_DB);
        s_urlMatcher.addURI("telephony", "carriers/delete", URL_DELETE);

        // Only called by DevicePolicyManager to manipulate DPC records.
        s_urlMatcher.addURI("telephony", "carriers/dpc", URL_DPC);
        // Only called by DevicePolicyManager to manipulate a DPC record with certain _ID.
        s_urlMatcher.addURI("telephony", "carriers/dpc/#", URL_DPC_ID);
        // Only called by Settings app, DcTracker and other telephony components to get APN list
        // according to whether DPC records are enforced.
        s_urlMatcher.addURI("telephony", "carriers/filtered", URL_FILTERED);
        // Only called by Settings app, DcTracker and other telephony components to get a
        // single APN according to whether DPC records are enforced.
        s_urlMatcher.addURI("telephony", "carriers/filtered/#", URL_FILTERED_ID);
        // Only Called by DevicePolicyManager to enforce DPC records.
        s_urlMatcher.addURI("telephony", "carriers/enforce_managed", URL_ENFORCE_MANAGED);

        s_currentNullMap = new ContentValues(1);
        s_currentNullMap.put(CURRENT, "0");

        s_currentSetMap = new ContentValues(1);
        s_currentSetMap.put(CURRENT, "1");
    }     
    //更该telephony.db数据库
    @Override
    public synchronized int update(Uri url, ContentValues values, String where, String[] whereArgs)
    {
        int count = 0;
        int uriType = URL_UNKNOWN;
        int subId = SubscriptionManager.getDefaultSubscriptionId();

        checkPermission();
        syncBearerBitmaskAndNetworkTypeBitmask(values);

        SQLiteDatabase db = getWritableDatabase();
        //uri匹配才可以更新数据库
        int match = s_urlMatcher.match(url);
        switch (match)
        {    
            case URL_TELEPHONY:
            {
                if (!values.containsKey(EDITED)) {
                    values.put(EDITED, CARRIER_EDITED);
                }

                // Replace on conflict so that if same APN is present in db with edited
                // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
                // edited USER/CARRIER_EDITED
                count = db.updateWithOnConflict(CARRIERS_TABLE, values, where +
                                " and " + IS_NOT_OWNED_BY_DPC, whereArgs,
                        SQLiteDatabase.CONFLICT_REPLACE);
                break;
            }        
            ......
            case URL_SIMINFO: {
                count = db.update(SIMINFO_TABLE, values, where, whereArgs);
                uriType = URL_SIMINFO;
                break;
            }

            default: {
                throw new UnsupportedOperationException("Cannot update that URL: " + url);
            }
        }            
        ......
        return count;
    }        

五.常用Uri

显示网页:
1. Uri uri = Uri.parse(“http://www.google.com“);
2. Intent it = new Intent(Intent.ACTION_VIEW,uri);
3. startActivity(it);

显示地图:
1. Uri uri = Uri.parse(“geo:38.899533,-77.036476”);
2. Intent it = new Intent(Intent.Action_VIEW,uri);
3. startActivity(it);

路径规划:
1. Uri uri = Uri.parse(“http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en“);
2. Intent it = new Intent(Intent.ACTION_VIEW,URI);
3. startActivity(it);

拨打电话:
调用拨号程序
1. Uri uri = Uri.parse(“tel:xxxxxx”);
2. Intent it = new Intent(Intent.ACTION_DIAL, uri);
3. startActivity(it);
1. Uri uri = Uri.parse(“tel.xxxxxx”);
2. Intent it =new Intent(Intent.ACTION_CALL,uri);
3. 要使用这个必须在配置文件中加入

发送SMS/MMS
调用发送短信的程序
1. Intent it = new Intent(Intent.ACTION_VIEW);
2. it.putExtra(“sms_body”, “The SMS text”);
3. it.setType(“vnd.android-dir/mms-sms”);
4. startActivity(it);
发送短信
1. Uri uri = Uri.parse(“smsto:0800000123”);
2. Intent it = new Intent(Intent.ACTION_SENDTO, uri);
3. it.putExtra(“sms_body”, “The SMS text”);
4. startActivity(it);
发送彩信
1. Uri uri = Uri.parse(“content://media/external/images/media/23”);
2. Intent it = new Intent(Intent.ACTION_SEND);
3. it.putExtra(“sms_body”, “some text”);
4. it.putExtra(Intent.EXTRA_STREAM, uri);
5. it.setType(“image/png”);
6. startActivity(it);

发送Email
1.
2. Uri uri = Uri.parse(“mailto:xxx@abc.com”);
3. Intent it = new Intent(Intent.ACTION_SENDTO, uri);
4. startActivity(it);
1. Intent it = new Intent(Intent.ACTION_SEND);
2. it.putExtra(Intent.EXTRA_EMAIL, “me@abc.com”);
3. it.putExtra(Intent.EXTRA_TEXT, “The email body text”);
4. it.setType(“text/plain”);
5. startActivity(Intent.createChooser(it, “Choose Email Client”));
1. Intent it=new Intent(Intent.ACTION_SEND);
2. String[] tos={“me@abc.com”};
3. String[] ccs={“you@abc.com”};
4. it.putExtra(Intent.EXTRA_EMAIL, tos);
5. it.putExtra(Intent.EXTRA_CC, ccs);
6. it.putExtra(Intent.EXTRA_TEXT, “The email body text”);
7. it.putExtra(Intent.EXTRA_SUBJECT, “The email subject text”);
8. it.setType(“message/rfc822”);
9. startActivity(Intent.createChooser(it, “Choose Email Client”));

添加附件
1. Intent it = new Intent(Intent.ACTION_SEND);
2. it.putExtra(Intent.EXTRA_SUBJECT, “The email subject text”);
3. it.putExtra(Intent.EXTRA_STREAM, “[url=]file:///sdcard/mysong.mp3[/url]”);
4. sendIntent.setType(“audio/mp3”);
5. startActivity(Intent.createChooser(it, “Choose Email Client”));

播放多媒体
1.
2. Intent it = new Intent(Intent.ACTION_VIEW);
3. Uri uri = Uri.parse(“[url=]file:///sdcard/song.mp3[/url]”);
4. it.setDataAndType(uri, “audio/mp3”);
5. startActivity(it);
1. Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, “1”);
2. Intent it = new Intent(Intent.ACTION_VIEW, uri);
3. startActivity(it);

Uninstall 程序
1. Uri uri = Uri.fromParts(“package”, strPackageName, null);
2. Intent it = new Intent(Intent.ACTION_DELETE, uri);
3. startActivity(it);

//调用相册
public static final String MIME_TYPE_IMAGE_JPEG = “image/*”;
public static final int ACTIVITY_GET_IMAGE = 0;
Intent getImage = new Intent(Intent.ACTION_GET_CONTENT);
getImage.addCategory(Intent.CATEGORY_OPENABLE);
getImage.setType(MIME_TYPE_IMAGE_JPEG);
startActivityForResult(getImage, ACTIVITY_GET_IMAGE);

//调用系统相机应用程序,并存储拍下来的照片
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
time = Calendar.getInstance().getTimeInMillis();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment
.getExternalStorageDirectory().getAbsolutePath()+”/tucue”, time + “.jpg”)));
startActivityForResult(intent, ACTIVITY_GET_CAMERA_IMAGE);

uninstall apk
/**未测试
Uri uninstallUri = Uri.fromParts(“package”, “xxx”, null);
returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri);
*/
Uri packageURI = Uri.parse(“package:”+wistatmap);
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
startActivity(uninstallIntent);

install apk
Uri installUri = Uri.fromParts(“package”, “xxx”, null);
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);
play audio
Uri playUri = Uri.parse(“[url=]file:///sdcard/download/everything.mp3[/url]”);
returnIt = new Intent(Intent.ACTION_VIEW, playUri);

//发送附件
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_SUBJECT, “The email subject text”);
it.putExtra(Intent.EXTRA_STREAM, “[url=]file:///sdcard/eoe.mp3[/url]”);
sendIntent.setType(“audio/mp3”);
startActivity(Intent.createChooser(it, “Choose Email Client”));

//搜索应用
Uri uri = Uri.parse(“market://search?q=pname:pkg_name”);
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where pkg_name is the full package path for an application

//进入联系人页面
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(People.CONTENT_URI);
startActivity(intent);

//查看指定联系人
Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, info.id);//info.id联系人ID
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(personUri);
startActivity(intent);

你可能感兴趣的:(android基础)