ContentProvider详解
概述
ContentProvider虽说我们平时用的并不多,但是作为安卓四大组件之一,其地位不容忽视。ContentProvider的作用是为不同的应用之间数据共享,提供统一的接口,我们知道安卓系统中应用内部的数据是对外隔离的,要想让其它应用能使用自己的数据(例如通讯录)这个时候就用到了ContentProvider。
ContentProvider如何共享数据
ContentProvider通过uri来标识其它应用要访问的数据,通过ContentResolver的增、删、改、查方法实现对共享数据的操作。还可以通过注册ContentObserver来监听数据是否发生了变化来对应的刷新页面。下面分别说说每个类的作用。
ContentProvider讲解
ContentProvider是一个抽象类,如果我们需要开发自己的内容提供者我们就需要继承这个类并复写其方法,需要实现的主要方法如下:
public boolean onCreate()
在创建ContentProvider时使用
public Cursor query()
用于查询指定uri的数据返回一个Cursor
public Uri insert()
用于向指定uri的ContentProvider中添加数据
public int delete()
用于删除指定uri的数据
public int update()
用户更新指定uri的数据
public String getType()
用于返回指定的Uri中的数据MIME类型
数据访问的方法insert,delete和update可能被多个线程同时调用,此时必须是线程安全的
uri讲解
其它应用可以通过ContentResolver来访问ContentProvider提供的数据,而ContentResolver通过uri来定位自己要访问的数据,所以我们要先了解uri。URI(Universal Resource Identifier)统一资源定位符,如果您使用过安卓的隐式启动就会发现,在隐式启动的过程中我们也是通过uri来定位我们需要打开的Activity并且可以在uri中传递参数。URI的格式如下:
[scheme:][//host:port][path][?query]
单单看这个可能我们并不知道是什么意思,下面来举个栗子就一目了然了
URI:http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
scheme:根据格式我们很容易看出来scheme为http
host:www.baidu.com
port:就是主机名后面path前面的部分为8080
path:在port后面?的前面为wenku/jiatiao.html
query:?之后的都是query部分为 id=123456$name=jack
uri的各个部分在安卓中都是可以通过代码获取的,下面我们就以上面这个uri为例来说下获取各个部分的方法:
getScheme() :获取Uri中的scheme字符串部分,在这里是http
getHost():获取Authority中的Host字符串,即 www.baidu.com
getPost():获取Authority中的Port字符串,即 8080
getPath():获取Uri中path部分,即 wenku/jiatiao.html
getQuery():获取Uri中的query部分,即 id=15&name=du
到这里关于uri的部分就讲解完了,下面让我们通过一个自定义ContentProvider实例来说下自定义ContentProvider的流程以及怎么使用ContentResolver操作ContentProverb的数据
自定义ContentProvider实例讲解
我们先创建一个项目叫contentprovider,在该项目中我们自定义一个StudentContentProvider的类用来共享该应用的数据,自定义StudentContentProvider的代码如下:
public class StudentContentProvider extends ContentProvider {
//这里的AUTHORITY就是我们在AndroidManifest.xml中配置的authorities
private static final String AUTHORITY = "com.jrmf360.studentProvider";
//匹配成功后的匹配码
private static final int MATCH_CODE = 100;
private static UriMatcher uriMatcher;
private StudentDao studentDao;
//数据改变后指定通知的Uri
private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/student");
static {
//匹配不成功返回NO_MATCH(-1)
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//添加我们需要匹配的uri
uriMatcher.addURI(AUTHORITY,"student", MATCH_CODE);
}
@Override
public boolean onCreate() {
studentDao = StudentDao.getInstance(getContext());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
int match = uriMatcher.match(uri);
if (match == MATCH_CODE){
Cursor cursor = studentDao.queryStudent();
return cursor;
}
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
if (uriMatcher.match(uri) == MATCH_CODE){
studentDao.insertStudent(values);
notifyChange();
}
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
if (uriMatcher.match(uri) == MATCH_CODE){
int delCount = studentDao.deleteStudent();
notifyChange();
return delCount;
}
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}
private void notifyChange(){
getContext().getContentResolver().notifyChange(NOTIFY_URI,null);
}
}
例子中有注释,但还是要说明一下几点:
- ContentProvider是安卓四大组件之一,所以它和Activity一样也需要我们再xml文件中声明,声明如下:
<provider
android:authorities="com.jrmf360.studentProvider"
android:name=".StudentContentProvider"
android:exported="true"/>
这里的authorities唯一标识该内容提供者,这样其它的应用才可以找到该内容提供者并操作它的数据;exported为true当前内容提供者可以被其它应用使用,默认为true。
- 在query、insert和delete方法中都是先调用uriMatcher.match(uri) 判断当前uri是不是匹配,如果匹配才能操作数据(该例子没有添加update功能,方式与其它三个方法一样),MATCH_CODE是我们在调用
public void addURI (String authority, String path, int code)
方法添加uri时设置的,当外部应用传递过来的uri与对应add的uri一致时,会返回我们设置的code。 - 该例子中整个数据的操作都是通过SQLite数据库完成的,在onCreate方法中我们先获得数据库的操作对象,通过该对象完成数据库的增、删、改、查,数据库的代码比较简单就不贴了。
- 在数据库发生变化的时候我们调用notifyChange方法
private void notifyChange(){
getContext().getContentResolver().notifyChange(NOTIFY_URI,null);
}
在该方法中我们通知ContentObserver那个uri数据发生了变化,以便及时更新页面。
通过其他的应用操作ContentProvider中的数据
我们再创建另外一个项目,在该项目的MainActivity方法中我们来操作刚才自定义的ContentProvider中的数据
- 获得ContentObserver
contentResolver = getContentResolver();
- 注册ContentObserver来监听对应uri的数据变化,这步不是必须的,如果不需要监听数据变化也可以不注册
private static final String AUTHORITY = "com.jrmf360.studentProvider";
private static final Uri STUDENT_URI = Uri.parse("content://" + AUTHORITY + "/student");
contentResolver.registerContentObserver(STUDENT_URI, true, new MyContentObserver(handler));
可以看到在注册ContentObserver的方法中我们需要传递一个ContentObserver对象,下面是我写的MyContentObserver类
public class MyContentObserver extends ContentObserver {
Handler mHandler;
public MyContentObserver(Handler handler) {
super(handler);
mHandler = handler;
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
Message message = Message.obtain();
message.obj = uri;
mHandler.sendMessage(message);
}
}
自定义ContentObserver类,必须实现其构造函数,在构造函数中我们需要传递一个Handler,到此我们可以明白,ContentObserver在收到数据变化的通知后通过Handler机制来通知主线程更新UI
- 通过ContentProvider来操作数据
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_query) {
Cursor cursor = contentResolver.query(STUDENT_URI, null, null, null, null, null);
if (cursor != null && cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
int age = cursor.getInt(cursor.getColumnIndex("age"));
String desc = cursor.getString(cursor.getColumnIndex("desc"));
Log.e(getClass().getSimpleName(), "Student:" + "name = " + name + ",age = " + age + ",desc = " + desc);
}
cursor.close();
}
} else if (id == R.id.btn_insert) {
ContentValues contentValues = new ContentValues();
contentValues.put("name", "新插入");
contentValues.put("age", "-100");
contentValues.put("desc", "我是新插入的呦。。。");
contentResolver.insert(STUDENT_URI, contentValues);
} else if (id == R.id.btn_del) {
contentResolver.delete(STUDENT_URI, null, null);
}
}
这样我们就可以对ContentProvider共享的数据进行增删查的操作。至此,就完成了ConentProvider的自定义,通过其他应用操作数据,更新UI的流程,感谢您的阅读。