星期六, 8月 30, 2014

Creating a Content Provider (一)

Before You Start Building

在開始建立provider之前,有以下幾件事要做。

  1. 決定是否需要一個content provider。如果你需要提供一個或多個以下的議題,你就需要建立一個content provider。
    • 想提供複雜的資料或檔案給其他應用程式。
    • 想允許從你的應用程式複製複雜資料到其他應用程式。
    • 想利用搜尋框架提供客製化搜尋。
  2. 如果你還沒準備好,請先看Content Provider Basics。
接下來,跟隨以下這些步驟建立provider。
  1. 對於你的資料設計raw storage。Content provider有兩種方式提供資料。
    1. File data
      資料通常進入檔案,如照片、視訊、音樂,儲存這些檔案在應用程式私有的空間。回應其它應用程式對於檔案的請求,你的provider可以提供檔案的處理。
    2. "Structured" data
      資料通常進入資料庫、陣列或類似的結構。資料的儲存格式是行和列的資料表,行表示值組,如一個人或一個項目,列表示對於值組的一些資料,如人的姓名或項目的價錢。一般儲存此型態的資料的方式是在SQLite資料庫,但是你也可以使用任何型態的永久性儲存,詳細說明在Designing Data Storage。
  2. 定義實際的ContentProvider類別和它需要的方法的實作。該類別是你的資料和Android系統間的介面。
  3. 定義provider的authority字串、content URI、列名稱。如果你想要provider的應用程式能處理intent,也要定義intent action、extras data、flags。也要定義想存取你的資料的應用程式將請求的權限。你應該考慮定義這些所有數值為常數,放在獨立的contract類別,往後,你可以顯示該類別給其他開發者。
  4. 加入其他可選部分。例如樣品資料或是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,以其指出個別的資料表。例如,假如你有兩個資料表table1table2,由先前的例子結合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。

沒有留言:

張貼留言