Android - 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 的格式如下:

1
[scheme:][//host:port][path][?query]

举个例子更便于理解

1
Uri: https://www.baidu.com:443/s?wd=Uri
格式 注释 安卓中获取方法
scheme 即协议,这里的协议为 https getScheme()
host 主机名,这里为 www.baidu.com getHost()
port 端口,这里为 443 getPost()
path 资源路径,这里为 s getPath()
query 查询,问号之后的每一个键值对都是一个查询规则,由 & 隔开,这里为 wd=Uri getQuery()

ContentProvider 示例

定义一个 StudentContentProvider 类用来共享该应用的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class StudentContentProvider extends ContentProvider {

// AUTHORITY 就是在 AndroidManifest.xml 中配置的 authorities
private static final String AUTHORITY = "cn.hoshinosuzumi.studentProvider";
// 匹配成功后的匹配码
private static final int MATCH_CODE = 100;
private static UriMatcher uriMatcher;
private StudentDB studentDB;

// 数据改变后指定通知的 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() {
studentDB = StudentDB.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 = studentDB.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) {
studentDB.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 = studentDB.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 文件中声明

1
2
3
4
5
<provider
android:name=".StudentContentProvider"
android:authorities="cn.hoshinosuzumi.studentProvider"
android:enabled="true"
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 方法。

ContentObserver 示例

来吧,新建一个项目,在这个项目中来操作刚才自定义的 ContentProvider 中的数据

  • 首先获得 ContentObserver 实例
1
ContentResolver contentResolver = getContentResolver();
  • 注册 ContentObserver 来监听对应 uri 的数据变化,这步时可选的,如果不需要监听数据变化也当然也可以不注册啦~
1
2
3
4
private static final String AUTHORITY = "cn.hoshinosuzumi.studentProvider";
private static final Uri STUDENT_URI = Uri.parse("content://" + AUTHORITY + "/student");

contentResolver.registerContentObserver(STUDENT_URI, true, new MyContentObserver(handler));

这里在注册 ContentObserver 的方法中需要传递一个 ContentObserver 对象,下面是自定义的 StudentContentObserver 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StudentContentObserver extends ContentObserver {

Handler mHandler;
public StudentContentObserver(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 来操作数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public void onClick(View v) {
switch (v.getId()) {
case 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();
}
break;
case R.id.btn_insert:
ContentValues contentValues = new ContentValues();
contentValues.put("name", "无罪不犯的张三");
contentValues.put("age", "-1");
contentValues.put("desc", "张三XX不给钱");
contentResolver.insert(STUDENT_URI, contentValues);
break;
case R.id.btn_delete:
contentResolver.delete(STUDENT_URI, null, null);
break;
}
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×