ContentProvider(数据提供者)是在应用程序间共享数据的一种接口机制 ContentProvider提供了更为高级的数据共享方法,应用程序可以指定需要共享的数据,而其他应用程序则可以在不知数据来源、路径的情况下,对共享数据进行查询、添加、删除和更新等操作 许多Android系统的内置数据也通过ContentProvider提供给用户使用,例如通讯录、音视频文件和图像文件等 在创建ContentProvider时,需要首先使用数据库、文件系统或网络实现底层存储功能,然后在继承ContentProvider的类中实现基本数据操作的接口函数,包括添加、删除、查找和更新等功能 调用者不能够直接调用ContentProvider的接口函数,而需要使用ContentResolver对象,通过URI间接调用ContentProvider。
使用ContentProvider可以在不同的应用程序之间共享数据。 它为存储和获取数据提供了统一的接口。ContentProvide对数据进行封装,不用关心数据存储的细节。
ContentProvider不管底层数据的实际存储方式,对外统一使用表的形式来组织数据 。
URI的简介
Uri代表了要操作的数据,它为系统的每一个资源给其一个名字,比方说通话记录。每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。
URI的格式
A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://" B:URI 的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。这个标识在元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称; "content://hx.android.text.myprovider" C:路径,通俗的讲就是你要操作的数据库中表的名字;"content://hx.android.text.myprovider/tablename" D:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部; "content://hx.android.text.myprovider/tablename/#" #表示数据id
路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下: • 要操作contact表中id为10的记录,可以构建这样的路径:/contact/10 • 要操作contact表中id为10的记录的name字段, contact/10/name • 要操作contact表中的所有记录,可以构建这样的路径:/contact 要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下: 要操作xml文件中contact节点下的name节点,可以构建这样的路径:/contact/name 如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下: Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")
UriMatcher
因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供UriMatcher ,用于匹配Uri,用法如下: 1.首先把你需要匹配Uri路径全部给注册上,如: uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1); uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2); 2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码. //如果match()匹配路径,返回匹配码为1 content://com.changcheng.sqlite.provider.contactprovider/contact //如果match()匹配路径,返回匹配码为2 content://com.changcheng.sqlite.provider.contactprovider/contact/23
因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。 UriMatcher类用于匹配Uri,它的用法如下: 首先第一步把你需要匹配Uri路径全部给注册上,如下: //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码 UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); //如果match()方法匹配content://cn.itcast.provider.personprovider/person路径,返回匹配码为1 sMatcher.addURI(“cn.itcast.provider.personprovider”, “person”, 1);//添加需要匹配uri,如果匹配就会返回匹配码 //如果match()方法匹配content://cn.itcast.provider.personprovider/person/230路径,返回匹配码为2 sMatcher.addURI(“cn.itcast.provider.personprovider”, “person/#”, 2);//#号为通配符 switch (sMatcher.match(Uri.parse("content://cn.itcast.provider.personprovider/person/10"))) { case 1 break; case 2 break; default://不匹配 break; } 注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://cn.itcast.provider.personprovider/person路径,返回的匹配码为1
ContentUris类
ContentUris类用于获取Uri路径后面的ID部分,它有两个比较实用的方法: withAppendedId(uri, id)用于为路径加上ID部分: Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person") Uri resultUri = ContentUris.withAppendedId(uri, 10); //生成后的Uri为:content://cn.itcast.provider.personprovider/person/10
parseId(uri)方法用于从路径中获取ID部分: Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person/10") long personid = ContentUris.parseId(uri);//获取的结果为:10
ContentProvider的编程方法
程序开发人员通过继承ContentProvider类可以创建一个新的数据提供者,过程可以分为三步 1.继承ContentProvider,并重载六个函数 public boolean onCreate() 该方法在ContentProvider创建后就会被调用, Android开机后, ContentProvider在其它应用第一次访问它时才会被创建。 public Uri insert(Uri uri, ContentValues values) 该方法用于供外部应用往ContentProvider添加数据。 public int delete(Uri uri, String selection, String[] selectionArgs) 该方法用于供外部应用从ContentProvider删除数据。 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 该方法用于供外部应用更新ContentProvider中的数据。 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于供外部应用从ContentProvider中获取数据。 public String getType(Uri uri) 该方法用于返回当前Url所代表数据的MIME类型。如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,例如:要得到所有person记录的Uri为content://cn.itcast.provider.personprovider/person,那么返回的MIME类型字符串应该为:“vnd.android.cursor.dir/person”。如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,例如:得到id为10的person记录,Uri为content://cn.itcast.provider.personprovider/person/10,那么返回的MIME类型字符串应该为:“vnd.android.cursor.item/person”。
2.声明CONTENT_URI,实现UriMatcher 3.注册ContentProvider AndroidManifest.xml使用<provider>对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把 ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
<application android:icon="@drawable/icon" android:label="@string/app_name"> <provider android:name = ".PeopleProvider" android:authorities = "edu.hrbeu.peopleprovider"/> </application>
注意:一旦应用继承了ContentProvider类,后面我们就会把这个应用称为ContentProvider(内容提供者)。
ContentResolver的编程方法
使用ContentProvider是通过Android组件都具有的ContentResolver对象,通过URI进行数据操作 程序开发人员只需要知道URI和数据集的数据格式,则可以进行数据操作,解决不同应用程序之间的数据共享问题 每个Android组件都具有一个ContentResolver对象,获取ContentResolver对象的方法是调用getContentResolver()函数
使用ContentResolver操作ContentProvider中的数据
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver 类提供了与ContentProvider类相同签名的四个方法: public Uri insert(Uri uri, ContentValues values) 该方法用于往ContentProvider添加数据。 public int delete(Uri uri, String selection, String[] selectionArgs) 该方法用于从ContentProvider删除数据。 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 该方法用于更新ContentProvider中的数据。 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于从ContentProvider中获取数据。
这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,假设给定的是: Uri.parse(“content://cn.itcast.providers.personprovider/person/10”),那么将会对主机名为cn.itcast.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。
使用ContentResolver对ContentProvider中的数据进行添加、删除、修改和查询操作: ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person"); //添加一条记录 ContentValues values = new ContentValues(); values.put("name", "itcast"); values.put("age", 25); resolver.insert(uri, values); //获取person表中所有记录 Cursor cursor = resolver.query(uri, null, null, null, "personid desc"); while(cursor.moveToNext()){ Log.i("ContentTest", "personid="+ cursor.getInt(0)+ ",name="+ cursor.getString(1)); } //把id为1的记录的name字段值更改新为liming ContentValues updateValues = new ContentValues(); updateValues.put("name", "liming"); Uri updateIdUri = ContentUris.withAppendedId(uri, 2); resolver.update(updateIdUri, updateValues, null, null); //删除id为2的记录 Uri deleteIdUri = ContentUris.withAppendedId(uri, 2); resolver.delete(deleteIdUri, null, null);
当ContentProvider中的数据发生变化时可以向其用户发出通知
如果ContentProvider的访问者需要得知ContentProvider中的数据发生了变化,可以在ContentProvider 发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下: public class PersonContentProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("person", "personid", values); getContext().getContentResolver().notifyChange(uri, null); } } 如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法: getContentResolver().registerContentObserver(Uri.parse("content://cn.itcast.providers.personprovider/person"), true, new PersonObserver(new Handler())); public class PersonObserver extends ContentObserver{ public PersonObserver(Handler handler) { super(handler); } public void onChange(boolean selfChange) { //此处可以进行相应的业务处理 } }
具体一个例子代码:
View Code
import cn.itcast.service.DBOpenHelper;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class PersonProvider extends ContentProvider {
private DBOpenHelper dbOpenHelper;
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int PERSONS = 1;
private static final int PERSON = 2;
static{
//如果match()方法匹配content://cn.itcast.provider.personprovider/person路径,返回匹配码为1
MATCHER.addURI("cn.itcast.providers.personprovider", "person", PERSONS);
//如果match()方法匹配content://cn.itcast.provider.personprovider/person/230路径,返回匹配码为2
MATCHER.addURI("cn.itcast.providers.personprovider", "person/#", PERSON);
}
//删除person表中的所有记录 /person
//删除person表中指定id的记录 /person/10
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
int count = 0;
switch (MATCHER.match(uri)) {
case PERSONS:
count = db.delete("person", selection, selectionArgs);
return count;
case PERSON:
long id = ContentUris.parseId(uri);
String where = "personid="+ id;
if(selection!=null && !"".equals(selection)){
where = selection + " and " + where;
}
count = db.delete("person", where, selectionArgs);
return count;
default:
throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
}
}
@Override
public String getType(Uri uri) {//返回当前操作的数据的mimeType
switch (MATCHER.match(uri)) {
case PERSONS: //多条数据
return "vnd.android.cursor.dir/person";
case PERSON: //单条数据
return "vnd.android.cursor.item/person";
default:
throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {// /person
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
switch (MATCHER.match(uri)) {
case PERSONS:
long rowid = db.insert("person", "name", values);
//生成后的Uri为:content://cn.itcast.provider.personprovider/person/10
Uri insertUri = ContentUris.withAppendedId(uri, rowid);//得到代表新增记录的Uri
//当ContentProvider中的数据发生变化时可以向其用户发出通知,第一个参数为uri,说明是person表的uri,不是单条记录的uri
this.getContext().getContentResolver().notifyChange(uri, null);
return insertUri;
default://不匹配
throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
}
}
@Override
public boolean onCreate() {
this.dbOpenHelper = new DBOpenHelper(this.getContext());
return false;
}
//查询person表中的所有记录 /person
//查询person表中指定id的记录 /person/10
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (MATCHER.match(uri)) {
case PERSONS:
return db.query("person", projection, selection, selectionArgs, null, null, sortOrder);
case PERSON:
long id = ContentUris.parseId(uri);
String where = "personid="+ id;
if(selection!=null && !"".equals(selection)){
where = selection + " and " + where;
}
return db.query("person", projection, where, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
}
}
//更新person表中的所有记录 /person
//更新person表中指定id的记录 /person/10
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
int count = 0;
switch (MATCHER.match(uri)) {
case PERSONS:
count = db.update("person", values, selection, selectionArgs);
return count;
case PERSON:
//parseId(uri)方法用于从路径中获取ID部分:获取的结果为:10
long id = ContentUris.parseId(uri);
String where = "personid="+ id;
//如果外面传进来的条件不为空,而且不为空字符串
if(selection!=null && !"".equals(selection)){
//外面的条件加自己的条件
where = selection + " and " + where;
}
count = db.update("person", values, where, selectionArgs);
return count;
default:
throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
}
}
}
AndroidMainFest.xml代码
View Code
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.itcast.db"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<uses-library android:name="android.test.runner" />
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:name=".PersonProvider" android:authorities="cn.itcast.providers.personprovider"/>
</application>
<uses-sdk android:minSdkVersion="8" />
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="cn.itcast.db" android:label="Tests for My App" />
</manifest>
另一个应用程序调用ContentProvider
View Code
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.util.Log;
public class AccessContentProviderTest extends AndroidTestCase {
private static final String TAG = "AccessContentProviderTest";
/**
* 往内容提供者添加数据
* @throws Throwable
*/
public void testInsert() throws Throwable{
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri insertUri = Uri.parse("content://cn.itcast.providers.personprovider/person");
ContentValues values = new ContentValues();
values.put("name", "zhangxiaoxiao");
values.put("amount", 90);
Uri uri = contentResolver.insert(insertUri, values);
Log.i(TAG, uri.toString());
}
/**
* 更新内容提供者中的数据
* @throws Throwable
*/
public void testUpdate() throws Throwable{
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri updateUri = Uri.parse("content://cn.itcast.providers.personprovider/person/1");
ContentValues values = new ContentValues();
values.put("name", "lili");
contentResolver.update(updateUri, values, null, null);
}
/**
* 从内容提供者中删除数据
* @throws Throwable
*/
public void testDelete() throws Throwable{
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri deleteUri = Uri.parse("content://cn.itcast.providers.personprovider/person/1");
contentResolver.delete(deleteUri, null, null);
}
/**
* 获取内容提供者中的数据
* @throws Throwable
*/
public void testFind() throws Throwable{
ContentResolver contentResolver = this.getContext().getContentResolver();
Uri selectUri = Uri.parse("content://cn.itcast.providers.personprovider/person");
Cursor cursor = contentResolver.query(selectUri, null, null, null, "personid desc");
while(cursor.moveToNext()){
int id = cursor.getInt(cursor.getColumnIndex("personid"));
String name = cursor.getString(cursor.getColumnIndex("name"));
int amount = cursor.getInt(cursor.getColumnIndex("amount"));
Log.i(TAG, "id="+ id + ",name="+ name+ ",amount="+ amount);
}
}
}