Android Content Provider詳解
Android中的Contentprovider機制可支持在多個應用中存儲和讀取數據。這也是跨應用共享數據的唯一方式。在android系統中,沒有一個公共的內存區域,供多個應用共享存儲數據。
Android提供了一些主要數據類型的ContentProvider,比如音頻、視頻、圖片和私人通訊錄等。可在android.provider包下面找到一些android提供的Contentprovider。可以獲得這些Contentprovider,查詢它們包含的數據,當然前提是已獲得適當的讀取權限。
如果想公開自己的數據,那么可有兩種辦法:
創建自己的Contentprovider,需要繼承ContentProvider類; 如果你的數據和已存在的Contentprovider數據結構一致,可以將數據寫到已存在的Contentprovider中,當然前提是獲取寫該Contentprovider的權限。比如把OA中的成員通訊信息加入到系統的聯系人Contentprovider中。
所有Contentprovider都需要實現相同的接口用于查詢Contentprovider并返回數據,也包括增加、修改和刪除數據。
首先需要獲得一個ContentResolver的實例,可通過Activity的成員方法getContentResovler()方法:
- ContentResolver cr = getContentResolver();
ContentResolver實例帶的方法可實現找到指定的Contentprovider并獲取到Contentprovider的數據。
ContentResolver的查詢過程開始,Android系統將確定查詢所需的具體Contentprovider,確認它是否啟動并運行它。android系統負責初始化所有的Contentprovider,不需要用戶自己去創建。實際上,contentprovider的用戶都不可能直接訪問到contentprovider實例,只能通過ContentResolver在中間代理。
數據模型
Contentprovider展示數據類似一個單個數據庫表。其中:
每行有個帶唯一值的數字字段,名為_ID,可用于對表中指定記錄的定位;Contentprovider返回的數據結構,是類似JDBC的ResultSet,在android中,是Cursor對象。 URI
每個contentprovider定義一個唯一的公開的URI,用于指定到它的數據集。一個contentprovider可以包含多個數據集(可以看作多張表),這樣,就需要有多個URI與每個數據集對應。這些URI要以這樣的格式開頭:
content://
表示這個uri指定一個contentprovider。
如果你想創建自己的contentprovider,***把自定義的URI設置為類的常量,這樣簡化別人的調用,并且以后如果更新URI也很容易。android定義了CONTENT_URI常量用于URI,比如:
android.provider.Contacts.Phones.CONTENT_URI android.provider.Contacts.Photos.CONTENT_URI
要注意的是上面例子中的Contacts,已經在android 2.0及以上版本不贊成使用。
查詢Contentprovider
要想使用一個contentprovider,需要以下信息:
定義這個contentprovider的URI 返回結果的字段名稱 這些字段的數據類型
如果需要查詢contentprovider數據集的特定記錄(行),還需要知道該記錄的ID的值。
構建查詢
查詢就是輸入URI等參數,其中URI是必須的,其他是可選的,如果系統能找到URI對應的contentprovider將返回一個Cursor對象。
可以通過ContentResolver.query()或者Activity.managedQuery()方法。兩者的方法參數完全一樣,查詢過程和返 回值也是相同的。區別是,通過Activity.managedQuery()方法,不但獲取到Cursor對象,而且能夠管理Cursor對象的生命周 期,比如當Activity暫停(pause)的時候,卸載該Cursor對象,當Activity restart的時候重新查詢。另外,也可以對一個沒有處于Activity管理的Cursor對象做成被Activity管理的,通過調用 Activity.startManaginCursor()方法。
類似這樣:
- Cursor cur = managedQuery(myPerson, null, null, null, null);
其中***個參數myPerson是Uri類型實例。
如果需要查詢的是指定行的記錄,需要用_ID值,比如ID值為23,URI將是類似:
content://. . . ./23
android提供了方便的方法,讓開發者不需要自己拼接上面這樣的URI,比如類似:
- Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
或者:
- Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
二者的區別是一個接收整數類型的ID值,一個接收字符串類型。
其他幾個參數:
names,可以為null,表示取數據集的全部列,或者聲明一個String數組,數組中存放列名稱,比如:People._ID。一般列名都在該Contentprovider中有常量對應; 針對返回結果的過濾器,格式類似于SQL中的WHERE子句,區別是不帶WHERE關鍵字,如果返回null表示不過濾,比如name=?; 前面過濾器的參數,是String數組,是針對前面條件中?占位符的值; 排序參數,類似SQL的ORDER BY字句,不過不需要寫ORDER BY部分,比如name desc,如果不排序,可輸入null。
返回值是Cursor對象,游標位置在***條記錄之前。
下面實例適用于android 2.0及以上版本,從android通訊錄中得到姓名字段:
- Cursor cursor = getContentResolver().query(
- ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null,null,null);
返回值的內容
返回值的內容類似上圖,不同的contentprovider會有不同的列和名稱,但是會有兩個相同的列,上面提到過的一個是_ID,用于唯一標識記錄,還有一個_COUNT,用于記錄整個結果集的大小,可以看到上面圖中的_COUNT的值是相同的。
讀取返回的數據
如 果在查詢的時候使用到ID,那么返回的數據只有一條記錄。在其他情況下,一般會有多條記錄。和JDBC的ResultSet類似,需要操作游標遍歷結果 集,在每行,再通過列名獲取到列的值,可以通過getString()、getInt()、getFloat()等方法獲取值。比如類似下面:
- while (cursor.moveToNext()) {
- builder
- .append(
- cursor
- .getString(cursor
- .getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)))
- .append("-");
- }
和JDBC中不同,沒有直接通過列名獲取列值的方法,只能先列名獲取到列的整型索引值,然后再通過該索引值定位獲取列的值。
編輯數據
可以通過contentprovider實現以下編輯功能:
增加新的記錄; 在已經存在的記錄中增加新的值; 批量更新已經存在的多個記錄; 刪除記錄。
所有的編輯功能都是通過ContentResolver的方法實現。一些Contentprovider對權限要求更嚴格一些,需要寫的權限,如果沒有會報錯。
增加記錄
要想增加記錄到contentprovider,首先,要在ContentValues對象中設置類似map的鍵值對,在這里,鍵的值對應contentprovider中的列的名字,鍵值對的值,是對應列希望的類型。然后,調用ContentResolver.insert()方法,傳入這個ContentValues對象,和對應Contentprovider的URI即可。返回值是這個新記錄的URI對象。這樣你可以通過這個URI獲得包含這條記錄的Cursor對象。比如:
- ContentValues values = new ContentValues();
- values.put(People.NAME, "Abraham Lincoln");
- Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
在原有記錄上增加值
如果記錄已經存在,可在記錄上增加新的值,或者編輯已經存在的值。
首先要過去到原來的值對象,然后要清除原有的值,然后像上面增加記錄一樣即可:
- Uri uri=Uri.withAppendedPath(People.CONTENT_URI, "23");
- Uri 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);
批量更新值
批量更新一組記錄的值,比如NY改名為Eew York。可調用ContenResolver.update()方法。
刪除記錄
如果是刪除單個記錄,調用ContentResolver.delete()方法,URI參數,指定到具體行即可。
如果是刪除多個記錄,調用ContentResolver.delete()方法,URI參數指定Contentprovider即可,并帶一個類似SQL的WHERE子句條件。這里和上面類似,不帶WHERE關鍵字。
創建自己的Contentprovider
創建contentprovider,需要:
設置存儲系統。大多數contentprovider使 用文件或者SQLite數據庫,不過你可以用任何方式存儲數據。android提供SQLiteoOpenHelper幫助開發者創建和管理 SQLiteDatabase。 繼承ContentProvider,提供對數據的訪問。 在manifest文件中聲明contentprovider。 繼承ContentProvider類
必須定義ContentProvider類的子類,需要實現如下方法:
query() insert() update() delete() getType() onCreate()
query() 方法,返回值是Cursor實例,用于迭代請求的數據。Cursor是一個接口。android為該接口提供了一些只讀的(和JDBC的 ResultSet不一樣,后者還提供可寫入的可選特性)Cursor實現。比如SQLiteCursor,可迭代SQLite數據庫中的數據。可以通過 SQLiteDatabase類的query()方法獲取到該Cursor實例。還有其他的Cursor實現,比如MatrixCursor,用于數據不 是存儲在數據庫的情況下。
因為Contentprovider可能被多個ContentResolver對象在不同的進程和線程中調用,因此實現Contentprovider必須考慮線程安全問題。
作為良好的習慣,在實現編輯數據的代碼中,要調用ContentResolver.notifyChange()方法,通知那些監聽數據變化的監聽器。
在實現子類的時候,還有一些步驟可以簡化Contentprovider客戶端的使用:
定義public static final Uri常量,名稱為CONTENT_URI:
- public static final UriCONTENT_URI =
- Uri.parse("content://com.example.codelab.transportationprovider");
如果有多個表,它們也是使用相同的CONTENT_URI,只是它們的路徑部分不同。
也就是說紅色框部分是一致的。
定義返回的列名,public static final,列名的值,比如使用SQLite數據庫作為存儲,對應表的列名。
在文檔中要寫出各個列的數據類型,便于使用者讀取。
如果需要處理新的MIME數據類型,比如通過Intent的方式,并且帶data的mimeType,那么需要在ContentProvider.getType()方法中進行處理,參見編寫完整的Contentprovider示例編寫一個getType方法部分。
如果處理數據庫表中超大的數據,比如很大的位圖文件,一般存在文件系統中,可以參照在contentprovider中使用大型二進制文件,這樣第三方的contentprovider使用者,可以訪問不屬于它權限的文件,通過contentprovider做代理。
聲明ContentProvider
創建ContentProvider后,需要在manifest文件中聲明,android系統才能知道它,當其他應用需要調用該ContentProvider時才能創建或者調用它。
語法類似:
- <provider android:name="com.easymorse.cp.MyContentProvider"
- android:authorities="com.easymorse.cp.mycp"></provider>
android:name要寫ContentProvider繼承類的全名。
android:authorities要寫和CONTENT_URI常量的B部分(見上面圖)。
注意不要把上圖C和D部分加到authorities中去。authorities是用來識別ContentProvider的,C和D部分實際上是ContentProvider內部使用的。