版权声明:本文出自汪磊的博客,转载请务必注明出处。
本篇博客只是记录一下ContentProvider的使用(这部分工作中用的比较少总是忘记),没有太深入研究。已经熟练掌握使用方式,想深入了解内部机制的同学可以绕过了。
一、ContentProvider概述
Android应用程序运行在不同的进程空间中,因此不同应用程序的数据是不能够直接访问的。为了增强程序之间的数据共享能力,Android系统提供了像SharedPreferences这类简单的跨越程序边界的访问方法,但这些方法都存在一定的局限性,提供数据的能力有限,安卓系统提供了另一种跨进程提供数据的方式也就ContentProvider,ContentProvider翻译过来叫做:数据提供者,是应用程序之间共享数据的一种接口机制,其他应用程序则可以在不知道数据来源的情况下,对共享数据进行增删改查等操作。在Android系统中,许多系统内置的数据也是通过ContentProvider提供给用户使用,例如通讯录、音视频图像文件等。
二、ContentProvider调用
调用者不能直接调用ContentProvider的接口函数,需要通过ContentResolver对象,通过URI间接调用ContentProvider,Android系统根据URI确定处理这个查询的ContentProvider。
三、通用资源标识符URI
URI可以理解为一个个网站的访问地址,比如百度有百度的地址,阿里有阿里的地址,同样在安卓系统中每个ContentProvider也都有自己的访问地址,ContentProvider使用的URI语法结构如下:
content://<authority>/<data_path>/<id>
- content:// 是通用前缀,表示该UIR用于ContentProvider定位资源。
- < authority > 是授权者名称,用来确定具体由哪一个ContentProvider提供资源。因此一般< authority >都由类的小写全称组成,以保证唯一性。
- < data_path > 是数据路径,用来确定请求的是哪个数据集。如果ContentProvider只提供一个数据集,数据路径则可以省略;如果ContentProvider提供多个数据集,数据路径必须指明具体数据集。数据集的数据路径可以写成多段格式,例如people/delete和people/insert。
- < id > 是数据编号,用来唯一确定数据集中的一条记录,匹配数据集中_ID字段的值。如果请求的数据不只一条,< id >可以省略。
四
、创建ContentProvider
创建一个类继承ContentProvider,重载6个函数,分别为onCreate(),getType(),insert()、delete()、update()、query()。
onCreate()
一般用来初始化底层数据集和建立数据连接等工作
getType()
用来返回指定URI的MIME数据类型,若URI是单条数据,则返回的MIME数据类型以vnd.android.cursor.item开头;若URI是多条数据,则返回的MIME数据类型以vnd.android.cursor.dir/开头。
insert()、delete()、update()、query()
用于对数据集的增删改查操作。
五
、UriMatcher类
UriMatcher类其实就是一个工具类,用于匹配用户传递进来的Uri。
示例:
1 private static final int PRESON_INSERT_CODE = 0;
2 private static final int PERSON_DELETE_CODE = 1;
3 private static final int PERSON_UPDATE_CODE = 2;
4 private static final int PERSON_QUERY_ALL_CODE = 3;
5 private static final int PERSON_QUERY_ITEM_CODE = 4;
6 //
7 private static UriMatcher uriMatcher;
8 private PersonSQLiteOpenHelper mOpenHelper;
9
10 static {
11 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
12
13 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_INSERT,
14 PRESON_INSERT_CODE);
15 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_DELETE,
16 PERSON_DELETE_CODE);
17 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_UPDATE,
18 PERSON_UPDATE_CODE);
19 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_QUERY_ALL,
20 PERSON_QUERY_ALL_CODE);
21 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_QUERY_ITEM,
22 PERSON_QUERY_ITEM_CODE);
23 }
UriMatcher的构造函数中,UriMatcher.NO_MATCH是URI无匹配时的返回代码,值为-1。 addURI() 方法用来添加新的匹配项,语法为:
public void addURI(String authority, String path, int code)
其中authority表示匹配的授权者名称,path表示数据路径,code表示匹配成功时的返回代码。
使用示例:
1 @Override
2 public String getType(Uri uri) {
3 switch (uriMatcher.match(uri)) {
4 case PERSON_QUERY_ALL_CODE: // 返回多条的MIME-type
5 return "vnd.android.cursor.dir/person";
6 case PERSON_QUERY_ITEM_CODE: // 返回单条的MIME-TYPE
7 return "vnd.android.cursor.item/person";
8 default:
9 break;
10 }
11 return null;
12 }
六
、ContentObserver简要介绍
ContentObserver——内容观察者,观察)特定Uri引起的数据库的变化,继而做一些相应的处理,当ContentObserver所观察的Uri发生变化时,便会触发它回调onChange方法。
ContentObserver的编写:创建一个类继承自ContentObserver,重写onChange,监听的的url数据发生变化时就会回调此方法。
示例:
1 public class PersonContentObserver extends ContentObserver {
2
3 //
4 private static final String TAG = "TestCase";
5 private Context mContext;
6
7 public PersonContentObserver(Handler handler,Context mContext) {
8 super(handler);
9 this.mContext = mContext;
10 }
11
12 @Override
13 public void onChange(boolean selfChange) {
14 //
1516 }
17
18 }
ContentObserver的注册:ContentObserver的注册是由ContentResolver来完成的。
示例:
1 public class MainActivity extends Activity {
2
3 private PersonContentObserver mContentObserver;
4
5 @Override
6 protected void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_main);
9
10 mContentObserver = new PersonContentObserver(new Handler(),this);
11 getContentResolver().registerContentObserver(Person.CONTENT_URI_DELETE,
12 true, mContentObserver);
13 }
14
15 @Override
16 protected void onDestroy() {
17 //
18 super.onDestroy();
19
20 getContentResolver().unregisterContentObserver(mContentObserver);
21 }
22 }
void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendents, @NonNull ContentObserver observer)参数说明
uri:监测的uri地址
notifyForDescendents:为true 表示可以同时匹配其派生的Uri,false只精确匹配当前Uri.
observer:就是我们自己编写的ContentObserve了。
七
、Demo源码示例
1,编写ContentProvider工程,此工程演示ContentProvider的创建以及ContentObserver的使用
工程目录:
先来看看Person类:
1 public class Person {
2
3 public static final String AUTHORITY = "com.wanglei.personcontentprovider";
4 //
5 public static final String PATH_INSERT = "person/insert";
6 public static final String PATH_DELETE = "person/delete";
7 public static final String PATH_UPDATE = "person/update";
8 public static final String PATH_QUERY_ALL = "person/queryAll";
9 public static final String PATH_QUERY_ITEM = "person/query/#";
10 //
11 public static final Uri CONTENT_URI_INSERT = Uri.parse("content://" + AUTHORITY + "/" + PATH_INSERT);
12 public static final Uri CONTENT_URI_DELETE = Uri.parse("content://" + AUTHORITY + "/" + PATH_DELETE);
13 public static final Uri CONTENT_URI_UPDATE = Uri.parse("content://" + AUTHORITY + "/" + PATH_UPDATE);
14 public static final Uri CONTENT_URI_QUERY_ALL = Uri.parse("content://" + AUTHORITY + "/" + PATH_QUERY_ALL);
15 public static final Uri CONTENT_URI_QUERY_ITEM = Uri.parse("content://" + AUTHORITY + "/" + PATH_QUERY_ITEM);
16 //
17 public static final String KEY_ID = "_id";
18 public static final String KEY_NAME = "name";
19 public static final String KEY_AGE = "age";
20 }
此类只是定义了一些常量,由于只是演示,为了方便没有写成正规的javaBean.很简单,没有多余需要解释的。
接下来看下PersonSQLiteOpenHelper类,此类就是数据库的创建了,很简单,如下:
1 /**
2 * @author andong 数据库帮助类, 用于创建和管理数据库的.
3 */
4 public class PersonSQLiteOpenHelper extends SQLiteOpenHelper {
5
6 //数据库名称
7 private static final String DB_NAME = "person.db";
8 //表名称
9 public static final String TABLE_NAME = "person";
10
11 /**
12 * 数据库的构造函数
13 *
14 * @param context
15 *
16 * name 数据库名称 factory 游标工程 version 数据库的版本号 不可以小于1
17 */
18 public PersonSQLiteOpenHelper(Context context) {
19 super(context, DB_NAME, null, 1);
20 }
21
22 /**
23 * 数据库第一次创建时回调此方法. 初始化一些表
24 */
25 @Override
26 public void onCreate(SQLiteDatabase db) {
27
28 // 操作数据库
29 String sql = "create table " + TABLE_NAME + "(" + Person.KEY_ID
30 + " integer primary key autoincrement, " + Person.KEY_NAME
31 + " varchar(100), "+Person.KEY_AGE+" integer);";
32 db.execSQL(sql); // 创建person表
33 }
34
35 /**
36 * 数据库的版本号更新时回调此方法, 更新数据库的内容(删除表, 添加表, 修改表)
37 */
38 @Override
39 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
40
41 db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
42 onCreate(db);
43 }
44 }
接下来继续看下PersonContentProvider类:
1 public class PersonContentProvider extends ContentProvider {
2
3 private static final int PRESON_INSERT_CODE = 0;
4 private static final int PERSON_DELETE_CODE = 1;
5 private static final int PERSON_UPDATE_CODE = 2;
6 private static final int PERSON_QUERY_ALL_CODE = 3;
7 private static final int PERSON_QUERY_ITEM_CODE = 4;
8 //
9 private static UriMatcher uriMatcher;
10 private PersonSQLiteOpenHelper mOpenHelper;
11
12 static {
13 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
14
15 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_INSERT,
16 PRESON_INSERT_CODE);
17 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_DELETE,
18 PERSON_DELETE_CODE);
19 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_UPDATE,
20 PERSON_UPDATE_CODE);
21 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_QUERY_ALL,
22 PERSON_QUERY_ALL_CODE);
23 uriMatcher.addURI(Person.AUTHORITY, Person.PATH_QUERY_ITEM,
24 PERSON_QUERY_ITEM_CODE);
25 }
26
27 @Override
28 public boolean onCreate() {
29 mOpenHelper = new PersonSQLiteOpenHelper(getContext());
30 return true;
31 }
32
33 @Override
34 public Cursor query(Uri uri, String[] projection, String selection,
35 String[] selectionArgs, String sortOrder) {
36 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
37 switch (uriMatcher.match(uri)) {
38 case PERSON_QUERY_ALL_CODE: // 查询所有人的uri
39 if (db.isOpen()) {
40 Cursor cursor = db.query(PersonSQLiteOpenHelper.TABLE_NAME,
41 projection, selection, selectionArgs, null, null,
42 sortOrder);
43 cursor.setNotificationUri(getContext().getContentResolver(), uri);
44 return cursor;
45 // db.close(); 返回cursor结果集时, 不可以关闭数据库
46 }
47 break;
48 case PERSON_QUERY_ITEM_CODE: // 查询的是单条数据, uri末尾出有一个id
49 if (db.isOpen()) {
50
51 long id = ContentUris.parseId(uri);
52
53 Cursor cursor = db.query(PersonSQLiteOpenHelper.TABLE_NAME,
54 projection, Person.KEY_ID + " = ?", new String[] { id
55 + "" }, null, null, sortOrder);
56 cursor.setNotificationUri(getContext().getContentResolver(), uri);
57 return cursor;
58 }
59 break;
60 default:
61 throw new IllegalArgumentException("uri不匹配: " + uri);
62 }
63 return null;
64 }
65
66 @Override
67 public String getType(Uri uri) {
68 switch (uriMatcher.match(uri)) {
69 case PERSON_QUERY_ALL_CODE: // 返回多条的MIME-type
70 return "vnd.android.cursor.dir/person";
71 case PERSON_QUERY_ITEM_CODE: // 返回单条的MIME-TYPE
72 return "vnd.android.cursor.item/person";
73 default:
74 break;
75 }
76 return null;
77 }
78
79 @Override
80 public Uri insert(Uri uri, ContentValues values) {
81
82 switch (uriMatcher.match(uri)) {
83 case PRESON_INSERT_CODE: // 添加人到person表中
84 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
85
86 if (db.isOpen()) {
87
88 long id = db.insert(PersonSQLiteOpenHelper.TABLE_NAME, null,
89 values);
90
91 db.close();
92 Uri newUri = ContentUris.withAppendedId(uri, id);
93 //通知内容观察者数据发生变化
94 getContext().getContentResolver().notifyChange(newUri, null);
95 return newUri;
96 }
97 break;
98 default:
99 throw new IllegalArgumentException("uri不匹配: " + uri);
100 }
101 return null;
102 }
103
104 @Override
105 public int delete(Uri uri, String selection, String[] selectionArgs) {
106 switch (uriMatcher.match(uri)) {
107 case PERSON_DELETE_CODE: // 在person表中删除数据的操作
108 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
109 if (db.isOpen()) {
110 int count = db.delete(PersonSQLiteOpenHelper.TABLE_NAME,
111 selection, selectionArgs);
112 db.close();
113 //通知内容观察者数据发生变化
114 getContext().getContentResolver().notifyChange(uri, null);
115 return count;
116 }
117 break;
118 default:
119 throw new IllegalArgumentException("uri不匹配: " + uri);
120 }
121 return 0;
122 }
123
124 @Override
125 public int update(Uri uri, ContentValues values, String selection,
126 String[] selectionArgs) {
127 switch (uriMatcher.match(uri)) {
128 case PERSON_UPDATE_CODE: // 更新person表的操作
129 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
130 if (db.isOpen()) {
131 int count = db.update(PersonSQLiteOpenHelper.TABLE_NAME,
132 values, selection, selectionArgs);
133 db.close();
134 //通知内容观察者数据发生变化
135 getContext().getContentResolver().notifyChange(uri, null);
136 return count;
137 }
138 break;
139 default:
140 throw new IllegalArgumentException("uri不匹配: " + uri);
141 }
142 return 0;
143 }
144
145 }
可以看到此ContentProvider内部操作对象就是person.db中的person表,并且对数据库操作完调用 getContext().getContentResolver().notifyChange(uri, null)通知对应内容观察者数据发生了变化。
PersonContentObserver类:
1 public class PersonContentObserver extends ContentObserver {
2
3 //
4 private static final String TAG = "TestCase";
5 private Context mContext;
6
7 public PersonContentObserver(Handler handler,Context mContext) {
8 super(handler);
9 this.mContext = mContext;
10 }
11
12 @Override
13 public void onChange(boolean selfChange) {
14 //
15 Log.i(TAG, "PersonContentObserver");
16 ContentResolver resolver = mContext.getContentResolver();
17
18 Cursor cursor = resolver
19 .query(Person.CONTENT_URI_QUERY_ALL, new String[] {
20 Person.KEY_ID, Person.KEY_NAME, Person.KEY_AGE }, null,
21 null, "_id desc");
22
23 if (cursor != null && cursor.getCount() > 0) {
24
25 int id;
26 String name;
27 int age;
28 while (cursor.moveToNext()) {
29 id = cursor.getInt(cursor.getColumnIndex(Person.KEY_ID));
30 name = cursor.getString(cursor.getColumnIndex(Person.KEY_NAME));
31 age = cursor.getInt(cursor.getColumnIndex(Person.KEY_AGE));
32 Log.i(TAG, "id: " + id + ", name: " + name + ", age: " + age);
33 }
34 cursor.close();
35 }
36 }
37 }
在我们接收到数据发生变化的时候进行的操作是重新查询person表中所有数据。
最后在MainActivity中注册PersonContentObserver:
1 public class MainActivity extends Activity {
2
3 private PersonContentObserver mContentObserver;
4
5 @Override
6 protected void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_main);
9
10 mContentObserver = new PersonContentObserver(new Handler(),this);
11 getContentResolver().registerContentObserver(Person.CONTENT_URI_DELETE,
12 true, mContentObserver);
13 }
14
15 @Override
16 protected void onDestroy() {
17 //
18 super.onDestroy();
19
20 getContentResolver().unregisterContentObserver(mContentObserver);
21 }
22 }
别忘了在清单文件中注册内容提供者:
1 <provider
2 android:name="com.wanglei.provider.PersonContentProvider"
3 android:authorities="com.wanglei.personcontentprovider"
4 android:exported="true" >
5 </provider>
接下来我们就要编写新项目测试我们的PersonContentProvider能不能正常使用以及PersonContentObserver能不能检测到数据发生发生变化了。
编写UseContentProvider项目,结构如下:
其中Person类和上面的Person类是一样的。
test.java类就是测试类了,测试增删改查:
1 public class test extends AndroidTestCase {
2
3 private static final String TAG = "TestCase";
4
5 public void testInsert() {
6 // 内容提供者访问对象
7 ContentResolver resolver = getContext().getContentResolver();
8
9 for (int i = 0; i < 10; i++) {
10 //
11 ContentValues values = new ContentValues();
12 values.put(Person.KEY_NAME, "wanglei"+i);
13 values.put(Person.KEY_AGE, i);
14 Uri uri = resolver.insert(Person.CONTENT_URI_INSERT, values);
15 Log.i(TAG, "uri: " + uri);
16 long id = ContentUris.parseId(uri);
17 Log.i(TAG, "添加到: " + id);
18 }
19 }
20
21 public void testDelete() {
22
23 // 内容提供者访问对象
24 ContentResolver resolver = getContext().getContentResolver();
25 String where = Person.KEY_ID + " = ?";
26 String[] selectionArgs = { "3" };
27 int count = resolver.delete(Person.CONTENT_URI_DELETE, where,
28 selectionArgs);
29 Log.i(TAG, "删除行: " + count);
30 }
31
32 public void testUpdate() {
33
34 // 内容提供者访问对象
35 ContentResolver resolver = getContext().getContentResolver();
36
37 ContentValues values = new ContentValues();
38 values.put(Person.KEY_NAME, "lisi");
39
40 int count = resolver.update(Person.CONTENT_URI_UPDATE, values,
41 Person.KEY_ID + " = ?", new String[] { "1" });
42 Log.i(TAG, "更新行: " + count);
43 }
44
45 public void testQueryAll() {
46
47 // 内容提供者访问对象
48 ContentResolver resolver = getContext().getContentResolver();
49
50 Cursor cursor = resolver
51 .query(Person.CONTENT_URI_QUERY_ALL, new String[] {
52 Person.KEY_ID, Person.KEY_NAME, Person.KEY_AGE }, null,
53 null, "_id desc");
54
55 if (cursor != null && cursor.getCount() > 0) {
56
57 int id;
58 String name;
59 int age;
60 while (cursor.moveToNext()) {
61 id = cursor.getInt(cursor.getColumnIndex(Person.KEY_ID));
62 name = cursor.getString(cursor.getColumnIndex(Person.KEY_NAME));
63 age = cursor.getInt(cursor.getColumnIndex(Person.KEY_AGE));
64 Log.i(TAG, "id: " + id + ", name: " + name + ", age: " + age);
65 }
66 cursor.close();
67 }
68 }
69
70 public void testQuerySingleItem() {
71
72 // 在uri的末尾添加一个id
73 Uri uri = ContentUris.withAppendedId(Person.CONTENT_URI_QUERY_ITEM, 1);
74
75 // 内容提供者访问对象
76 ContentResolver resolver = getContext().getContentResolver();
77
78 Cursor cursor = resolver.query(uri, new String[] { Person.KEY_ID,
79 Person.KEY_NAME, Person.KEY_AGE }, null, null, null);
80
81 if (cursor != null && cursor.moveToFirst()) {
82 int id = cursor.getInt(0);
83 String name = cursor.getString(1);
84 int age = cursor.getInt(2);
85 cursor.close();
86 Log.i(TAG, "id: " + id + ", name: " + name + ", age: " + age);
87 }
88 }
89 }
好了,本片只是个人记录一些经常忘记的知识点方便以后忘记了可以翻翻,没有特别仔细分析。