需求:自定义一个简单的ContentProvider类PersonContentProvider,并实现使用其他应用来操作本应用的数据库。
让自己的数据和其它应用程序共享有两种方式:创建自己的Content Provider (即继承自Content Provider的子类) 或者是将自己的数据添加到已有的Content Provider中去,后者需要保证现有的Content Provider和自己的数据类型相同并且具有该 Content Provider的写入的权限。
在这里我们先来了解一下实现和访问ContentProvider的基本思路和要使用到的API,基本思路实际上就是使用URI来访问对应的ContentProvider从而调用它里面的一些操作数据的方法。那么如何通过URI来实现呢,这里又需要引出两个android提供的API:UriMatcher和ContentUris;
一、UriMatcher用来匹配URI,它可以对于不同URI调用int UriMatcher.math()方法返回不同的匹配码来用于判断从而执行对应的操作。使用UriMatcher的基本方法如下:
1、得到UriMatcher的对象:
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);// 这里UriMatcher.NO_MATCH表示如果传入Uri没有任何匹配时的返回码,实际上这个值为-1
2、注册需要处理的Uri,这一步实际上经常放在static{}块中进行,使用addURI方法:
sMatcher.addURI("cn.xxt.provider.personprovider","person",1): //添加需要的Uri,如果匹配上了就会返回匹配码1
上面这一句代表的URI为:content://cn.xxt.provider.personprovider/person,其中content://为schema约束,表示这个Uri代表一个ContentProvider;cn.xxt.provider.personprovider代表主机名,表示的是哪一个ContentProvider控制这些数据;而person表示path路径名,相当于分机号码,表示URI下的某一个Item;后面的10代表ID值,有一些地方需要ID,比如说查询对应ID的数据库条目。sMatcher.addURI("cn.xxt.provider.personprovider","person/#",2): //添加需要的Uri,如果匹配上了就会返回匹配码2,这里的person/#号代表person/后面跟一个任意的数字,比如person/10
上面这一句代表的URI为:content://cn.xxt.provider.personprovider/person/2
3、使用switch分支语句来判断传入的URI是否匹配,这里使用match方法,如果匹配会返回对应的匹配值,如果不匹配则会返回-1(UriMatcher.NO_MATCH)
switch(sMatcher.match(Uri.parse("content://cn.xxt.provider.personprovider/person/2"))){
case 1:
//执行对应操作
break;
case 2:
//执行对应操作
break;
default:
break;
}
二、ContentUris用来获取并添加Uri信息的API,一般在这里就是跟URI的ID有关系了,如果我们需要对一个URI末尾添加一个ID或者从一个已经在末尾带有ID的URI中获取ID值,就会需要用的到ContentUris这个类了.
long id = ContentUris.parseId(uri);//获取uri末尾的id值
Uri uri = ContentUris.withAppendedId(uri, id);//给一个uri的末尾添加一个执行的id值
自定义content provider
在第一个应用里,先自定义一个ContentProvider的实现类PersonContentProvider,我们需要实现onCreate,query,getType,insert,delete,update等方法。
public class PersonContentProvider extends ContentProvider{
@Override
public boolean onCreate() {
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、boolean onCreate()方法:会在应用程序启动时就被调用,一般用来做一些初始化的工作,由于需要对数据库进行操作,在这里我们用来初始化数据库帮助类(SQLiteOpenHelper实现类PersonSQLiteOpenHelper,PersonSQLiteOpenHelper的写法可以参看前面的练习)对象:
private PersonSQLiteOpenHelper openHelper;//成员变量
......
public boolean onCreate() {
openHelper = new PersonSQLiteOpenHelper(getContext());
return false;
}
2、public String getType(Uri uri)方法,这个方法实际上是给系统来使用的,基本原则就是查询操作返回的对象是多条时就返回"vnd.android.cursor.dir/自定名",如果查询操作返回的是单条时就返回"vnd.android.cursor.item/自定名";
3、在写方法之前我们需要做一件事情,那就给得到UriMatcher的对象,和给该对象注册对应的URI地址,这里我们放在Static{}块中来完成:
private static UriMatcher uriMatcher;//成员变量
......
static {
// 在创建时加上需要注册的URI
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);// UriMatcher.NO_MATCH表示不匹配任何类型时返回的匹配码
// 下面这一句代表的URI实际上是:
// insert操作
// content://com.alexchen.sqlite.provider.PersonContentProvider/person/insert
uriMatcher.addURI(PATH, "person/insert",
INSERT_CODE_FOR_PERSONCONTENTPROVIDER);
// delete操作
uriMatcher.addURI(PATH, "person/delete",
DELETE_CODE_FOR_PERSONCONTENTPROVIDER);
// update操作
uriMatcher.addURI(PATH, "person/update",
UPDATE_CODE_FOR_PERSONCONTENTPROVIDER);
// queryAll操作
uriMatcher.addURI(PATH, "person/queryAll",
QUERYALL_CODE_FOR_PERSONCONTENTPROVIDER);
// queryItem操作
uriMatcher.addURI(PATH, "person/queryItem/#",
QUERYITEM_CODE_FOR_PERSONCONTENTPROVIDER);
}
4、public Uri insert(Uri uri, ContentValues values)方法,基本思路就是用uriMatcher.match(uri)来判断是否匹配,如果匹配就执行对应的数据库插入操作,这里最好使用API方式来操作数据库,因为传入的参数大多数跟API方式需要的参数对应,可以直接拿来用,如果不匹配则抛出一个IllegalArgumentException异常。
public Uri insert(Uri uri, ContentValues values) {
switch (uriMatcher.match(uri)) {
case INSERT_CODE_FOR_PERSONCONTENTPROVIDER:
db = openHelper.getWritableDatabase();
if (db.isOpen()) {
// 使用api方式来insert
long id = db.insert("person", null, values);
db.close();
// 使用ContentUris来在uri后面添加一个/id
return ContentUris.withAppendedId(uri, id);
}
break;
default:
throw new IllegalArgumentException("uri不匹配:" + uri);
}
return null;
}
5、public int delete(Uri uri, String selection, String[] selectionArgs),思路跟insert差不多
public int delete(Uri uri, String selection, String[] selectionArgs) {
switch (uriMatcher.match(uri)) {
case DELETE_CODE_FOR_PERSONCONTENTPROVIDER:
db = openHelper.getWritableDatabase();
if (db.isOpen()) {
count = db.delete("person", selection, selectionArgs);
db.close();
return count;
}
break;
default:
throw new IllegalArgumentException("uri不匹配:" + uri);
}
return 0;
}
6、public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) 方法:
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
switch (uriMatcher.match(uri)) {
case UPDATE_CODE_FOR_PERSONCONTENTPROVIDER:
SQLiteDatabase db = openHelper.getWritableDatabase();
if (db.isOpen()) {
count = db.update("person", values, selection, selectionArgs);
db.close();
return count;
}
break;
default:
throw new IllegalArgumentException("uri不匹配:" + uri);
}
return 0;
}
}
7、public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) 方法,这里有些不一样,switch需要判断两种不同的查询方法,,一种是全体查询,一个单体查询,根据_id来查询
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case QUERYALL_CODE_FOR_PERSONCONTENTPROVIDER:
db = openHelper.getReadableDatabase();
if (db.isOpen()) {
System.out.println("queryAll");
cursor = db.query("person", projection, selection,
selectionArgs, null, null, sortOrder);
// 下面这一句不行,因为要返回cursor,一旦关闭了数据库,cursor也没了
// db.close();
return cursor;
}
break;
case QUERYITEM_CODE_FOR_PERSONCONTENTPROVIDER:
db = openHelper.getReadableDatabase();
if (db.isOpen()) {
long id = ContentUris.parseId(uri);
System.out.println("id="+id);
String[] columns = new String[] { "_id", "name", "age" };
selection = "_id=?";
selectionArgs = new String[] { String.valueOf(id) };
cursor = db.query("person", projection, selection, selectionArgs, null,
null, null);
return cursor;
}
break;
default:
throw new IllegalArgumentException("uri不匹配:" + uri);
}
return null;
}
8、至此基本上一个自定义contentprovider就写完了,但是这里还有几个小细节需要注意的:
其一:contentprovider需要在清单文件中进行注册,其二需要对provider的权限进行设置,
这个设置有两种方法,一种是在onCreate方法中调用:
onCreate(){
....
setReadPermission("aa.bb.cc.write");
setWritePermission("aa.bb.cc.read");
}
另一种方式是在清单文件的provider中配置:
android:authorities="com.alexchen.sqlite.provider.PersonContentProvider"
android:readPermission="aa.bb.cc.read"
android:writePermission="aa.bb.cc.write"
android:exported="true">
上面两种方法都需要在清单文件中事先申明两个permission,permission的名字可以任意取,但是在引用的时候需要跟申明时一致:
这里还需要注意一点的就是,在4.0以上版本需要在provider标签中再加上android:exported="true"这一句,android:exported属性非常重要。这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互,否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。如果content provider允许其他应用调用,即允许其他进程调用,需要将该属性设置为true。如果,我们没设置该属性,会报下面的错误:
基本思路就是在第二个应用里,使用ContentResolver类来调用对应uri的contentprovider的方法,我们可以使用getContext.getContentResolver()来获取ContentResolver对象,然后调用resolver.insert(uri,values)方法来调用对一个contentprovider的insert方法,这里values是ContentValues类型,里面维护了一个map来存放数据,其他几种方法大同小异,我们这里写了一个测试类来测试功能,最后要记得在这个应用的清单文件中加入权限申明:其中"aa.bb.cc.read"和"aa.bb.cc.write"要和自定义的contentprovider的申明权限的名字一样
package com.alexchen.othercontentprovider.test;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
public class Test extends AndroidTestCase {
private ContentValues values;
private int _id;
private String name;
private int age;
private Cursor cursor;
public void testInsert() {
Uri uri = Uri
.parse("content://com.alexchen.sqlite.provider.PersonContentProvider/person/insert/");
// 内容提供者访问对象
ContentResolver resolver = getContext().getContentResolver();
values = new ContentValues();
values.put("name", "FENGJIE");
values.put("age", 90);
System.out.println("uri:" + uri);
uri = resolver.insert(uri, values);
long id = ContentUris.parseId(uri);
System.out.println("添加到第" + id + "行");
}
public void testDelete() {
Uri uri = Uri
.parse("content://com.alexchen.sqlite.provider.PersonContentProvider/person/delete/");
ContentResolver resolver = getContext().getContentResolver();
String where = "_id = ?";
String[] selectionArgs = { "1" };
int count = resolver.delete(uri, where, selectionArgs);
System.out.println("删除了" + count + "行");
}
public void testUpdate() {
Uri uri = Uri
.parse("content://com.alexchen.sqlite.provider.PersonContentProvider/person/update/");
ContentResolver resolver = getContext().getContentResolver();
values = new ContentValues();
values.put("name", "LISI");
String where = "_id=?";
String[] selectionArgs = { "2" };
int count = resolver.update(uri, values, where, selectionArgs);
System.out.println("更新了第" + count + "行");
}
public void testqueryAll() {
Uri uri = Uri
.parse("content://com.alexchen.sqlite.provider.PersonContentProvider/person/queryAll/");
ContentResolver resolver = getContext().getContentResolver();
String[] projection = new String[] { "_id", "name", "age" };
String selection = null;
String[] selectionArgs = null;
String sortOrder = "_id desc";
cursor = resolver.query(uri, projection, selection, selectionArgs,
sortOrder);
if (cursor != null && cursor.getCount() > 0) {
while (cursor.moveToNext()) {
_id = cursor.getInt(0);
name = cursor.getString(1);
age = cursor.getInt(2);
System.out.println(_id + ":" + name + ":" + age);
}
cursor.close();
}
}
public void testqueryItem() {
Uri uri = Uri
.parse("content://com.alexchen.sqlite.provider.PersonContentProvider/person/queryItem");
ContentResolver resolver = getContext().getContentResolver();
long id = 2;
// 在uri末尾添加一个id
uri = ContentUris.withAppendedId(uri, id);
System.out.println(uri.toString());
String[] projection = new String[] { "_id", "name", "age" };
cursor = resolver.query(uri, projection, null, null, null);
System.out.println(cursor==null);
if (cursor != null && cursor.moveToFirst()) {
_id = cursor.getInt(0);
name = cursor.getString(1);
age = cursor.getInt(2);
System.out.println(_id + ":" + name + ":" + age);
cursor.close();
}
}
}