建立一個Content Provider
content provider管理對中央資料倉儲的存取。你實現一個provider,就是在一個Android應用中實現一個或多個類,再加上manifest檔案中的一些元素。你實現一個 ContentProvider的子類,它作為你的provider和其它應也之間的介面。儘管content providers的目的是向其它應用提供資料,但當然也可以在你自己的應用中建立activity來允許使用者來查詢和修改你的provider所管理的資料。
建立之前的準備工作
在建立一個provider之前,需做以下工作:
1. 確定你是否需要一個content provider。你如果需要提供一個或多個下列特性,你就需建立一個content provider:
你想向其它應用提供複雜資料或檔案。
你想讓用護從你的應用複製複雜的資料到其它應用。
你想使用搜尋方塊架提供自訂的搜尋建議。
如果完全是在你的應用內部使用,你的provider不需使用 SQLite資料庫。
2. 如果你還未作出決定,請閱讀主題 Content Provider Basics 來進一步瞭解provider。
下一步,按以下步驟來建立你的provider:
1. 為你的資料設計原始儲存方式。一個content provider以兩種方式提供資料:
檔案資料
指那儲存在檔案中的資料,比片,視頻,音頻等。把檔案們儲存在你的應用的私人空間。為響應從其它應用發來的對某個檔案的請求,你的provider可以為檔案提供一個控制代碼。
"結構化" 資料
指那些儲存於資料庫中的資料、數組或小型結構。資料以相容於表的形式儲存。一行代表一條資料,就像一個人員或條目清單中的一條。一列代表一條資料中的一部分資料,比如人的名字或條目的價格。儲存這些類型的資料的一個常用方法是使用SQLite資料庫,但是你也可以使用其它形式。要瞭解android 系統中更多的儲存方式,見 設計資料存放區一節。
2. 具體定義ContentProvider 類和它的方法。此類是你的資料與Android系統中其它東西的介面。要進一步瞭解此類,見實現ContentProvider類 一節。
3. 定義provider的authority字串,content URIs,和列的名字。如果你想讓provider的應用處理intent,還要定義intent 的action、附加資料和標誌。還要定義對那些要訪問你的資料的應用所需具有的許可權。你應該考慮把這些值作為契約定義到另外一個單獨的契約類中。以後就可以向其它的開發人員展示這個類。更多關於content URI的資訊,見 設計Content URI.一節。更多關於intent的資訊,見Intent和資料操作一節。
4. 添加其它可選內容,比如樣本資料或實現 AbstractThreadedSyncAdapter 以實現provider和雲端式的資料之間的資料同步。
定義資料存放區
一個content provider是一個結構化儲存的資料介面。在你建立這個介面之前,你必須確定如如何儲存資料。你可以以任何你喜歡的方式儲存資料,然後設計讀寫資料的介面。
下面是一些在android中可用的資料存放區技術:
1 Android系統中包含一個 SQLite資料庫API,Android自己的provider使用它來儲存表格類的資料。SQLiteOpenHelper類協助你建立資料庫,而類SQLiteDatabase是操作資料庫的基礎類。
記住你不是必須使用一個資料庫來實現你的資料倉儲。一個 provider在外部的表現就像一個表的集合,類似於一個關係型資料庫,但是provider的內部實現是可以與此不同的。
2 對於資料存放區,Android具有各種各樣的面向檔案的API。要更多的瞭解檔案儲存體,請閱讀主題資料存放區。如果你 設計的provider要提供媒體相關的資料,比如音頻和視頻,你可以在provider中將表資料和檔案混合來使用。
3 要處理網路資料,應使用java.net 和 android.net中的類。你也可以同步網路資料到本機資料儲存中(比如資料庫中),然後以表或檔案的形式提供此資料。例子Sample Sync Adapter 示範了這種同步技術。
思考資料設計
下面是一些設計你的provider的資料結構的技巧:
1 表資料應總是具有一個"主鍵"列,它被用於管理一個代表每各行的唯一數值。你可以使用這個值連結某行到其它表中的相關行 (也就是"外鍵")。儘管你可以為此列取任何名字,但使用BaseColumns._ID 才是最佳選擇,因為當把一個provider的查詢結果關聯到一個 ListView 時,需要有一個列叫做 _ID。
2 如果你想提供位元影像映像或其它的非常大的面向檔案的資料,那麼應把它存於檔案中,然後間接的提供它,而不是直接把資料存放區於一個表中。如果你這樣做了,你還需要告訴你的provider的使用者,他們需要使用一個ContentResolver 的檔案方法來操作資料。
3用大二進位對象 (BLOB)資料類型來儲存大小不定或沒有固定結構的資料。例如,你可以使用一個 BLOB列來儲存 協議緩衝 或 JSON 結構。
你也可以使用一個BLOB來實現一個獨立模式的表。在此種表中,你定義一個主鍵列,一個MIME類型列,和一個或多個普通的BLOB列。在 BLOB 列中的資料的意義由MIME列中的值所表明。這使你可以在一個表的各行中儲存不同類型的資料。連絡人Provider的"data"表ContactsContract.Data ,就是一個獨立模式表的例子。
設計內容URIs
一個內容URI 標誌一個provider中的資料。內容URI包含了整個provider (他的 authority)的符號名和一個指向某個表或檔案的名字。可選的id部分指向表中的一個獨立的行。ContentProvider 的每個資料操作方法具有一個內容URI作為一個參數;這允許你決定要操作的表,行或檔案。
內容URI的基礎在標題Content Provider基礎 中被描述。
設計一個authority
一個provider通常具有單個authority,這作為它的安卓內部名字。為了避免與其它provider衝突,你應該使用互連網網域名稱方式取名 (不過是倒著的)來作為你的provider authority的基礎名字。因為此種建議也用於Android包的名字,所以你可以定義你的provider authority作為包含此provider的包名的擴充。 例如,如果你的Android包名是 com.example.<appname>,你應為你的provider authority命名為 com.example.<appname>.provider。
定義一個路徑結構
開發人員通常通過添加指向獨立表的路徑來從authority 建立content URI。例如,如果你具有兩個表 table1和table2,你從前面例子中的authority 合并出來的內容 URI為 com.example.<appname>.provider/table1 和com.example.<appname>.provider/table2。路徑中並不限制只有一個參數,並且路徑的每一層也不是必須是一個表。
處理內容URI的 ID
為了方便,provider接受把一行的ID放在URI的尾部來提供對錶中某一行的訪問。同樣為了方便,provider比較此 ID和表的_ID列,然後執行對匹配行的操作請求。.
這種方便性有助於在應用操作一個provider時使用一種通用的設計模式。應用向provider發出查詢然後在一個ListView 中使用一個CursorAdapter 顯示結果 Cursor 。CursorAdapter 的定義需要Cursor 中有一個列叫做 _ID 。
使用者之後就可以在UI中顯示的行中選擇一行進行查看或修改資料。app之後從ListView 背後的Cursor 中獲得相應的行,再獲得行的 _ID值,把此值添加到內容URI上,再把操作請求發送給provider。provider之後就執行查詢或修改使用者所指定的行。
內容URI的模式
為了幫你跟據到達的內容URI 選擇要執行的動作,provider API包含了簡便的類UriMatcher,它映射內容URI 的"模式" 到一個整數值。你可以在一個switch 語句中使用這個整數值來選擇為匹配一種模式的某個內容URI或多個URI們執行某種動作。
一個內容 URI使用萬用字元來匹配其它內容URI們:
*: 匹配任意長度的任意有效字串。
#: 匹配由數字組成的任意長度的字串。
看一個設計和編碼處理content URI的例子,假設一個provider其authority是 com.example.app.provider ,從它可以識別以下指向各表的內容URI:
1content://com.example.app.provider/table1: 一個叫table1 的表。
2content://com.example.app.provider/table2/dataset1: 一個叫dataset1 的表。
3content://com.example.app.provider/table2/dataset2: 一個叫dataset2 的表。
4content://com.example.app.provider/table3: 一個叫做table3 的表。
provider也識別那些具有一行的ID的內容URI,例如 content://com.example.app.provider/table3/1 指向表table3中的行1.
下面的內容 URI 模式也可能出現:
content://com.example.app.provider/*
匹配provider 中的任務內容URI。
content://com.example.app.provider/table2/*:
匹配表dataset1和表dataset2中的一個內容URI,但是不匹配table1或table3中的內容URI。
content://com.example.app.provider/table3/#: 匹配表 table3 中的任意一行,比如content://com.example.app.provider/table3/6 指向行6.
下面的程式碼片段示範了UriMatcher 中的方法們如何工作。此代碼通過使用內容URI模式content://<authority>/<path> 指向表,用content://<authority>/<path>/<id> 指向一個單獨行,以不同的方式來處理指向整個表的 URI和指向單行的URI 。
方法addURI() 映射一個authority和路徑到一個整數值。方法android.content.UriMatcher#match(Uri) match()} 返回一個URI對應的整數值。一個switch 語句從查詢整個表和查詢單條記錄之間作出選擇:
public class ExampleProvider extends ContentProvider {... // 建立一個UriMatcher對象 private static final UriMatcher sUriMatcher;... /* * 到這裡,是對addURI()的調用。provider應能識別所有的內容URI的模式。 * 但在此片段中,只示範對錶3的調用。 */... /* * 映射表3中的多行模式為1 。注意路徑中沒有用萬用字元。 */ sUriMatcher.addURI("com.example.app.provider", "table3", 1); /* * 映射單行模式為2 。 此例中,萬用字元"#"被使也。 * "content://com.example.app.provider/table3/3" 能匹配此模式,但是 * "content://com.example.app.provider/table3就不能了。 */ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);... // 實現ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {... /* * 選擇一個要查詢的表並跟據為輸入URI返回的代碼來選擇排序次序。 * 這裡也是只示範表3 。 */ switch (sUriMatcher.match(uri)) { // 如果輸入URI指向整個表3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // 如果輸入URI指向單行 case 2: /* * 因為這個URI指向單行,所以_ID部分就出現了。 * 從URI擷取最後最後面的路徑段;它就是_ID值。 * 然後,添加這個值到查詢語句的WHERE子語句中。 */ selection = selection + "_ID = " uri.getLastPathSegment(); break; default: ... // 如果URI不能被識別,你應該在此處做一些錯誤處理。 } // 調用進行實際查詢的代碼 }
另一個類,ContentUris, 提供處理內容URI中的id部分的簡便方法。類Uri 和 Uri.Builder 包含了分析已存在的Uri 對象和建立甌新對象的簡便方法。