数据共享 ContentProvider,ContentResolver

代码示例在:360云盘:自己的学习资料----Android总结过的项目----ContentProviderExample.rar

一、前言

前面介绍过了数据存储操作的方式可知,每个程序都是线程控制,并且数据无法共享,而 ContentProvider 来解决这个问题,ContentProvider 是所有应用程序之间数据存储与检索的一个桥梁,他的作用就是使得各个程序之间实现数据共享。
可以把 ContentProvider 理解为一种特殊的存储数据的类型,因为 ContentProvider 是个抽象类,继承自该类必须实现:getType,insert,delete,update,query方法,所以它提供了一套上述接口来获取,操作数据。
 
就像我们学习 web 开发刚接触 Model1 的 MVC 时,数据层方面实现了针对实体的增删改查,但是在业务层方面最好封装一个数据操作的 Service,这样的话就避免了业务层直接操作数据层,而把这个工作交给对应的 Service 的实现,更好的管理。这里就引出了ContentResolver,它的作用就可以理解为对应 Service 的实现来操作对应 ContentProvider。

ContentResolver 对象的获取可以通过 getContentResolver()方法来取得
      
这里需要说明的是,每个 ContentProvider 都会对外提供一个公共的 URI(包装成 Uri 对象),如果应用程序有数据需要共享,就需要使用 ContentProvider 为这些数据定义一个 URI,然后其他应用程序就可以通过 ContentProvider 传入这个URI来对数据进行操作。

--------------------------------------------------------------------------------------------
二、URI简介:

http://www.dubblogs.cc:8751/H264.mp4

URI 的作用是为互联网上所涉及到的所有资源比方说 HTML 文档、图像等等,提供一个唯一的标识。

URI 由3部分组成:
第一部分是:协议,如:http。第一部分与第二部分用“://”符号隔开。
第二部分是:存有该资源的主机域名或IP地址,如:www.dubblogs.cc:8751。第二部分与第三部分用“/”符号隔开。
第三部分是:主机资源的具体地址,如:H264.mp4。

(第三部分可以忽略不写,但第一和第二部分必须有)

--------------------------------------------------------------------------------------------
三、ContentProvider 中的 URI:

content://com.prd.contactprovider/contact/20

ContentProvider 中的 URI 也由3个部分组成:

1.协议部分(content://):这个是 android 规定的 content 协议

2.主机名部分(com.prd.contactprovider):需要在 AndroidManifest.xml 中声明用来唯一标识这个ContentProvider,外部调用者就可以根据这个标识来找到他。

3.路径部分(contact/20):用来表示我们要操作的数据,这个就是操作 contact 表中 id 为 20 的记录。contact/20/phone 代表 contact 表中 id 为 20 的记录的 phone 字段。当然,要操作的数据还可以是文件、xml或网络等其他存储方式。

content://media/internal/images   这个URI返回设备上存储的所有图片
content://contacts/people/5       联系人信息中ID为5的联系人记录
content://contacts:/people/       这个URI返回设备上得所有联系人信息。

--------------------------------------------------------------------------------------------
四、将字符串转换成 Uri

android 提供了两个操作 Uri 的工具类,分别是 ContentUris 和 UriMatcher。我们先来看 ContentUris。

1.ContentUris

我们先看他常用的两个方法

1.第一个方法 withAppendedId(uri,id) 这个方法用于为路径加上 ID 部分。

Uri uri = Uri.parse("content://com.test.provider.personprovider/person")
Uri resultUri = ContentUris.withAppendedId(uri, 5);
//生成后的Uri为:content://com.test.provider.personprovider/person/5

其结果等价于 Uri.withAppendedPath(Uri baseUri, String pathSegment)
Uri resultUri = Uri.withAppendedPath(uri, "5");

2.第二个方法:parseId(Uri contentUri) 这个方法用于从路径中获取 ID 部分。

Uri uri = Uri.parse("content://com.test.provider.personprovider/person/5")
long personid = ContentUris.parseId(uri);
//获取的结果 personid 为:5
 
2.UriMatcher,他的作用是来匹配 Uri

1.首先创建一个 UriMatcher 对象,参数 UriMatcher.NO_MATCH 表示不匹配任何路径的返回码(-1)
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

2.接着定义 2 个整形常量(当然可以是多个,要看你注册多少个 Uri 了)
private static final int CONTACTS = 1;
private static final int CONTACT = 2;

3.然后定义一个 static 代码块,他的作用是在项目启动的时候就会执行这个静态代码块,通过 addURI 注册所有 Uri,注意 addURI 里第一个参数是域名部分,不要加协议部分的 content,第二个参数是路径部分,第三个参数是返回的是匹配码也就是 1,第二个 addURI() 方法,如果 Uri 的路径符合第二个方法里的注册路径,就会返回 2 。其中 # 号是通配符用来匹配数字,还可以是 * 用来匹配任意的字符。

static {

// 注册URI路径content://com.prd.contactprovider/contactinfo
matcher.addURI("com.prd.contactprovider", "contactinfo", CONTACTS);

// 注册URI路径content://com.prd.contactprovider/contactinfo/#
matcher.addURI("com.prd.contactprovider", "contactinfo/#", CONTACT);
}

4.使用 uriMatcher.match()方法匹配 Uri 地址并返回匹配值,一般用在 getType(Uri uri)中。


//如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回匹配码为1
uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);//添加需要匹配uri,如果匹配就会返回匹配码

//如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2
uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);//#号为通配符

--------------------------------------------------------------------------------------------
五、ContentProvider 的实现

继承 ContentProvider 类,就会实现 onCreate()、getType()、delete()、insert()、query()、update(),六个方法。

1.onCreate()方法,这个方法是 ContentProvider 创建后就会被调用,当程序第一次访问这个 ContentProvider 时 ContentProvider 就会被创建。

2.getType(Uri uri)方法,这个方法用于返回当前 Uri 所代表数据的 MIME 类型。

问:MIME类型是什么?

答:当我们使用浏览器的时候,向服务器请求数据,服务器会返回很多类型的数据,可能有的是 mp3,有的是 excle文件,也有可能是 word 文件,服务器需要将这些数据的类型告诉浏览器,怎么来告诉呢,就是说明这些数据的 MIME 类型,这样浏览器就会根据 MIME 类型来选择相应的插件,来读取相关文件,比如 word 文件的 MIME 类型就是 .word application/msword,假如我们设定 word 程序,为处理这种 MIME 类型的程序,那么 word 文件就会交给 word 程序处理,这就是 MIME 类型,那么 android 所代表数据的 MIME 类型是什么呢?如果操作的数据属于集合类型,比如 URI 代表数据是一张表,那么 MIME 类型应该以 vnd.android.cursor.dir/ 开头,例如:要得到所有 contact 表也就是联系人表中的数据,那么他的 Uri 就是 content://com.prd.contactprovier/contact ,那么返回的 MIME 类型字符串应该为 vnd.android.cursor.dir/contact 如果要操作的数据属于非集合类型数据,比方说 contact 表中的一条数据,那么 MIME 类型字符串应该以 vnd.android.cursor.item/ 开头,例如得到 id 为 10 的 contact 记录,Uri为 content://com.prd.contactprovier/contact/10 那么返回的 MIME 类型字符串应该为 vnd.android.cursor.item/contact 我们可以对 URI 地址进行 MIME类型分类,相同 MIME 类型的 URI 可以执行相同的操作,方便代码的编写。

3.insert(Uri uri,ContentValues values)方法,用于当外部程序向 ContentProvider 中增加数据的时候调用。他的第一参数就是 Uri 对象,用于指定要操作的数据。第二个参数是 ContentValues,是要增加的数据的内容,我们看 ContentValues 的用法和完整的 insert() 操作。

//声明 ContentValues 保存数据
ContentValues tValues=new ContentValues();
tValues.put("name", "wang"); //name 对应数据库表中的字段 name,值为wang
tValues.put("icon", 123); // icon 对应数据库表中的字段 icon,值为 123,是 int 型,当然,也可以是其他类型。

Uri url=Uri.parse("content://xjl.prd.contactprovider/contactinfo"); //构建 ContentProvider Uri,这里对应 contactinfo 表
Uri tResolver=getContentResolver().insert(url, tValues); //根据 Uri地址保存数据,并返回拼接 id 后的 Uri地址
long id=ContentUris.parseId(tResolver); //返回刚才保存数据的 id

4.delete(Uri uri, String selection, String[] selectionArgs)方法,此方法用于 ContentProvider 删除操作,第一个参数是 Uri 对象,即:要操作的数据表。第二个参数 selection 相当于 sql 语句中的 where 部分。第三个参数 selectionArgs 相当于 where 的值,比如有一个 SQL 语句是用来删除 name 列为 tom 的数据,就是 delete from contact where name="tom",那么 selection 对应着 name 列,selectionArgs 对应着 tom。传参的时候就可以这样,定义一个字符串他的值是 name=?,?是占位符,然后定义一个字符串数组占位符的值在数组里定义。如下边的示例。

String where = "id=?";
String[] selectionArgs = new String[]{"1"};

getContentResolver().delete(url, where, selectionArgs);//删除 id 为 1 的记录

5.update(Uri uri, ContentValues values, String selection, String[] selectionArgs)方法,用于更新,这四个参数前面都介绍过了,uri 指定要操作的数据。values 用来保存要更新的数据。第三个参数和第四个参数用来限定更新条件,相当于 sql 中的 where 部分,用于检索要更新的数据。

ContentValues tUpdateValues=new ContentValues();
tUpdateValues.put("msn", "99999");

String tUpdateWhere = "name=?";
String[] tUpdateSelectionArgs = new String[]{"san1"};

getContentResolver().update(url, tUpdateValues, tUpdateWhere, tUpdateSelectionArgs);

6.query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)方法,用于查询,他的返回值是一个 Cursor 对象。我们看他的参数,第一三四个参数介绍过了。第二个参数 projection 是指定返回查询的列,是一个字符串数组,比方说定义一个字符串数组值是name和phone,那么就返回name和phone列。第五个参数是指定排列方式,比方升序或者降序,如下示例:

Cursor tCursor=getContentResolver().query(url, null, null, null, null);

while (tCursor.moveToNext()) {

String id=tCursor.getString(tCursor.getColumnIndex("id"));
String name=tCursor.getString(tCursor.getColumnIndex("name"));
String msn=tCursor.getString(tCursor.getColumnIndex("msn"));

Log.e(TAG, "id为:"+id+" name为:"+name+" msn为:"+msn);
}

--------------------------------------------------------------------------------------------
六、完整代码

1.先定义数据库,这里不说了。

2.自定义 ContentProvider 类

public class ContactProvider extends ContentProvider {

private DBOpenHelper dbOpenHelper;

private static final UriMatcher matcher = new UriMatcher(
UriMatcher.NO_MATCH);

private static final int CONTACTS = 1;
private static final int CONTACT = 2;
private static final String TABLENAME = "contactinfo";

static {

// 注册URI路径content://com.prd.contactprovider/contactinfo
matcher.addURI("xjl.prd.contactprovider", "contactinfo", CONTACTS);

// 注册URI路径content://com.prd.contactprovider/contactinfo/#
matcher.addURI("xjl.prd.contactprovider", "contactinfo/#", CONTACT);
}

@Override
public boolean onCreate() {

dbOpenHelper = new DBOpenHelper(this.getContext());
return true;
}

/**
* 获得 URI 的 MIME 类型
*/
@Override
public String getType(Uri uri) {

switch (matcher.match(uri)) {

case CONTACTS:

return "vnd.android.cursor.dir/contactinfo";
case CONTACT:

return "vnd.android.cursor.item/contactinfo";

default:
throw new IllegalArgumentException("URI" + uri + "无法解析!");
}
}

// 对数据库进行删除操作
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {

SQLiteDatabase sqLiteDatabase = dbOpenHelper.getWritableDatabase();
int rowNum = 0;

switch (matcher.match(uri)) {

case CONTACTS:

rowNum = sqLiteDatabase.delete(TABLENAME, selection, selectionArgs);
return rowNum;
case CONTACT:

long id = ContentUris.parseId(uri);
String whereClause = "id=" + id;
if (selection != null) {
whereClause += "and" + selection;
}
rowNum = sqLiteDatabase.delete(TABLENAME, whereClause,
selectionArgs);
return rowNum;

default:
throw new IllegalArgumentException("URI" + uri + "无法解析!");
}
}

/**
* 向数据库中添加数据
*/
@Override
public Uri insert(Uri uri, ContentValues values) {

SQLiteDatabase sqLiteDatabase = dbOpenHelper.getWritableDatabase();

switch (matcher.match(uri)) {

case CONTACTS:

long id = sqLiteDatabase.insert(TABLENAME, "name", values);
Uri insertUri = ContentUris.withAppendedId(uri, id);

return insertUri;

default:
throw new IllegalArgumentException("URI" + uri + "无法解析!");
}
}

/**
* 查询数据库表中的数据
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {

SQLiteDatabase sqLiteDatabase = dbOpenHelper.getWritableDatabase();
switch (matcher.match(uri)) {

case CONTACTS:

return sqLiteDatabase.query(TABLENAME, projection, selection,
selectionArgs, null, null, sortOrder);
case CONTACT:

long id = ContentUris.parseId(uri);
String whereClause = "id=" + id;

if (selection != null) {

whereClause += "and" + selection;
}
return sqLiteDatabase.query(TABLENAME, projection, whereClause,
selectionArgs, null, null, sortOrder);

default:
throw new IllegalArgumentException("URI" + uri + "无法解析!");
}
}

/**
* 更新数据库表的记录
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {

SQLiteDatabase sqLiteDatabase = dbOpenHelper.getWritableDatabase();
int rowNum = 0;

switch (matcher.match(uri)) {

case CONTACTS:

rowNum = sqLiteDatabase.update(TABLENAME, values, selection,
selectionArgs);
return rowNum;

case CONTACT:

long id = ContentUris.parseId(uri);
String whereClause = "id=" + id;

if (selection != null) {

whereClause += "and" + selection;
}
rowNum = sqLiteDatabase.update(TABLENAME, values, whereClause,
selectionArgs);
return rowNum;

default:
throw new IllegalArgumentException("URI" + uri + "无法解析!");
}
}
}

/**
* @Description:主页面
*/
public class MainActivity extends Activity {

private static final String TAG = "@@@MainActivity";

/** 构建 ContentProvider Uri,这里对应 contactinfo 表 */
Uri url = Uri.parse("content://xjl.prd.contactprovider/contactinfo");

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

/**
* 添加操作
*/
for (int i = 0; i <= 2; i++) {

// 声明 ContentValues 保存数据
ContentValues tValues = new ContentValues();
tValues.put("name", "san" + i); // name 对应数据库表中的字段 name,值为wang
tValues.put("icon", 123 + i); // icon 对应数据库表中的字段 icon,值为 123,是 int
// 型,当然,也可以是其他类型。

/* 根据 Uri 地址保存数据,并返回拼接 id 后的 Uri地址 */
Uri tResolver = getContentResolver().insert(url, tValues);
long id = ContentUris.parseId(tResolver); // 返回刚才保存数据的 id

Log.e(TAG, "刚插入数据的 id 为:" + id);
}

showContact("添加操作完成后");

/**
* 删除操作
*/
String tDelWhere = "id=?";
String[] tDelSelectionArgs = new String[] { "1" };

getContentResolver().delete(url, tDelWhere, tDelSelectionArgs); // 删除 id 为 1 的记录

showContact("删除操作完成后");

/**
* 修改操作
*/
ContentValues tUpdateValues = new ContentValues();
tUpdateValues.put("msn", "99999");

String tUpdateWhere = "name=?";
String[] tUpdateSelectionArgs = new String[] { "san1" };

getContentResolver().update(url, tUpdateValues, tUpdateWhere,
tUpdateSelectionArgs);

showContact("修改操作完成后");
}

public void showContact(String manipulate) {

/**
* 查看操作
*/
Cursor tCursor = getContentResolver()
.query(url, null, null, null, null);

while (tCursor.moveToNext()) {

String id = tCursor.getString(tCursor.getColumnIndex("id"));
String name = tCursor.getString(tCursor.getColumnIndex("name"));
String msn = tCursor.getString(tCursor.getColumnIndex("msn"));

Log.e(TAG, manipulate+" id为:" + id + " name为:" + name + " msn为:" + msn);
}
}
}

3.在 AndroidManifest.xml 配置 provider(与 activity 同级)
<provider
android:name="com.xjl.contentprovider.provider.ContactProvider"
android:authorities="xjl.prd.contactprovider" />

注:android:authorities="xjl.prd.contactprovider"

这里 authorities 值是注册的 URI:matcher.addURI("xjl.prd.contactprovider", "contactinfo", CONTACTS); 里的 xjl.prd.contactprovider ,此处必须对应,否则不能操作数据。

此属性在官方文档中的解释:
A list of one or more URI authorities that identify data offered by the content provider. Multiple authorities are listed by separating their names with a semicolon. To avoid conflicts, authority names should use a Java-style naming convention (such ascom.example.provider.cartoonprovider). Typically, it's the name of the ContentProvidersubclass that implements the provider
There is no default. At least one authority must be specified
【在同一项目中运行结果】
@@@MainActivity
刚插入数据的 id 为:1
@@@MainActivity
刚插入数据的 id 为:2
@@@MainActivity
刚插入数据的 id 为:3
@@@MainActivity
======添加操作完成后======
@@@MainActivity
id为:1 name为:san0 msn为:null
@@@MainActivity
id为:2 name为:san1 msn为:null
@@@MainActivity
id为:3 name为:san2 msn为:null
@@@MainActivity
======删除操作完成后======
@@@MainActivity
id为:2 name为:san1 msn为:null
@@@MainActivity
id为:3 name为:san2 msn为:null
@@@MainActivity
======修改操作完成后======
@@@MainActivity
id为:2 name为:san1 msn为:99999
@@@MainActivity
id为:3 name为:san2 msn为:null


4.在其他程序中要访问自定义的 ContentProvider ,需要在共享的 ContentProvider 中设置 android:exported="true"

<provider
android:name="com.xjl.contentprovider.provider.ContactProvider"
android:authorities="com.xjl.contactprovider"
android:exported="true" />

这样其他程序就可以访问自定义的 ContentProvider 了,其中,exported 在 sdk 16 或以下默认值为 true,17或以上默认值为 false;

代码示例在:360云盘:自己的学习资料----Android总结过的项目----ContentProviderExample.rar

原文地址:https://www.cnblogs.com/zx-blog/p/11835723.html