ContentProvider初体验

写在前面

当我们想要读取写入第三方app的数据库的时候怎么办?默认android系统是有权限限制不能访问别的app的数据库的,那么我们应该怎么做,才能让app可以读取别的app的数据库,ContentProvider就应用而生了,专门用于app的数据库访问读取操作。

provider提供者

作为provider的提供者,我们至少要准备的两个文件AndroidManifest.xml和继承自ContentProvider的类,下面来看下具体的实现。
AndroidManifest.xml

1
2
3
4
<provider
android:name="com.pachong.main.MyContentProvider"
android:authorities="com.pachong.myprovider"
android:exported="true" />

name为自定义ContentProvider完整类名。
authorities为自定义认证字符串,类似Activity、Broadcast和Service定义的隐式启动的action字符串,第三方应用要想调用到本provider就得用这个authorities的字符串。
exported为true表示可以被其他应用访问。

MyContentProvider.java

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
public class MyContentProvider extends ContentProvider {
private static final UriMatcher mUriMatcher;
private static final String AUTHORITY = "com.pachong.myprovider";
private static final String PATH_NAME = "name";
private static final String PATH_PASSWORD = "password/private/steven";
private static final String CONTENT_TYPE_NAME = "vnd.android.cursor.dir/steven.name";
private static final String CONTENT_TYPE_PASSWORD = "vnd.android.cursor.item/steven.password";
private static final int CODE_NAME = 1;
private static final int CODE_PASSWORD = 2;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, PATH_NAME, CODE_NAME);
mUriMatcher.addURI(AUTHORITY, PATH_PASSWORD, CODE_PASSWORD);
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (mUriMatcher.match(uri)) {
case CODE_NAME:
break;
case CODE_PASSWORD:
break;
default:
break;
}
Log.d("TAG", "query:"+mUriMatcher.match(uri)+",uri:"+uri);
return null;
}
@Override
public String getType(Uri uri) {
String result = null;
switch (mUriMatcher.match(uri)) {
case CODE_NAME:
result = CONTENT_TYPE_NAME;
break;
case CODE_PASSWORD:
result = CONTENT_TYPE_PASSWORD;
break;
default:
break;
}
Log.d("TAG", "getType:"+result+",uri:"+uri);
return result;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
switch (mUriMatcher.match(uri)) {
case CODE_NAME:
break;
case CODE_PASSWORD:
break;
default:
break;
}
Log.d("TAG", "insert:"+mUriMatcher.match(uri)+",uri:"+uri);
getContext().getContentResolver().notifyChange(uri, null);
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
switch (mUriMatcher.match(uri)) {
case CODE_NAME:
break;
case CODE_PASSWORD:
break;
default:
break;
}
Log.d("TAG", "delete:"+mUriMatcher.match(uri)+",uri:"+uri);
getContext().getContentResolver().notifyChange(uri, null);
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
switch (mUriMatcher.match(uri)) {
case CODE_NAME:
break;
case CODE_PASSWORD:
break;
default:
break;
}
Log.d("TAG", "update:"+mUriMatcher.match(uri)+",uri:"+uri);
getContext().getContentResolver().notifyChange(uri, null);
return 0;
}
}

MyContentProvider是继承自ContentProvider的类,默认会重写onCreate、query、getType、insert、delete、update等方法。在MyContentProvider中我们初始化了UriMatcher并addURI了两条uri,那么UriMatcher是做什么的呢?其实它就像一个过滤网,比如我们有很多种AUTHORITY = “com.pachong.myprovider”的uri,但是我们并不想每一种都处理,只处理我们要处理里的某一条或者某几条uri,那要怎么办呢?这个时候就得有我们的过滤装置UriMatcher了,UriMatcher通过addURI预设我们要处理的uri,然后通过match来比较uri是否是我们预设的uri,如果是就返回想要的code,这样我们就可以做特定的操作了。MyContentProvider类中我没有做数据库的操作,想进一步深入的朋友可以参考ContentProvider数据库共享之——实例讲解。query、insert、delete、update等都是对应的数据库操作方法,我们只要match出相应的uri,做处理就ok了。在insert、delete、update方法中我们加了一句getContext().getContentResolver().notifyChange(uri, null);加这句话的目的是通知相关的调用者本provider有数据更新。另外getType与启动指定的Activity或者Service中这个属性有关,不多说了,可以参考ContentProvider数据库共享之——MIME类型与getType()做深入了解。

provider调用者

再起一个新的app,然后实现下面的代码做测试。主要用到的方法getContentResolver()的insert、query、delete、update、registerContentObserver和unregisterContentObserver。
Main.java

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
public class Main extends Activity implements OnClickListener {
private String[] mUris = {
"content://com.pachong.myprovider/name",
"content://com.pachong.myprovider/password/private/steven",
"content://com.pachong.myprovider/other"
};
private MyContentObserver mObserver = new MyContentObserver(null);
private class MyContentObserver extends ContentObserver {
public MyContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.d("TAG", "onChange:"+selfChange);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.insert).setOnClickListener(this);
findViewById(R.id.query).setOnClickListener(this);
findViewById(R.id.delete).setOnClickListener(this);
findViewById(R.id.update).setOnClickListener(this);
getContentResolver().registerContentObserver(Uri.parse(mUris[0]), true, mObserver);
}
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(mObserver);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.insert:
getContentResolver().insert(Uri.parse(mUris[0]), new ContentValues());
break;
case R.id.query:
getContentResolver().query(Uri.parse(mUris[1]), null, null, null, null);
break;
case R.id.delete:
getContentResolver().delete(Uri.parse(mUris[2]), null, null);
break;
case R.id.update:
getContentResolver().update(Uri.parse(mUris[1]), new ContentValues(), null, null);
break;
default:
break;
}
}
}

Main.java中我声明了3条了Uri,其中有两条是和MyContentProvider匹配的,另外一条是不匹配的。这样做的目的就是测试前面所说UriMatcher.match的作用。

main.xml

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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="insert" />
<Button
android:id="@+id/query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="query" />
<Button
android:id="@+id/delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="delete" />
<Button
android:id="@+id/update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="update" />
</LinearLayout>

依次点击上面的四个按钮后,打印的消息如下:

1
2
3
4
5
08-25 19:59:28.150: D/TAG(26776): insert:1,uri:content://com.pachong.myprovider/name
08-25 19:59:28.150: D/TAG(27469): onChange:false
08-25 19:59:28.800: D/TAG(26776): query:2,uri:content://com.pachong.myprovider/password/private/steven
08-25 19:59:29.200: D/TAG(26776): delete:-1,uri:content://com.pachong.myprovider/other
08-25 19:59:29.600: D/TAG(26776): update:2,uri:content://com.pachong.myprovider/password/private/steven

通过log可以看到和MyContentProvider匹配的uri都返回了正确的code,所以我们在做数据库读取操作时一定要和对应的ContentProvider里的uri一致。才能保证操作生效。其他就不多说了,现在也只是通过启舰的博客对ContentProvider有了一定的认识,实际项目中还没有用到,等到工作中有项目用它再做深入的探究。

参考文章

ContentProvider数据库共享之——概述
ContentProvider数据库共享之——实例讲解
ContentProvider数据库共享之——MIME类型与getType()
ContentProvider数据库共享之——读写权限与数据监听