Android中資料共用機制的實現——ContentProvider的應用從入門到精通
來源:互聯網
上載者:User
Android中的Content Provider可以實現在許可權許可的情況下,在多個應用程式之間共用資料。Android還提供了一些主要資料類型如音頻、視頻、圖片和私人通訊錄等現成的Content provider類。當然,如果我們想讓我們應用程式的資料公有化,使其它應用也可以訪問,那麼,我們可以實現一個自己的ContentProvider類。一種類型的ContentProvider只允許有一個執行個體,但它可以和多個不同的應用程式或進程的ContentResolver進行通訊。進程間的互動就是由ContentResolver和ContentProvider一起完成的。我們可以通過getContentResolver()方法來獲得 ContentResolver對象。 資料模型: ContentProvider以資料表的形式對外暴露,其中每一行資料都有一個唯一的_ID值。每次查詢資料時都會返回一個Cursor 對象。 不過使用Cursor對象擷取資料前一定要Crowdsourced Security Testing道資料的類型。 Android中通過URI來定位各種資源,每一個可以調用的資源都有一個唯一的URI,片,視頻等。同樣,ContentProvider中的每一個表也各有一個唯一的URI。ContentProvider提供的URI都是以content://開頭的。在所有ContentProvider互動中都要用到URI,而且,每個ContentResolver方法的第一個參數就是URI,它告訴ContentResolver方法要訪問的ContentProvider對象,以及操作的目標表。 因此,為了方便,給自訂ContentProvider中的URI指定一個專門的CONTENT_URI常量是很有必要的。通過Content Provider查詢資料前要準備的: 1,要查詢資料的Provider的URI; 2,要查詢的資料的列名以及它們的資料類型;其中_ID和_COUNT列唯一某行資料以及總資料行數。 3,如果是某一行資料,還需要知道它的ID值;如果_ID=23 則為“content://. . . ./23” 查詢資料: 可以用ContentResolver.query()方法或Activity.managedQuery()方法來擷取結果Cursor。後者得到的Cursor的生命週期由Activity來管理,而不需要我們再手動關閉遊標,我們還可以方便的精確控制它的載入與釋放。Activity.startManagingCursor()方法可以將未受管理的遊標加入到管理列表中(註:2.3版以後推薦使用getLoaderManager()得到LoaderManger對象來管理遊標)stopManagingCursor(Cursor)可以停止自動管理功能。 1,擷取URI: ContentUris.withAppendedId() Uri.withAppendedPath() 以上兩個方法都可以實現將參數(ID)添加到查詢URI當中去。 而ContentUris.parseId(uri)方法可以擷取URI中的ID部分。樣本: import android.provider.Contacts.People; import android.content.ContentUris; import android.net.Uri; import android.database.Cursor; Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23); Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23"); Cursor cur = managedQuery(myPerson, null, null, null, null); 2,讀取資料: private void getColumnData(Cursor cur){ if (cur.moveToFirst()) { String name; String phoneNumber; int nameColumn = cur.getColumnIndex(People.NAME); int phoneColumn = cur.getColumnIndex(People.NUMBER); String imagePath; do { // Get the field values name = cur.getString(nameColumn); phoneNumber = cur.getString(phoneColumn); // Do something with the values. ... } while (cur.moveToNext()); } } 如果是較小的位元據,可以直接像普通資料一樣儲存到表中去,但如果位元據量較大,且表的URI是content:URI形式的,則我們需要採用ContentResolver.openInputStream()方法來獲得資料流,以便讀取資料。 所有資料的更改都需要調用ContentResolver的方法來完成。 首先,將要更改的資料儲存到ContentValues對象中,然後調用ContentResolver的相應方法面完成更新操作,樣本: 增加新記錄: ContentValues values = new ContentValues(); values.put(People.NAME, "Abraham Lincoln"); values.put(People.STARRED, 1); Uri uri = getContentResolver().insert(People.CONTENT_URI, values); 在原有記錄上增加新值: Uri phoneUri = null; phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY); values.clear(); values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE); values.put(People.Phones.NUMBER, "1233214567"); getContentResolver().insert(phoneUri, values); 其中的CONTENT_DIRECTORY是一個常量,表明這是追加的資料。 如果資料是content:URI形式的二進位大對象,調用ContentResolver.openOutputStream()來儲存。 大位元據(多媒體資料)處理樣本: import android.provider.MediaStore.Images.Media; import android.content.ContentValues; import java.io.OutputStream; // Save the name and description of an image in a ContentValues map. ContentValues values = new ContentValues(3); values.put(Media.DISPLAY_NAME, "road_trip_1"); values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles"); values.put(Media.MIME_TYPE, "image/jpeg"); // Add a new record without the bitmap, but with the values just set. // insert() returns the URI of the new record. Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values); // Now get a handle to the file for that record, and save the data into it. // Here, sourceBitmap is a Bitmap object representing the file to save to the database. try { OutputStream outStream = getContentResolver().openOutputStream(uri); sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream); outStream.close(); } catch (Exception e) { Log.e(TAG, "exception while writing image", e); } 實現我們自己的Content Provider: 1,需要建立一個類並且繼承ContentProvider類,並實現這個抽象類別中的六個方法。 public int delete(Uri uri, String selection, String[] selectionArgs){} public Uri insert(Uri uri, ContentValues values) {} public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {} //SQLiteCursor,MatrixCursor public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {} public boolean onCreate() {} public String getType(Uri uri) {} 2,定義URI常量: public static final Uri CONTENT_URI; CONTENT_URI=Uri.parse("content://com.example.codelab.transportationprovider"); 3,定義列名,不要忘記_ID列。 public static String ColumnName 4,如果我們的資料在是新的類型,必須在getType ()中返回這個定義好的MIME 類型,自訂類型的名稱格式為: vnd.android.cursor.item/vnd.yourcompanyname.contenttype vnd.android.cursor.dir/vnd.yourcompanyname.contenttype 5,當儲存大位元據時,列名下實際儲存的是content:URI,另聲明一個“_data”列,儲存檔案的真實路徑,以供ContentResolver.openInputStream()時訪問。 6,當資料發生改變時,最好還要調用ContentResolver.notifyChange()方法,來通知調用程式。 7,在AndroidManifest.xml中聲明ContentProvider。 樣本: <provider android:name=".AutoInfoProvider" android:authorities="com.example.autos.autoinfoprovider" . . . /> </provider> <provider>標籤的屬性中還可以設定ContentProvider的更多參數。 一個完整的自訂ContentProvider及共用調用與事件偵聽的例子:資料結構User.java:Java代碼 1. package com.yaku.pojo; 2. 3. public class User { 4. private int id; 5. private String name; 6. private int age; 7. 8. public User(int id, String name, int age) { 9. super(); 10. this.id = id; 11. this.name = name; 12. this.age = age; 13. } 14. public int getId() { 15. return id; 16. } 17. public void setId(int id) { 18. this.id = id; 19. } 20. public String getName() { 21. return name; 22. } 23. public void setName(String name) { 24. this.name = name; 25. } 26. public int getAge() { 27. return age; 28. } 29. public void setAge(int age) { 30. this.age = age; 31. } 32. @Override 33. public String toString() { 34. return "User [age=" + age + ", id=" + id + ", name=" + name + "]"; 35. } 36. } 資料庫操作DBOpenHelper.java:Java代碼1. package com.yaku.db; 2. 3. import android.content.Context; 4. import android.database.sqlite.SQLiteDatabase; 5. import android.database.sqlite.SQLiteOpenHelper; 6. 7. public class DBOpenHelper extends SQLiteOpenHelper { 8. private static final String DBNAME = "yaku.db"; //資料庫名稱 9. private static final int DBVER = 1;//資料庫版本 10. 11. public DBOpenHelper(Context context) { 12. super(context, DBNAME, null, DBVER); 13. } 14. 15. @Override 16. public void onCreate(SQLiteDatabase db) { 17. String sql = "CREATE TABLE user (userid integer primary key autoincrement, name varchar(20), age integer)"; 18. db.execSQL(sql);//執行有更改的sql語句 19. } 20. 21. @Override 22. public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) { 23. db.execSQL("DROP TABLE IF EXISTS user"); 24. onCreate(db); 25. } 26. 27. } 對外共用處理類ContentProviderUser.java:Java代碼 1. package com.yaku.ContentProvider; 2. 3. import com.yaku.db.DBOpenHelper; 4. 5. import android.content.ContentProvider; 6. import android.content.ContentUris; 7. import android.content.ContentValues; 8. import android.content.UriMatcher; 9. import android.database.Cursor; 10. import android.database.sqlite.SQLiteDatabase; 11. import android.net.Uri; 12. 13. public class ContentProviderUser extends ContentProvider { 14. private DBOpenHelper dbOpenHelper; 15. //常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼 16. private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 17. private static final int USERS = 1; 18. private static final int USER = 2; 19. static{ 20. //如果match()方法匹配content://com.yaku.ContentProvider.userprovider/user路徑,返回匹配碼為1 21. MATCHER.addURI("com.yaku.ContentProvider.userprovider", "user", USERS); 22. //如果match()方法匹配content://com.yaku.ContentProvider.userprovider/user/123路徑,返回匹配碼為2 23. MATCHER.addURI("com.yaku.ContentProvider.userprovider", "user/#", USER);//#號為萬用字元 24. } 25. @Override 26. public int delete(Uri uri, String selection, String[] selectionArgs) { 27. SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); 28. int count = 0; 29. switch (MATCHER.match(uri)) { 30. case USERS: 31. count = db.delete("user", selection, selectionArgs); 32. return count; 33. case USER: 34. //ContentUris類用於擷取Uri路徑後面的ID部分 35. long id = ContentUris.parseId(uri); 36. String where = "userid = "+ id; 37. if(selection!=null && !"".equals(selection)){ 38. where = selection + " and " + where; 39. } 40. count = db.delete("user", where, selectionArgs); 41. return count; 42. default: 43. throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString()); 44. } 45. } 46. 47. /** 48. * 該方法用於返回當前Url所代表資料的MIME類型。 49. * 如果操作的資料屬於集合類型,那麼MIME類型字串應該以vnd.android.cursor.dir/開頭 50. * 如果要操作的資料屬於非集合類型資料,那麼MIME類型字串應該以vnd.android.cursor.item/開頭 51. */ 52. @Override 53. public String getType(Uri uri) { 54. switch (MATCHER.match(uri)) { 55. case USERS: 56. return "vnd.android.cursor.dir/user"; 57. 58. case USER: 59. return "vnd.android.cursor.item/user"; 60. 61. default: 62. throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString()); 63. } 64. } 65. 66. @Override 67. public Uri insert(Uri uri, ContentValues values) { 68. SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); 69. switch (MATCHER.match(uri)) { 70. case USERS: 71. long rowid = db.insert("user", "name", values); 72. Uri insertUri = ContentUris.withAppendedId(uri, rowid);//得到代表新增記錄的Uri 73. this.getContext().getContentResolver().notifyChange(uri, null); 74. return insertUri; 75. 76. default: 77. throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString()); 78. } 79. } 80. 81. @Override 82. public boolean onCreate() { 83. this.dbOpenHelper = new DBOpenHelper(this.getContext()); 84. return false; 85. } 86. 87. @Override 88. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 89. String sortOrder) { 90. SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); 91. switch (MATCHER.match(uri)) { 92. case USERS: 93. return db.query("user", projection, selection, selectionArgs, null, null, sortOrder); 94. case USER: 95. long id = ContentUris.parseId(uri); 96. String where = "userid = "+ id; 97. if(selection!=null && !"".equals(selection)){ 98. where = selection + " and " + where; 99. } 100. return db.query("user", projection, where, selectionArgs, null, null, sortOrder); 101. default: 102. throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString()); 103. } 104. } 105. 106. @Override 107. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 108. SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); 109. int count = 0; 110. switch (MATCHER.match(uri)) { 111. case USERS: 112. count = db.update("person", values, selection, selectionArgs); 113. return count; 114. case USER: 115. long id = ContentUris.parseId(uri); 116. String where = "userid = "+ id; 117. if(selection!=null && !"".equals(selection)){ 118. where = selection + " and " + where; 119. } 120. count = db.update("user", values, where, selectionArgs); 121. return count; 122. default: 123. throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString()); 124. } 125. } 126. } 單元測試類(在另一個應用中): Java代碼1. package com.yaku.ContentProvider; 2. 3. import android.content.ContentResolver; 4. import android.content.ContentValues; 5. import android.database.Cursor; 6. import android.net.Uri; 7. import android.test.AndroidTestCase; 8. import android.util.Log; 9. 10. /** 11. * 對ContentProvider工程中的ContentProviderActivity進行單元測試 12. */ 13. public class ContentProviderActivityTest extends AndroidTestCase { 14. private static final String TAG = "ContentProvider"; 15. //往內容提供者添加資料 16. public void testInsert() throws Throwable{ 17. ContentResolver contentResolver = this.getContext().getContentResolver(); 18. Uri insertUri = Uri.parse("content://com.yaku.ContentProvider.userprovider/user"); 19. ContentValues values = new ContentValues(); 20. values.put("name", "道長"); 21. values.put("age", 86); 22. Uri uri = contentResolver.insert(insertUri, values); 23. Log.i(TAG, uri.toString()); 24. } 25. 26. //更新內容提供者中的資料 27. public void testUpdate() throws Throwable{ 28. ContentResolver contentResolver = this.getContext().getContentResolver(); 29. Uri updateUri = Uri.parse("content://com.yaku.ContentProvider.userprovider/user/1"); 30. ContentValues values = new ContentValues(); 31. values.put("name", "青眉道長"); 32. contentResolver.update(updateUri, values, null, null); 33. } 34. 35. //從內容提供者中刪除資料 36. public void testDelete() throws Throwable{ 37. ContentResolver contentResolver = this.getContext().getContentResolver(); 38. Uri deleteUri = Uri.parse("content://com.yaku.ContentProvider.userprovider/user/1"); 39. contentResolver.delete(deleteUri, null, null); 40. } 41. 42. //擷取內容提供者中的資料 43. public void testFind() throws Throwable{ 44. ContentResolver contentResolver = this.getContext().getContentResolver(); 45. Uri selectUri = Uri.parse("content://com.yaku.ContentProvider.userprovider/user"); 46. Cursor cursor = contentResolver.query(selectUri, null, null, null, "userid desc"); 47. while(cursor.moveToNext()){ 48. int id = cursor.getInt(cursor.getColumnIndex("userid")); 49. String name = cursor.getString(cursor.getColumnIndex("name")); 50. int age = cursor.getInt(cursor.getColumnIndex("age")); 51. Log.i(TAG, "id="+ id + ",name="+ name+ ",age="+ age); 52. } 53. } 54. 55. } 監聽資料的變化:Java代碼1. <span style="font-size: medium;">package com.yaku.ContentProvider; 2. 3. import android.content.ContentResolver; 4. import android.content.ContentValues; 5. import android.database.Cursor; 6. import android.net.Uri; 7. import android.test.AndroidTestCase; 8. import android.util.Log; 9. 10. /** 11. * 監聽資料變化 12. */ 13. public class OtherContentProviderTest extends AndroidTestCase { 14. private static final String TAG = "OtherContentProvider"; 15. 16. @Override 17. public void onCreate(Bundle savedInstanceState) { 18. super.onCreate(savedInstanceState); 19. setContentView(R.layout.main); 20. 21. Uri insertUri = Uri.parse("content://com.yaku.ContentProvider.userprovider/user"); 22. ContentResolver contentResolver = this.getContentResolver(); 23. //對指定uri進行監聽,如果該uri代表的資料發生變化,就會調用PersonObserver中的onChange() 24. contentResolver.registerContentObserver(insertUri, true, new PersonObserver(new Handler())); 25. } 26. 27. private final class PersonObserver extends ContentObserver{ 28. public PersonObserver(Handler handler) { 29. super(handler); 30. } 31. 32. @Override 33. public void onChange(boolean selfChange) { 34. ContentResolver contentResolver = getContentResolver(); 35. Uri selectUri = Uri.parse("content://com.yaku.ContentProvider.userprovider/user"); 36. Cursor cursor = contentResolver.query(selectUri, null, null, null, "userid desc"); 37. while(cursor.moveToNext()){ 38. int id = cursor.getInt(cursor.getColumnIndex("userid")); 39. String name = cursor.getString(cursor.getColumnIndex("name")); 40. int age = cursor.getInt(cursor.getColumnIndex("age")); 41. Log.i(TAG, "id="+ id + ",name="+ name+ ",age="+ age); 42. } 43. } 44. } 45. } 《完》