内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是 Android实现跨程序共享数据的标准方式。内容提供器的用法一般有两种, 一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。

 

一、访问其他程序中的数据

    Android自带的电话薄、短信、媒体库等程序提供了外部访问接口,第三方程序可以利用这些数据进行开发

    1、ContentResolver的基本用法

    如果想要访问内容提供器的数据需要使用到ContentResolver类,可以通过Context中的getContentResolver()方法获取到该类的实例。ContentResolver提供了很多方法用于对数据进行CRUD操作,分别是insert()\update()\delete()\query(),与SQLiteDatabase类似,只不过参数不同。ContentResolver中的CRUD方法都不接受表名,而是使用uri参数代替,这个参数被称为内容URI。内容 URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成,权限(authority)和路径(path) 。权限是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是 com.example.app,那么该程序对应的权限就可以命名为 com.example.app.provider。路径则是用于对同一应用程序中不同的表做区分的,通常都会添加到权限的后面。比如某个程序的数据库里存在两张表,table1和 table2,这时就可以将路径分别命名为/table1和/table2,然后把权限和路径进行组合,内容 URI就变成了 com.example.app.provider/table1和com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此,内容 URI最标准的格式写法如下:

content://com.example.app.provider/table1
content://com.example.app.provider/table2

    

    在得到了内容 URI字符串之后,我们还需要将它解析成 Uri对象才可以作为参数传入。解析的方法也相当简单,代码如下所示:

Uri uri = Uri.parse("content://com.example.app.provider/table1")

    现在我们就可以使用这个 Uri对象来查询 table1表中的数据了,代码如下所示:

Cursor cursor = getContentResolver().query(
        uri,
        projection,
        selection,
        selectionArgs,
        sortOrder);

    

    查询完成后返回的仍然是一个 Cursor对象。

    添加数据操作:

ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);

    更新数据操作:

ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new
String[] {"text", "1"});

    删除数据操作:

getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

 

二、创建自己的内容提供器

    1、创建内容提供器的步骤

    可以通过新建一个类去继承 ContentProvider的方式来创建一个自己的内容提供器。ContentProvider类中有六个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部重写。新建 MyProvider继承自 ContentProvider,代码如下所示:

package ga.orlion.contactdemo;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

public class MyProvider extends ContentProvider {

	@Override
	public boolean onCreate() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String getType(Uri uri) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		// TODO Auto-generated method stub
		return 0;
	}

}
  1. onCreate()
    初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化
  2. query()
    从内容提供器中查询数据。使用 uri参数来确定查询哪张表,projection参数用于确定查询哪些列,selection和 selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,查询的结果存放在 Cursor对象中返回。
  3. insert()
    向内容提供器中添加一条数据。使用 uri参数来确定要添加到的表,待添加的数据保存在 values参数中。添加完成后,返回一个用于表示这条新记录的 URI。
  4. update()
    更新内容提供器中已有的数据。使用 uri参数来确定更新哪一张表中的数据,新数据保存在 values参数中,selection和 selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回。
  5. delete()
    从内容提供器中删除数据。使用 uri参数来确定删除哪一张表中的数据,selection和 selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回
  6. getType()
    根据传入的内容 URI来返回相应的 MIME类型。

 

    几乎每一个方法都会带有Uri这个参数, 这个参数也正是调用ContentResolver的增删改查方法时传递过来的。而现在,我们需要对传入的 Uri参数进行解析,从中分析出调用方期望访问的表和数据。

一个标准的内容 URI写法是这样的:content://com.example.app.provider/table1。这就表示调用方期望访问的是 com.example.app这个应用的 table1表中的数据。除此之外,我们还可以在这个内容 URI的后面加上一个 id,如下所示:

content://com.example.app.provider/table1/1。这就表示调用方期望访问的是 com.example.app这个应用的 table1表中 id为 1的数据。

    内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id结尾就表示期望访问该表中拥有相应 id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容 URI,规则如下:

  1. *:表示匹配任意长度的任意字符
  2. #:表示匹配任意长度的数字

 

    所以,一个能够匹配任意表的内容 URI格式就可以写成:content://com.example.app.provider/*。而一个能够匹配 table1表中任意一行数据的内容 URI格式就可以写成:content://com.example.app.provider/table1/#。

    接着, 我们再借助UriMatcher这个类就可以轻松地实现匹配内容URI的功能。 UriMatcher中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义代码传进去。这样,当调用 UriMatcher的 match()方法时,就可以将一个 Uri对象传入,返回值是某个能够匹配这个 Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改 MyProvider中的代码,如下所示:

package com.example.contactdemo;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;

public class MyProvider extends ContentProvider {

	public static final int TABLE1_DIR = 0;
	
	public static final int TABLE1_ITEM = 1;
	
	public static final int TABLE2_DIR = 2;
	
	public static final int TABLE2_ITEM = 3;
	
	private static UriMatcher uriMatcher;
	
	static {
		uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
		uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
		uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
		uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		
		switch (uriMatcher.match(uri)) {
		
		case TABLE1_DIR:
			// 查询table1表中所有数据
			break;
		case TABLE1_ITEM:
			// 查询table2表中单条数据
			break;
		case TABLE2_DIR:
			// 查询table2表中所有数据
			break;
		case TABLE2_ITEM:
			//  查询table2表中单条数据
			break;
		default:
			break;
		}
		....
	}

	...

}

 

    可以看到,MyProvider中新增了四个整型常量,其中 TABLE1_DIR表示访问 table1表中的所有数据,TABLE1_ITEM 表示访问 table1表中的单条数据,TABLE2_DIR 表示访问table2表中的所有数据,TABLE2_ITEM表示访问 table2表中的单条数据。接着在静态代码块里我们创建了 UriMatcher的实例,并调用 addURI()方法,将期望匹配的内容 URI格式传递进去,注意这里传入的路径参数是可以使用通配符的。然后当 query()方法被调用的时候,就会通过 UriMatcher的 match()方法对传入的 Uri对象进行匹配,如果发现 UriMatcher中某个内容 URI格式成功匹配了该 Uri对象,则会返回相应的自定义代码,然后我们就可以判断出调用方期望访问的到底是什么数据了。上述代码只是以 query()方法为例做了个示范,其实 insert()、update()、delete()这几个方

法的实现也是差不多的,它们都会携带 Uri这个参数,然后同样利用 UriMatcher的 match()方法判断出调用方期望访问的是哪张表,再对该表中的数据进行相应的操作就可以了。

    getType()方法是所有的内容提供器都必须提供的一个方法,用于获取 Uri对象所对应的 MIME类型。一个内容 URI所对应的 MIME字符串主要由三部分组分,Android对这三个部分做了如下格式规定:

  1. 必须以 vnd开头。
  2. 如果内容 URI以路径结尾,则后接 android.cursor.dir/,如果内容 URI以 id结尾,则后接 android.cursor.item/。
  3. 最后接上 vnd.<authority>.<path>。

 

    所以,对于 content://com.example.app.provider/table1这个内容 URI,它所对应的 MIME类型就可以写成:vnd.android.cursor.dir/vnd.com.example.app.provider.table1

    现在我们可以继续完善 MyProvider中的内容了,这次来实现 getType()方法中的逻辑,代码如下所示:

@Override
	public String getType(Uri uri) {
		switch (uriMatcher.match(uri)) {
		case TABLE1_DIR:
			return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
		case TABLE1_ITEM:
			return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
		case TABLE2_DIR:
			return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
		case TABLE2_ITEM:
			return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
		default:
			break;
		}
		return null;
	}