星期四, 8月 28, 2014

Content Provider Basics (二)


Displaying query results

ContentResolver.query()客戶端方法始終回傳一個Cursor,cursor包含符合查詢條件的行和查詢中projection指定的欄位,Cursor物件提供隨機讀取它包含的行和欄位。使用Cursor方法,你可以遍歷在查詢結果中的各行、決定每個欄位的資料型態、從欄位中取值出來及檢查查詢結果的其他特性。一些Cursor在provider的資料改變時會自動地更新物件,或在Cursor改變時觸發一些方法,或兩者都會。

如果沒有行符合selection條件,provider回傳Cursor物件而Cursor.getCount()為0(一個空的Cursor)。

如果內部錯誤發生,查詢結果會根據特別的provider,它可能會選擇回傳null,或是丟出Exception。

因為一個Cursor是一個行列表,顯示Cursor內容的一個好方式是透過SimpleCursorAdapter將Cursor連結到ListView。

以下片段接續了上面的片段,建立SimpleCursorAdapter物件包含藉由查詢描述擷取出的Cursor,接著設定此物件為ListView的Adapter。

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};
// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};
// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)
// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);
注意 -回到有Cursor的ListView,cursor必須包含名稱為_ID的欄位。因為如此,查詢描述顯示擷取"words"資料表的_ID欄位,儘管如此,ListView不會顯示它。此限制也說明了為什麼大部分的provider對於每個資料表都有_ID欄位了。


Getting data from query results

比起簡單地顯示查詢結果,妳可能會使用它們完成其他任務。例如,你可以從user dictionary擷取拼法,接著在其他provider中尋找它們。要完成這項功能,你要遍歷Cursor中的所有行。

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */
if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}
Cursor實現包含許多"get"方法擷取來自物件不同資料型態的資料。例如,前面的片段使用getString()。它們也有getType()方法,回傳欄位的資料型態。


Content Provider Permissions

一個provider的應用程式可以指定權限讓其他應用程式要存取provider資料必須有該權限,這些權限確保了使用者知道應用程式將會試著存取什麼樣的資料。根據provider的需求,其他應用程式為了存取provider資料,請求它們需要的權限。終端使用者在安裝應用程式時會看到請求的權限。

取得存取provider所需的權限,應用程式在它的manifest文件中用<uses-permission>標籤請求。

以下為請求讀取User Dictionary Provider權限

 <uses-permission android:name="android.permission.READ_USER_DICTIONARY">


Inserting, Updating, and Deleting Data

如同我們從provider擷取資料出來的方法,你也可以使用provider客戶端與provider的ContentProvider間的互動修改資料。你以參數呼叫ContentResolver方法,參數會被傳遞到相對應的ContentProvider方法。provider和provider客戶端會自動地處理安全性與處理程序間的溝通。


Inserting data

將資料插入provider,呼叫ContentResolver.insert()方法。此方法插入新的行到provider並回傳該行的content Uri。以下片段插入新的單字到User Dictionary Provider。

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;
...
// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();
/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);
新行的資料進入一個單一ContentValue物件,它格式有點類似包含一行的cursor。此物件中的欄位不需要有相同的資料型態,且如果不想要指定數值,你可以使用ContentValue.putNull()設定欄位為null。

不需要加入_ID欄位,因為此欄位是自動維護的。provider會分配給被加入的每一行一個_ID唯一值。provider通常使用此值作為資料表的主鍵。

回傳到mNewValues的content Uri用來辨識新加入的行,如以下格式

content://user_dictionary/words/<id_value>
<id_value>為新行的_ID值,大部分的provider能自動地偵測此格式的content Uri,然後執行在特定行上的操作。

從回傳的Uri中取得_ID值,呼叫ContentUris,parseId()。


Updating data

 更新一個row,你可以使用ContentValues物件與更新資料一起,就如同在插入資料時所做的一樣,selection條件也和在query時一樣。在客戶端使用的方法是ContentResolver.update(),只需要針對欲更新欄位,加數值到ContentValues物件。如果想要清除欄位的內容,把數值設為null。

以下片段將所有的row它的locale為"en"改變為null,回傳值為有改變的row個數。

// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();
// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};
// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);
當呼叫ContentResolver.update()時,也應該將使用者輸入做消毒,就像在Protecting against malicious input。


Deleting data

刪除rows類似於擷取row資料,針對想刪除的row指定selection條件,且客戶端方法回傳被刪除的row的號碼。以下片段為刪除appid符合"user"的row,方法回傳被刪除的row的號碼。

// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};
// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;
...
// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);
當呼叫ContentResolver.delete()時,也應該將使用者輸入做消毒,就像在Protecting against malicious input。


Provider Data Types

Content providers提供許多不同的資料型態。User Dictionary Provider只有文字型態,但provider可以提供以下型態

  • integer
  • long integer (long)
  • floating point
  • long floating point (double)
provider常使用的它資料型態是Binary Large OBject (BLOB)被實作為64KB的byte array。可用的資料型態可到Cursor類別看看"get"方法。

一個provider中,每個欄位的資料型態通常被列在它的文件中。

provider也為每個content URI維護MIME型態資訊。


Alternative Forms of Provider Access

應用程式發展過程中,以下三種provider存取的替代形式是重要的:
  • Batch access - 你可以在ContentProviderOperation類別中建立呼叫存取方法的批次處理,接著運用在ContentResolver.applyBatch()。
  • Asynchronous queries - 應該獨立的執行緒中執行查詢,一種達成方式是使用CursorLoader物件。
  • Data access vis intents - 雖然你無法直接地傳送一個intent,你可以傳送intent到provider的應用程式,應用程式有最佳的條件去修改provider資料。


沒有留言:

張貼留言