牛骨文教育服务平台(让学习变的简单)

Android四大组件之Content Provider

一、概念

Content Provider 作为Android应用程序四大组件之一,为存储和查询数据提供统一的接口,实现程序间数据的共享。Android系统内一些常见的数据如音乐、视频、图像等都内置了一系列的Content Provider。
    

应用程序间共享数据有两种方式:

一是创建子类继承于Content Provider,重写该类用于数据存储和查询的方法。

二是直接使用已经存在的Content Provider,如联系人等。

在Content Provider中数据的是以表的形式存储,在数据表中每一行为一条记录,每一列为类型和数据。每一条记录都包括一个唯一的名叫_ID的数值字段,这个字段唯一标识一条数据记录,在创建表的时候用 INTEGER PRIMARY KEY AUTOINCREMENT来标识此字段。
      

二、相关类介绍

类URI:

每一个Content Provider为其管理的多个数据集,分配一个URI,这个URI对外提供了一个能够唯一标识自己数据集的字符串。这样别的应用程序就可以通过这个URI来访问这个数据集。Android中所有的Content Provider的URI都有固定格式:content://**开头,一般可分为4个部分:

标准前缀:用来标识一个Content Provider,固定Content://

URI标识:定义了是哪个Content Provider提供这些数据。一般是定义该ContentProvider的包类的名字。和AndroidManifest.xml中定义的authorities属性相同。

路径:标识URI下的某一个Item。

记录的ID:如果URI中包含表示某个记录的ID,则返货该id对应的数据。否则表示返回全部。

注意:"content://com.android.people.provider/contacts/#" 这里#表示匹配任意数字"content://com.android.people.provider/contacts/*"表示匹配任意文本

UriMatcher:Uri标识了要操作的数据,而UriMatcher即使Android提供给我们用于操作Uri这个数据的工具类。
常用方法:
        public void addURI(String authority, String path, int code) 往UriMatcher对象里面添加Uri。
        public int match(Uri uri) 与UriMatcher对象的Uri进行匹配,如果成功返回上面传入的code值,否则返回-1.

类ContentUris:类似于UriMatcher,也是一个操作Uri数据的工具类,用于在Uri后面追加一个ID或解析出传入的Uri对象的ID值。
常用方法:
        public static Uri withAppendId(Uri contentUri, long id)  为前面contentUri加上ID部分
        public static long parseId(Uri contentUri) 从contentUri中获取ID部分

类ContentProvider:常用方法:

public abstract boolean onCreate();
public abstract Uri insert(Uri uri, ContentValues values)
public abstract int delete(Uri uri, String selection, String[] selectionArsg);
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder)
public abstract String getType(Uri uri)

这些都是抽象方法需要子类去实现。类 ContentValues:

       android.content.ContentValues 这个用于存储ContentResolver能处理的数据的集合。

构造函数:

       ContentValues()         // 创建一个空的ContentValues对象,初始化默认大小

       ContentValues(int size)

       ContentValues(ContentValues from)

常用方法:        

void clear()
boolean containKey(String key)
Object get(String key)
void put(String key, Type value)     // Type: Byte Integer Float Short byte[] String Double Long Boolean
int size()
Type getAsTypeArray(String key)	     // Type: Object Boolean Byte byte[] Double Float Integer Long Short String

类android.content.ContentResolver:

一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据以类似数据库中表的方式完全暴露出去,那么外界其他应用程序怎么从数据库中获取数据呢,这就需要ContentResolver了,通过URI表示外界访问需要的数据库。           这个类为我们定义了一系列的方法包括:插入、删除、修改、查询等,与ContentProvider基本类似,主要根据传入的参数Uri找到对应的Content Provider,调用其相应的方法。

构造函数:

        public ContentResolver(Context context)

一般在代码中我们直接通过: MainActivity.this.getContentResolver()获得当前应用程序的一个ContentResolver实例。      

这里我们需要考虑一个问题,就是如果多个程序同时通过ContentResolver共享访问一个ContentProvider,会不会不同步,尤其是数据写入的时候这就需要在AndroidManifest.xml中定义ContentProvider的时候加上:元素的multiprocess属性。同时Android在ContentResolver中为我们提供了

notifyChange()接口,在数据发生改变时通知其他的ContentObserver。

final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
final void unregisterContentObserver(ContentObserver observer)
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)
void notifyChange(Uri uri, ContentObserver observer)

三、使用已经存在的ContentProvider

使用已经存在的ContentProvider包括两种情况:一是从已经存在的ContentProvider里面读取数据,如获得
手机里面联系人的信息。二是将自己的数据加入到ContentProvider里面,让其他程序能共享次数据,这就需要获得对这个
ContentProvider的写权限了。

Android中电话簿就是通过ContentProvider实现数据共享的,系统中有很多已经存在的共享URI,我们可以使用ContentResolver通过Uri来操作不同的标的数据。如Contacts.People.CONTENT_URI

在Android中为我们提供了两种方法来查询Content Provider:一是使用ContentResolver的query()方法,二是使用Activity对象的manageQuery()方法,他们的参数都相同,而且都返回Cursor对象。但是使用manageQuery()方法返回的Cursor对象的生命周期自动被Activity来管理,被管理的Cursor对象在Activity进入暂停状态的时候调用自己的deactivate()方法卸载,在Activity回到运行状态的时候调用自己的requery()方法重新查询生成的Cursor。而使用ContentResolver的query()方法返回的Cursor对象需要手动加入Activity来管理,这是通过Activity的startManagingCursor()方法来实现的。

四、创建自己的ContentProvider

(1) 创建一个继承于ContentProvider的类MyContentProvider
(2) 定义一个public static final Uri 类型变量CONTENT_URI
如:public static final Uri CONTENT_URI = Uri.parse("content://com.android.MyContentProvider")
(3) 定义需要返回给客户端的数据列名,如果使用到数据库SQLite,必须定义一个_id,表示记录的唯一性。
(4) 创建数据存储系统,如文件系统或数据库SQLite系统。
(5) 如果存储字节型数据,如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,用来读取对应的实际文件数据。 处理这种数据类型的Content Provider需要实现一个名为_data的字段,该字段列出了该文件在Android系统的实际路 径,客户端可以通过调用方法ContentResolver.

openOutputStream()来处理该URI指向的文件资源。
(6) 查询返回一个Cursor类型对象,所有执行写操作的方法如insert()、update()及delete()都将被监听,可以通过Content
Resolver().notifyChange()来通过监听器关于数据更新的消息。
(7) 在AndroidManifest.xml中使用标签来设置Content Provider信息,如:android:authorities、android:name
android:permission等。

Demo:
数据库类DatabaseHelper用来存储个人信息,名字(name)对应年龄(age)

public class DatabaseHelper extends SQLiteOpenHelper {
	private static final String DB_NAME = "personInfo.db";
	private static final String TB_NAME = "person"; 
	private static final int VERSION = 1;
	private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)";
	private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)";
	
	public DatabaseHelper(Context context) {
		super(context, DB_NAME, null, VERSION);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		// TODO Auto-generated method stub
		db.execSQL(creat_cmd);
	}
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// TODO Auto-generated method stub
		db.execSQL(upgrade_cmd);
	}
}

创建自己的ContentProvider

// 创建一个MyProvider继承于ContentProvider
public class MyProvider extends ContentProvider {
	private DatabaseHelper dbHelper;	// 数据库类
	private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
	private static final int ALL_PERSON = 1;
	private static final int PERSON = 2;
	private static final String TAG = "MyProvider";
	private static final String TABLE_NAME = "person";
	// 定义自己的URI
	private static final String AUTHORITY = "com.myAndroid.myProvider";
	public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
	
	static {
		URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON);
		URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON);
	}
	
	// ContentProvider的入口,初始化
	@Override
	public boolean onCreate() {
		// TODO Auto-generated method stub
		Log.i(TAG, "----onCreate----");
		dbHelper = new DatabaseHelper(this.getContext());
		return false;
	}
	
	@Override
	public int delete(Uri arg0, String arg1, String[] arg2) {
		// TODO Auto-generated method stub
		Log.i(TAG, "----delete----");
		SQLiteDatabase db = dbHelper.getWritableDatabase();
		int count = 0;
		switch (URI_MATCHER.match(arg0)) {
		case ALL_PERSON:
			count = db.delete(TABLE_NAME, arg1, arg2);
		case PERSON:
			long id = ContentUris.parseId(arg0);
			String where = "_id=" + id;
			if(arg1 != null && !"".equals(arg1)) {
				where += " and " + arg1;
			}
			count = db.delete(TABLE_NAME, where, arg2);
		default:
			throw new IllegalArgumentException("Unknown Uri:" + arg0.toString());
		}
		getContext().getContentResolver().notifyChange(arg0, null);	// 通知注册客户端数据发生改变
		return count;
	}

	@Override
	public String getType(Uri arg0) {
		// TODO Auto-generated method stub
		Log.i(TAG, "----getType----");
		switch (URI_MATCHER.match(arg0)) {
		case ALL_PERSON:
				return "com.android.cursor.dir/person";
		case PERSON:
				return "com.android.cursor.item/person";
		default:
				throw new IllegalArgumentException("Unknow Uri" + arg0.toString());
		}
	}

	@Override
	public Uri insert(Uri uri, ContentValues arg1) {
		// TODO Auto-generated method stub
		Log.i(TAG, "----insert----");
		SQLiteDatabase db = dbHelper.getWritableDatabase();
		Uri insertUri = null;
		switch (URI_MATCHER.match(uri)) {
		case ALL_PERSON:
			long rowId = db.insert(TABLE_NAME, "name", arg1);
			Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId);
			insertUri = ContentUris.withAppendedId(uri, rowId);
//			this.getContext().getContentResolver().notifyChange(insertUri, null);
			break;
		default:
			throw new IllegalArgumentException("Unknown Uri:" + uri.toString());
		}
		getContext().getContentResolver().notifyChange(insertUri, null);
		return insertUri;
	}

	// 处理查询,返回Cursor
	@Override
	public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
			String arg4) {
		// TODO Auto-generated method stub
		Log.i(TAG, "----query----");
		SQLiteDatabase db = dbHelper.getReadableDatabase();
		Cursir cursor = null;
		switch (URI_MATCHER.match(arg0)) {
		case ALL_PERSON:					// query all the person info
			cursor =  db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4);
		case PERSON:						// query only one person info from a given ID
			long id = ContentUris.parseId(arg0);
			String where = " _id=" + id;
			if( (!"".equals(arg2)) && (arg2 != null)) {
				where += " and " + arg2 ;
			}
			cursor =  db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4);
		default:
			throw new IllegalArgumentException("unknow uri" + arg0.toString());
		}
		if(cursor !=  null) {
		// 注册该Uri对应的数据发生改变时,向客户端发送通知
			cursor.setNotificationUri(getContext().getContentResolver(), uri);			
		}
		return cursor;
	}

	@Override
	public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
		// TODO Auto-generated method stub
		Log.i(TAG, "----update----");
		SQLiteDatabase db = dbHelper.getWritableDatabase();
		int count = 0;
		switch (URI_MATCHER.match(arg0)) {
		case ALL_PERSON:
			count = db.update(TABLE_NAME, arg1, arg2, arg3);
			break;
		case PERSON:
			long id = ContentUris.parseId(arg0);
			String where = "_id=" + id;
			if( (arg2 != null) && (!"".equals(arg2))) {
				where += " and " + arg2;
			}
			count = db.update(TABLE_NAME, arg1, where, arg3);
			break;
		default:
			throw new IllegalArgumentException("Unknow Uri:"+arg0.toString());
		}
		getContext().getContentResolver().notifyChange(arg0, null);
		return count;
	}
}

客户端使用Content Provider:

// 定义自己的URI
private static final String TABLE_NAME = "person";
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);

// 插入
ContentResolver contenResolver = MainActivity.this.getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "hello");
values.put("age", 25);
Uri resultUri = contentResolver.insert(CONTENT_URI, values);
if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); }

// 查询
String columns[] = new String[] ("_id", "name", "age");
Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id");
if(cursor.moveToFirst()) {
	do {
		Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id")));
		Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name")));
		Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age")));
	} while(cursor.moveToNext());
	cursor.close();
}

// 删除 ID为1的记录
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null);

// 修改ID为 1 的记录
ContentValues values = new ContentValues();
values.put("name", "world");
values.put("age",  32);
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null);

最后我们还需要在AndroidManifest.xml中定义我们的provider属性:

<provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" />

使用游标:

这里我们关注下在query()方法中,我们查询数据库的时候使用到了一个Cursor对象,这个Cursor对象就是游标,它包含0个或多个记录。列名称、顺序和类型都是特定于ContentProvider的,但是返回的每行都包涵啊一个名为_id的默认咧,表示该行的唯一ID。

 1、游标是一个行集合     

 2、读取数据之前,需要使用moveToFirst()将游标移动到第一行之前

 3、需要知道列名称和列类型

 4、所有字段的访问都是基于列编号,所有必须首先将该列名称转换为列编号

 5,、游标可以随意移动(往前、往后,跳过一段距离等)

主要方法:

	boolean moveToFirst()
	boolean isBeforeFirst()
	boolean isAfterLast()
	boolean isClosed()

使用Where查询:

上面代码中使用的manageQuery()的签名为:

public final Cursor manageQuery(Uri uri, String[] projectin,  String selection, String[] selectionArgs, String sortOrder);

参数:
Uri: 给定的URI
projection: 声明要返回的行属性,传递null 返回给定URI的所有行
selection 表示过滤器,以SQL Where字句(不含Where本身)的格式声明,可以使用?,将被替换为selectionArgs中的值,按照在列表中出现的顺序显示。
selectionArgs: 过滤器残数
sortOrder: 排序方式

如:查询id为23的笔记:

	manageQuery("Content://com.google.provider.NotePad/notes/23",
		null, null, null, null);
	manageQuery("Content://com.google.provider.Notepad/notes",
		null, "_id=?", new String[] {23}, null);