版權聲明:本文出自汪磊的博客,轉載請務必注明出處。
本篇博客只是記錄一下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 }
好了,本片只是個人記錄一些經常忘記的知識點方便以后忘記了可以翻翻,沒有特別仔細分析。
