在開始建立provider之前,有以下幾件事要做。
- 決定是否需要一個content provider。如果你需要提供一個或多個以下的議題,你就需要建立一個content provider。
- 想提供複雜的資料或檔案給其他應用程式。
- 想允許從你的應用程式複製複雜資料到其他應用程式。
- 想利用搜尋框架提供客製化搜尋。
- 如果你還沒準備好,請先看Content Provider Basics。
接下來,跟隨以下這些步驟建立provider。
- 對於你的資料設計raw storage。Content provider有兩種方式提供資料。
- File data
資料通常進入檔案,如照片、視訊、音樂,儲存這些檔案在應用程式私有的空間。回應其它應用程式對於檔案的請求,你的provider可以提供檔案的處理。 - "Structured" data
資料通常進入資料庫、陣列或類似的結構。資料的儲存格式是行和列的資料表,行表示值組,如一個人或一個項目,列表示對於值組的一些資料,如人的姓名或項目的價錢。一般儲存此型態的資料的方式是在SQLite資料庫,但是你也可以使用任何型態的永久性儲存,詳細說明在Designing Data Storage。 - 定義實際的ContentProvider類別和它需要的方法的實作。該類別是你的資料和Android系統間的介面。
- 定義provider的authority字串、content URI、列名稱。如果你想要provider的應用程式能處理intent,也要定義intent action、extras data、flags。也要定義想存取你的資料的應用程式將請求的權限。你應該考慮定義這些所有數值為常數,放在獨立的contract類別,往後,你可以顯示該類別給其他開發者。
- 加入其他可選部分。例如樣品資料或是AbstractThreadedSyncAdapter的實作,AbstractThreadedSyncAdapter實作可以在provider與雲端資料之間同步資料。
Designing Data Storage
Content provider是資料儲存結構化模式的介面。建立介面之前,必須決定如何儲存資料。可以儲存成任何你喜歡的模式,然後再設計讀取和寫入資料所必要的介面。
以下是在Android下可用的資料儲存技術
- Android系統包含了SQLite資料庫API。SQLiteOpenHelper類別幫助你建立資料庫,且SQLiteDatabase類別是存取資料的基本類別。
- 對於儲存成檔案的資料,Android提供多種檔案導向的API。如果你正在設計提供多媒體相關資料如音樂視訊的provider,你可以結合資料表和檔案。
- 對於以網路為基礎的資料的運作,使用java.net及android.net中的類別。你也可以同步network-based資料到本地資料儲存如資料庫,然後提供資料表或檔案,
Data design considerations
以下有一些設計provider的資料結構的訣竅
- 資料表資料應該始終有一個"主鍵"欄位,用來讓provider維護每行。也可以使用此欄位row與其他資料表的相關的row連結(如使用外鍵一樣)。雖然主鍵可以用任何名字命名,使用BaseColumns._ID還是最佳的選擇,因為連結查詢結果到ListView需要有欄位名稱為_ID。
- 如果想要提供Bitmap圖片或其他非常大的檔案導向資料,應將資料儲存在檔案然後鍵接的提供它,而不是直接地儲存在資料表中。如果如此做的話,應該告訴你的provider的使用者,他們需使用ContentResolver檔案方法來存取資料。
- 使用Binary Large OBject(BLOB)資料型態,它有多種大小和不同的結構。例如,可以使用BLOB欄位儲存protocol buffer或JSON Structure。你也可以使用BLOB實現綱目獨立(schema independent)的資料表。在這種資料表中,你定義了一個關鍵欄位、一個MIME型態欄位及一個或多個通用欄位做為BLOB,意味著,在BLOB欄位中的資料是由在MIME型態的欄位的值表示的。Contacts Provider的"data"ContactsProvider.Data是schema-independent資料表的例子。
Designing Content URIs
content URI是一種URI用來辨識provider中的資料,Content URI包含整個provider的名稱(它的authority),只到的檔案或資料表的名稱(path),可選的id部分指出在資料表中個別row。ContentProvider的每一個資料存取的方法都有一個content URI作為參數,此允許你決定存取table row或檔案。
Designing an authority
一個provider通常有一個個別的authority,它作為Android內部的名稱。避免與其他provider衝突,你應該使用網路域名所有權作為你的authority的基礎,因為這與Android package名稱的建議一樣,你可以將你的provider authority定義為包含provider的package的名稱的延伸。例如,你的Android package名稱為com.example.<appname>,可以給你的provider authority為com.example.<appname>.provider。
Designing a path structure
開發人員通常藉由從authority附加路徑建立content URI,以其指出個別的資料表。例如,假如你有兩個資料表table1和table2,由先前的例子結合authority產生content URIs為com.example.<appnmae>.provider/table1及con.example.<appname>.provider.table2。
Handling content URI IDs
按照慣例,provider藉由在content URI末端附加上每個row的ID值,提供存取資料表中的個別row。provider的ID值與資料表的_ID欄位相配,並執行被請求的存取。
為應用程式存取provider,此慣例有助於一般設計樣式。應用程式針對provider查詢,並利用CursorAdapter將查詢結果Cursor顯示在ListView,CursorAdapter的定義是在Cursor中需要有一個欄位為_ID。
接著使用者從使用者介面中挑選被顯示的row,查看或修改資料。應用程式從支持ListView的Cursor取得相對應的row,取得此row的_ID值,附加到content URI並送出存取provider的請求。provider接著查詢或修改使用者挑出來的row。
Content URI patterns
幫助你針對傳入的content URI選擇採取動作,provider API包含了便利的類別UriMatcher,UriMatcher可以將content Uri樣式成整數值。你可以在switch描述中使用整數值,採取預期的動作。
content URI pattern匹配content URIs
Designing an authority
一個provider通常有一個個別的authority,它作為Android內部的名稱。避免與其他provider衝突,你應該使用網路域名所有權作為你的authority的基礎,因為這與Android package名稱的建議一樣,你可以將你的provider authority定義為包含provider的package的名稱的延伸。例如,你的Android package名稱為com.example.<appname>,可以給你的provider authority為com.example.<appname>.provider。
Designing a path structure
開發人員通常藉由從authority附加路徑建立content URI,以其指出個別的資料表。例如,假如你有兩個資料表table1和table2,由先前的例子結合authority產生content URIs為com.example.<appnmae>.provider/table1及con.example.<appname>.provider.table2。
Handling content URI IDs
按照慣例,provider藉由在content URI末端附加上每個row的ID值,提供存取資料表中的個別row。provider的ID值與資料表的_ID欄位相配,並執行被請求的存取。
為應用程式存取provider,此慣例有助於一般設計樣式。應用程式針對provider查詢,並利用CursorAdapter將查詢結果Cursor顯示在ListView,CursorAdapter的定義是在Cursor中需要有一個欄位為_ID。
接著使用者從使用者介面中挑選被顯示的row,查看或修改資料。應用程式從支持ListView的Cursor取得相對應的row,取得此row的_ID值,附加到content URI並送出存取provider的請求。provider接著查詢或修改使用者挑出來的row。
Content URI patterns
幫助你針對傳入的content URI選擇採取動作,provider API包含了便利的類別UriMatcher,UriMatcher可以將content Uri樣式成整數值。你可以在switch描述中使用整數值,採取預期的動作。
content URI pattern匹配content URIs
- * :Matches a string of any valid characters of any length
- # :Matches a string of numeric characters of any length
以設計及編碼content URI的處理作為例子,考慮有authority的provider-com.example.app.provider,用它來辨認以下的content URI以指出資料表。
- content://com.example.app.provider/table1:名稱為table1的資料表。
- content://com.example.app.provider/table2/dataset1:名稱為dataset1的資料表。
- content://com.example.app.provider/table2/dataset2:名稱為dataset2的資料表。
- content://com.example.app.provider/atble3:名稱為table3的資料表。
如果這些content URI有row id附加在它們上,provider也能辨認。例如,com.example.app.provider/table3/1被辨認為table3的id為1的row。
以下content URI patterns也是:
content://com.example.app.provider/* - 匹配在provider中的任何content URI。
content://com.example.app.provider/table2/* - 匹配在資料表dataset1及dataset2中的content URI,但不匹配在資料表table1及table3中的content URI。
content://com.example.app.provider/table3/# - 匹配在資料表table3中的單一row的content URI,例如,content://com.example.app.provider/table3/6為由6確定row。
以下程式片段顯示在UriMatcher中的方法如何運作,這程式處理對於整個資料表的content URI不同於處理資料表中單一row的content URI。藉由使用content URI patttern針對資料表的content://<authority>/<path>以及針對單一row的content://<authority>/<path>/<id>。
addURI()方法是用來將authority和path對應到整數數值,match()方法回傳對應到一個Uri其整數數值。
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher sUriMatcher; ... /* * The calls to addURI() go here, for all of the content URI patterns that the provider * should recognize. For this snippet, only the calls for table 3 are shown. */ ... /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path */ sUriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the "#" wildcard is * used. "content://com.example.app.provider/table3/3" matches, but * "content://com.example.app.provider/table3 doesn't. */ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (sUriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query */ selection = selection + "_ID = " uri.getLastPathSegment(); break; default: ... // If the URI is not recognized, you should do some error handling here. } // call the code to actually do the query }
其他類別如contentUris,它提供了便利的方法,用來處理content URI的id部分。類別Uri及Uri.Builder,它提供便利方法,用來處理解析已存在的Uri物件及建立新的Uri。