版权声明:本文出自汪磊的博客,转载请务必注明出处。
本篇博客只是记录一下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 }
好了,本片只是个人记录一些经常忘记的知识点方便以后忘记了可以翻翻,没有特别仔细分析。