文章的开头奉送上代码方便大家对照学习。
1 ContentProvider简介
ContentProvider是android4大组件之一,它的出现是为了在二个应用之间共享数据。
在Android中,对数据的保护是很严密的,除了放在SD卡中的数据,一个应用所持有的数据库、文件、等等内容,都是不允许其他直接访问的。但有时候沟通是必要的,不仅对第三方很重要,对应用自己也很重要。比如,一个联系人管理的应用。如果不允许第三方的应用对其联系人数据库进行增删该查,整个应用就失去了可扩展力,必将被其他应用抛弃,然后另立门户,自个玩自个的去了。Andorid当然不会真的把每个应用都做成一座孤岛,它为所有应用都准备了一扇窗,这就是Content Provider。
Content Provider屏蔽了内部数据的存储细节,向外提供了上述统一的接口模型。这样大大简化了我们代码的书写。
在使用ContentProvider时我们需要用到以下工具,这里先做简单介绍,下面会仔细讲解:
类 | 功能 |
---|---|
Uri | 外部应用唯一确定要访问的ContentProvider |
ContentResolver | 外部应用用来获取ContentProvider对象,进行增删改查操作 |
UriMatcher | |
ContentUris | |
ContentProvider | 给外部应用提供接口,用统一的方式获取共享数据 |
2 ContentProvider相关类的介绍
这些类大家看不懂也不要紧,大体看一下心里有个印象就行。我们在contentProvider实例讲解用到的时候直接到这里面查阅就行。
2.1 Uri介绍
外部应用通过uri来唯一标识一个ContentProvider。
举个例子:
由图中可以看出uri分3部分:
1.scheme:content://
固定写法,表示这个uri指定一个contentprovider(即我们要访问的是一个contentprovider)。
2.主机名(或叫Authority)用于唯一标识一个contentyprovider。注意这个主机名必须和menifest中contentProvider的authorities属性一样,不然找不到我们需要的contentyprovider。在本例子中主机名是:com.ljq.provider.personprovider
3.路径用来表示我们要操作的数据,路径的构建应根据业务而定。例如:
/person/10:要操作person表中id为10的记录
person/10/name:要操作person表中id为10的记录的name字段,
/person:要操作person表中的所有记录
/xxx:要操作xxx表中的记录
如果你想创建自己的contentprovider,最好把自定义的URI设置为常量,这样简化别人的调用,并且以后如果更新URI也很容易。android定义了CONTENT_URI常量用于URI,比如:
public static final Uri CONTENT_URI =
Uri.parse(“content://contacts/people”);
可能大家看到这里还是不知道uri怎么用,我学习的时候也有这个疑问,不要着急,uri要结合UriMatcher和ContentUris一块使用的,下面会讲到。
2.2 ContentResolver
在外部应用的activity中通过getContentResolver()方法来获取对象,然后进行增删改查操作,步骤如下:
1.获取ContentResolver。
getContentResolver()
2.利用ContentResolver增删改查:
增:getContentResolver().insert(Uri url, ContentValues values);
删:getContentResolver().delete(Uri url, String where, String[] selectionArgs)
改:getContentResolver().update(Uri uri, ContentValues values, String where, String[] selectionArgs)
查:getContentResolver().query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
在这4个方法中,每一个方法都需要一个uri,因为这个uri唯一标识一个contentyprovider(内部是怎么实现的我们下面会讲到,这里不做介绍)。其他的参数就和sqlite操作的参数大同小异,这里不做介绍。
2.3 UriMatcher
UriMatcher用来匹配uri的作用。UriMatcher中只有3个方法(包括构造方法):
1.UriMatcher(int code)方法
构造方法,固定写法:new UriMatcher(UriMatcher.NO_MATCH)
2.addURI(String authority, String path, int code)方法
添加匹配规则
参数 | 介绍 |
---|---|
authority | 主机名 |
path | 路径 |
code | 匹配码,当匹配成功后,就会返回这个码 |
这里对path讲一下,因为它比较灵活,”*”表于匹配所有的字符串,”#” 表示匹配所有数字,如:
uri的路径是: “/people”
sURIMatcher.addURI("contacts", "people", PEOPLE);
uri的路径是: “/people/10”
sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
uri的路径是: “/people/10/phones”
sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
uri的路径是: “/people/10/phones/3”
sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
3.match(Uri uri)方法
根据addURI()方法添加的匹配规则来匹配,如果匹配到了就会返回响应的匹配码。如:
int matchId= sURIMatcher.match(uri);
switch(matchId){
case 1://表示访问的student表
break;
case 2://表示访问的是teacher表
break;
}
2.4 ContentProvider
终于讲到我们的重点了,使用步骤如下:
1.继承ContentProvider重写它的方法:
onCreate()
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
getType(Uri uri)
insert(Uri uri, ContentValues values)
delete(Uri uri, String selection, String[] selectionArgs)
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
在ContentProvider中,我们要给UriMatcher添加匹配规则,做匹配操作。
2.在menifenst中注册:
<provider
android:name=".contentprovider.MyContentProvider"
android:authorities="com.czh.mycontentprovider.study" />
注意android:authorities是主机名,唯一标识这个ContentProvider,外部应用也是通过这个来查找。
2.5 补充ContentUris
ContentUris是一个辅助工具类,里面全是静态方法,我们看一下:
方法 | 介绍 |
---|---|
appendId(Uri.Builder builder, long id) | 在路径后添加id |
parseId(Uri contentUri) | 将路径的最后一个参数转化成long数据,一般用来获取id |
withAppendedId(Uri contentUri, long id) | 在uri后添加id |
3 ContentProvider实例
呼!终于讲到实例了。接下来我们自定义ContentProvider来访问数据库。
第一步重写contentProvider:
public class MyContentProvider extends ContentProvider {
private final static int MyStudyTableFlag = 1;//查询表的标记,建议大家都弄成常量
private final static int MyStudyTableNumFlag = 2;//查询表的标记,建议大家都弄成常量
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{//静态语句块,当第一次加载的时候调用
//匹配查询表
sURIMatcher.addURI(Constant.Authorities, Constant.TableName, MyStudyTableFlag);
//匹配查询表中的特定记录
sURIMatcher.addURI(Constant.Authorities, Constant.TableName+"/#", MyStudyTableNumFlag);
}
@Override
public boolean onCreate() {
Log.d(Constant.TAG,"--MyContentProvider--onCreate--");
return false;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(Constant.TAG,"--MyContentProvider--query--");
int matchId= sURIMatcher.match(uri);
switch(matchId) {
case MyStudyTableFlag://如果查询的是表
SQLiteDatabase db1 = SqlManager.getInstance(getContext()).getWritableDatabase();
// String selectSql1 = "select * from " + Constant.TableName;
Cursor cursor1 = db1.query(true, Constant.TableName, projection,
selection, selectionArgs, null, null, null, null);
return cursor1;
case MyStudyTableNumFlag://如果查询的是表中的确定id的记录
long id = ContentUris.parseId(uri);//ContentUris的使用。
Log.d(Constant.TAG, "ContentUris:" + id);
SQLiteDatabase db2 = SqlManager.getInstance(getContext()).getWritableDatabase();
//查找id相匹配的数据。
String selectSql2 = "select * from " + Constant.TableName + " where _id = ?";
String[] args = {"" + id};
Cursor cursor2 = db2.rawQuery(selectSql2, args);
return cursor2;
}
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
Log.d(Constant.TAG,"--MyContentProvider--getType--");
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = SqlManager.getInstance(getContext()).getWritableDatabase();
db.insert(Constant.TableName,null,values);
Log.d(Constant.TAG,"--MyContentProvider--insert--");
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(Constant.TAG,"--MyContentProvider--delete--");
int matchId= sURIMatcher.match(uri);
switch(matchId) {
case MyStudyTableFlag://
SQLiteDatabase db1 = SqlManager.getInstance(getContext()).getWritableDatabase();
// String selectSql1 = "select * from " + Constant.TableName;
db1.delete(Constant.TableName, selection, selectionArgs);
break;
case MyStudyTableNumFlag:
long id = ContentUris.parseId(uri);//ContentUris的使用。
Log.d(Constant.TAG, "ContentUris:" + id);
SQLiteDatabase db2 = SqlManager.getInstance(getContext()).getWritableDatabase();
String[] args = {"" + id};
db2.delete(Constant.TableName, "_id = ?", args);
break;
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d(Constant.TAG,"--MyContentProvider--update--");
int matchId= sURIMatcher.match(uri);
switch(matchId) {
case MyStudyTableFlag:
SQLiteDatabase db1 = SqlManager.getInstance(getContext()).getWritableDatabase();
db1.update(Constant.TableName, values, selection, selectionArgs);
break;
case MyStudyTableNumFlag:
long id = ContentUris.parseId(uri);//ContentUris的使用。
Log.d(Constant.TAG, "ContentUris:" + id);
SQLiteDatabase db2 = SqlManager.getInstance(getContext()).getWritableDatabase();
String[] args = {"" + id};
db2.update(Constant.TableName, values,"_id = ?", args);
break;
}
return 0;
}
}
第二步注册contentProvider:
<provider
android:name=".contentprovider.MyContentProvider"
android:authorities="com.czh.mycontentprovider.study" />
第三步外部应用增删改查
查找数据的代码:
String uriStr = "content://com.czh.mycontentprovider.study/"+Constant.TableName+"/4";
String[] projection = {Constant._ID,Constant.NAME,Constant.AGE};
String selection = "_id = ?";
String[] selectionArgs = {"2"};
Cursor cursor = getContentResolver().query(Uri.parse(uriStr),projection,selection,selectionArgs,null,null);
CursorAdapter ca = new SqlAdapter(MainActivity.this, cursor);
listView.setAdapter(ca);
插入数据的代码:
String uriStr = "content://com.czh.mycontentprovider.study";
ContentValues cv = new ContentValues();
cv.put("name","yyd插入测试");
cv.put("age",100);
getContentResolver().insert(Uri.parse(uriStr), cv);
删除数据的代码:
String uriStr = "content://com.czh.mycontentprovider.study/"+Constant.TableName+"/10";
// Uri url, String where, String[] selectionArgs
getContentResolver().delete(Uri.parse(uriStr),null,null);
更新数据的代码:
String uriStr = "content://com.czh.mycontentprovider.study/"+Constant.TableName+"/10";
getContentResolver().delete(Uri.parse(uriStr),null,null);
4 contentprovider使用小结
外部应用操作步骤:
第一步获取ContentResolver
ContentResolver cr = getContentResolver();
第二步:
利用ContentResolver,结合uri进行增删改查。
主应用使用步骤:
第一步:继承contentprovider,重写它的方法
第二步:在注册contentprovider。
监听ContentProvider中数据的变化
步骤:
1.在ContentProvider类的insert\update\delete方法添加改变通知:
this.getContext().getContentResolver().notifyChange(URI,null);
参数 | 介绍 |
---|---|
uri | 表示监听的URI |
null | 表示发送消息给任何人; |
2.在外部应用设置监听:
getContentResolver().registerContentObserver(Uri.parse("content://com.czh.mycontentprovider.study"),true,new PersonObserver(new Handler()));
private class PersonObserver extends ContentObserver{//监听
public PersonObserver(Handler handler) {
super(handler);
}
//当ContentProvier数据发生改变,则触发该函数
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.i("myapp", "数据改变");
}
}
挺简单的,这里不过多讨论。
5 ContentProvider深入理解
讲到最后了,我们来讨论几个问题:
1. 在应用程序A里面怎么跨进程拿到ContentProvider的对象呢?
2. ContentProvider实例对象是保存在哪里呢?
3. ContentProvider的方法实现要注意线程安全吗?
这块还没研究透彻,后面补充。
6 参考文档
【1】Android开发手册
【2】内容提供程序
【3】ContentProvider浅析—写点你平时没注意到的
7 结尾
文章的结尾奉送上代码方便大家对照学习。
好了就讲到这里吧。希望对大家有所帮助!如果觉的我写的不错,就点个赞吧!