赞
踩
Android ContentProvider 基本原理和使用详解
Android ContentProvider详解
四大组件之一,IPC通信的方式之一,管理 Android 以结构化方式存放的数据,以相对安全的方式封装数据(表)并且提供简易的处理机制和统一的访问接口供其他程序调用。
Android 的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。
有时候我们需要操作其他应用程序的一些数据,就会用到 ContentProvider。而且 Android 为常见的一些数据提供了默认的 ContentProvider(包括音频、视频、图片和通讯录等)。
进程间进行数据交互 & 共享, 即跨进程通信。
主要用于在不同的应用程序之间实现数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证数据访问的安全性。
底层是采用Binder机制。
要实现与其他的 ContentProvider 通信首先要查找到对应的 ContentProvider 进行匹配。
Android 中 ContenProvider 借助 ContentResolver 通过 Uri 与其他的 ContentProvider 进行匹配通信。
定义:Uniform Resource Identifier , 即统一资源标识符。URI 为系统中的每一个资源赋予一个名字,比方说通话记录。每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。
作用:唯一标识 ContentProvider & 其中的数据,外界进程通过 URI 找到对应的ContentProvider & 其中的数据, 再进行数据操作。
URI分为系统预置和自定义两种,主要介绍自定义URI。
格式:[scheme:][//host:port][path][?query]
如:content://com.example.app.myprovider/tablename/1,以下对各部分进行介绍:
主题名(Schema):URI前缀,安卓CP中的URI固定为:content://,用来说明是一个Content Provider控制这些数据;
URI所对应数据的唯一标识,授权信息(Authority):com.example.app.myprovider,用于唯一标识这个 ContentProvider,外部调用者可以根据这个标识来找到它。
对于第三方应用程序,为了保证 URI 标识的唯一性,它必须是一个完整的、小写的类名。
这个标识在provider元素的authorities属性中说明,一般是定义该 ContentProvider 的包.类的小写名称,如下:
路径(path),表名(tablename):通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义;
记录(ID):表中的某个记录(若无指定,则返回表的全部记录)
对于第三部分路径(path)做进一步的解释,用来表示要操作的数据,构建时应根据实际项目需求而定。如:
操作tablename表中id为11的记录,构建路径:/tablename/11;
操作tablename表中id为11的记录的name字段:tablename/11/name;
操作tablename表中的所有记录:/tablename;
操作来自文件、xml或网络等其他存储方式的数据,如要操作xml文件中tablename节点下name字段:/ tablename/name;
若需要将一个字符串转换成Uri,可以使用Uri类中的parse()方法,如:
Uri uri = URI.parse(“URI = http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack”);
uri 的各个部分在安卓中都是可以通过代码获取的,下面我们就以这个 uri 为例来说下获取各个部分的方法:
uri.getScheme():获取 Uri 中的 scheme 字符串部分,在这里是 http
uri.getHost():获取 Authority 中的 Host 字符串,即 www.baidu.com
uri.getPort():获取 Authority 中的 Port 字符串,即 8080
uri.getPath():获取 Uri 中 path 部分,即 wenku/jiatiao.html
uri.getQuery():获取 Uri 中的 query 部分,即 id=15&name=du
URI中可以进行通配符匹配:
// : 匹配任意长度的任何有效字符的字符串,以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/
// # : 匹配任意长度的数字字符的字符串,以下的URI表示匹配provider中的table表的任意一行
content://com.example.app.provider/table/#
是什么:
MIME 类型一般包含两部分:类型和子类型,如:
text/html
text/css
text/xml
application/pdf
ContentProvider 会根据 URI 来返回 MIME 类型,符合MIME规范,ContentProvider 会返回一个包含两部分的字符串,第一部分标识该URI对应的数据属于多条记录(集合记录,dir)还是单条记录(item),第二部分是自定义类型,如下:
集合记录(dir):vnd.android.cursor.dir/自定义
单条记录(item):vnd.android.cursor.item/自定义
vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写。
Uri 代表要操作的数据,在对数据进行获取时需要在CP中对Uri进行解析和匹配,来判断访问者要访问的数据是否是在本CP中提供。
Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 针对欲访问tablename表所有数据的Uri,匹配成功,并返回匹配码为1
sMatcher.addURI("content://com.wang.provider.myprovider", " tablename ", 1);
// 针对欲访问tablename表某一行单条数据的Uri,匹配成功,并返回匹配码为2
sMatcher.addURI("com.wang.provider.myprovider", "tablename/#", 2);
此处采用 addURI 注册了两个需要用到的 URI;注意,添加第二个 URI 时,路径后面的 id 采用了通配符形式 “#”,表示 URI对应访问表的某一行单条数据。
注册完需要匹配的 Uri 后,可以使用 sMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码,匹配码为调用 addURI() 方法时传入的第三个参数。
switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) {
case 1:
//match 1, todo something
break;
case 2: // 匹配成功
//match 2, todo something
break;
default:
//match nothing, todo something
break;
}
ContentUris 类用于操作 Uri 路径后面的 ID 部分,它有两个比较实用的方法:withAppendedId(Uri uri, long id) 和 parseId(Uri uri)。
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
//生成后的Uri为:content://cn.scu.myprovider/user/7
Uri resultUri = ContentUris.withAppendedId(uri, 7);
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
//获取的结果为:7
long personid = ContentUris.parseId(uri);
分为系统CP和自定义CP
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):用于查询指定 uri 的数据返回一个 Cursor
public Uri insert(Uri uri, ContentValues values):用于向指定uri的 ContentProvider 中添加数据
public int delete(Uri uri, String selection, String[] selectionArgs):用于删除指定 uri 的数据,返回被删除的行数
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):用户更新指定 uri 的数据,返回被更新内容的行数
数据访问的方法 insert,delete 和 update 可能被多个线程同时调用,此时必须是线程安全的。
public String getType(Uri uri):用于返回指定的 Uri 对象所对应的表数据 MIME 类型。
一个URI所对应的数据的MIME类型主要由3部分组成,Android对这3个部分做了如下格式规定:
获取:ContentResolver resolver = getContentResolver();
构造Uri:Uri uri = Uri.parse(“content://com.wang.provider.myprovider/tablename”);
添加记录:
// 添加一条记录
ContentValues values = new ContentValues();
values.put(“name”, “wang1”);
values.put(“age”, 28);
resolver.insert(uri, values);
更新记录:
// 把id为1的记录的name字段值更改新为zhang1
ContentValues updateValues = new ContentValues();
updateValues.put(“name”, “zhang1”);
Uri updateIdUri = ContentUris.withAppendedId(uri, 1);
resolver.update(updateIdUri, updateValues, null, null);
删除记录:
// 删除id为2的记录
Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);
resolver.delete(deleteIdUri, null, null);
如果ContentProvider的访问者需要知道数据发生的变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者。只给出类中监听部分的代码:
public class MyProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("tablename", "tablenameid", values);
getContext().getContentResolver().notifyChange(uri, null);
}
}
而访问者必须使用 ContentObserver 对数据(数据采用 uri 描述)进行监听,当监听到数据变化通知时,系统就会调用 ContentObserver 的 onChange() 方法:
getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),
true, new PersonObserver(new Handler()));
public class PersonObserver extends ContentObserver{
public PersonObserver(Handler handler) {
super(handler);
}
public void onChange(boolean selfChange) {
//to do something
}
}
使用CP实现跨进程数据访问的总流程:
内容提供者进程:
什么是ContentProvider及其使用
ContentProvider的权限管理
ContentProvider,ContentResolver,ContentObserver之间的关系
ContentProvider的实现原理
ContentProvider的优点
Uri是什么
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。