首頁/ 汽車/ 正文

Android 開發 | API 指南- Content Provider 應用程式的使用方法

概述

Content Provider 以資料表的形式向外部應用程式提供資料,這與關係型資料庫中的表很類似。 其中,行(row)表示由多個不同型別資料構成的單個實體,每行資料中的列(column)代表實體中的一個數據項。

例如,使用者詞典就是 Android 系統內建的 Provider 之一,裡面記錄著使用者需要留存的自定義拼寫規則的單詞。 表1例舉了此 Provider 資料表中可以查詢的欄位資訊:

表1:

使用者詞典表舉例

word

app id

frequency

locale

_ID

mapreduce

user1

100

en_US

1

precompiler

user14

200

fr_FR

2

applet

user2

225

fr_CA

3

const

user1

255

pt_BR

4

int

user5

100

en_UK

5

在表1中,每行代表一個可能無法在標準詞典中查到的單詞。 每列代表與單詞相關的資料,比如首次使用時的地區(語言)。 每列的標題即為儲存時的列名稱。 引用

locale

列就可以得到每一行資料的地區資訊。 這裡的

_ID

列被用作“主鍵”(primary key),並且是由 Provider 自動維護的。

注意:

Provider 本身不需要用到主鍵,主鍵的名稱也不一定要是

_ID

。 但是,如果要把 Provider 作為資料來源與

ListView

繫結,則必須有一個列的名稱是

_ID

。 詳細要求將在 顯示查詢結果中描述。

訪問 Provider

應用程式是透過客戶端物件

ContentResolver

訪問 Content Provider 的。 此物件中包含一些方法,這些方法將會呼叫 Provider 物件中的同名方法。而 Provider 物件是

ContentProvider

某個具體子類的例項。

ContentResolver

中的方法內建了基本的“CRUD”(建立、查詢、更新、刪除(create、retrieve、update 和 delete))功能。

ContentResolver

物件運行於客戶端應用的程序中,而

ContentProvider

運行於提供 Provider 應用的程序中,兩者會自動完成程序間的通訊。

ContentProvider

還發揮著資料抽象層的作用,負責將內部資料以資料庫表的形式提供出來。

注意:

為了訪問 Provider,應用程式通常必須在 Manifest 檔案中請求相應的許可權

例如,要從 User Dictionary Provider 中讀取單詞及地區列表,就要用到

ContentResolver。query()

query()

方法會去呼叫 User Dictionary Provider 中對應的

ContentResolver。query()

方法。以下程式碼演示了

ContentResolver。query()

的呼叫過程:

1 // 查詢使用者詞典並返回結果2 mCursor = getContentResolver()。query(3 UserDictionary。Words。CONTENT_URI, // 單詞表的 Content URI4 mProjection, // 需要返回的列5 mSelectionClause, // 查詢條件6 mSelectionArgs, // 查詢條件的引數7 mSortOrder); // 返回結果的排序要求

表2給出了

query(Uri,projection,selection,selectionArgs,sortOrder)

的引數與 SQL SELECT 語句的對應關係:

表2:

Query() 與 SQL 查詢的對比

query() 引數

SELECT 關鍵字/引數

說明

Uri

FROM *table_name*

Uri

對應於

table_name

指定的 Provider 資料表名。

projection

*col,col,col,。。。*

projection

是包含返回列名稱的陣列。

selection

WHERE *col* = *value*

selection

指定查詢條件。

selectionArgs

(沒有固定值,該查詢引數將會替換查詢語句中的佔位符“?”。)

sortOrder

ORDER BY *col,col,。。。*

sortOrder

指定了返回

Cursor

中各行的顯示順序。

Content URI

Content URI

是一種用於標識 Provider 資料的 URI。 Content URI 包括了整個 Provider 的符號名稱(

authority

)和表名(

path

)。 呼叫客戶端的方法訪問 Provider 資料表時,表的 Content URI 是引數之一。

在前面的程式碼中,常量

CONTENT_URI

包含了指向使用者詞典中 “word” 表的 Content URI。

ContentResolver

物件將分離出 URI 中的 authority ,並用它“解析” 出 Provider,這是透過將 authority 與系統記錄的已有 Provider 清單進行比較來實現的。 然後

ContentResolver

就可以將查詢引數傳送給相應的 Provider 了。

ContentProvider

用 Content URI 的 path 部分選擇要訪問的資料表。 通常, Provider 公開的所有資料表都會帶有自己的

path

在上述程式碼中,“word”表的完整 URI 為:

content://user_dictionary/words

這裡的字串

user_dictionary

是 Provider 的 authority 部分, 字串

words

是資料表的 path 部分。 字串

content://

scheme

)是必須指定的,以表明這是一個 Content URI。

很多 Provider 提供了對單條記錄的訪問能力,只要在 URI 後面跟一個 ID 值即可。 例如,要讀取使用者詞典中

_ID

4

的資料行,可以使用以下 Content URI:

Uri singleUri =ContentUris。withAppendedId(UserDictionary。Words。CONTENT_URI,4);

如果已經讀取了一些資料,然後需要修改或刪除其中的某一條,這時就經常會用到 ID 值了。

注意:

Uri

Uri。Builder

類中已內建了一些工具性的方法,可以由字串搭建合乎規則的 Uri 物件。

ContentUris

中有一些在 URI 後面追加 ID 值的常用方法。 上述程式碼就用了

withAppendedId()

把 ID 追加到 UserDictionary 的 Content URI 之後。

從 Provider 讀取資料

本節將介紹從 Provider 讀取資料的過程,還是以 User Dictionary Provider 為例。

為了清晰起見,本節中的程式碼將會呼叫“UI 執行緒”中的

ContentResolver。query()

。但是在實際的程式碼中,應該在單獨的執行緒中實現非同步查詢。 一種方案是利用

CursorLoader

類,而且,以下只給出了部分程式碼,而非一個完整的應用程式。

從 Provider 中讀取資料的基本步驟如下所示:

申請讀取 Provider 的許可權。

編寫向 Provider 傳送查詢請求的程式碼。

申請讀取許可權

要從 Provider 讀取資料,應用程式需要擁有對 Provider 的“讀許可權”。 在執行時是無法申請該許可權的,只能在 Manifest 檔案中透過

指定。在 Manifest 檔案中的定義,實際上是表明此應用程式需要“申請”該許可權。 這樣使用者在安裝此應用程式時,就可以明確授權。

在 Provider 的參考文件中,給出了其用到的全部許可權的準確名稱。

User Dictionary Provider 在其 Manifest 檔案中定義了

android。permission。READ_USER_DICTIONARY

許可權, 因此要讀它的應用程式就必須請求該許可權。

構建查詢

接下來是構建查詢請求。 以下程式碼定義了一些變數,在訪問 User Dictionary Provider 時將會用到:

1 // “projection” 定義了要返回的資料列 2 String[] mProjection = 3 { 4 UserDictionary。Words。_ID, &n // 對應列名為 _ID 的 Contract Class 常量 5 UserDictionary。an class=“typ”>Words。WORD, // 對應列名為 word 的 Contract Class 常量 6 UserDictionary。an class=“typ”>Words。LOCALE &nbLOCALE // 對應列名為 local 的 Contract Class 常量 7 }; 8 9 // 定義存放查詢條件的字串10 String mSelectionClause =an class=“pln”> null; mSelectionArgs ={“”};

接下來的程式碼演示了

ContentResolver。query()

的使用方法,這裡以 User Dictionary Provider 為例。 Provider 客戶端查詢與 SQL 查詢很類似,也包含了需返回的列名、查詢條件和排序要求。

查詢返回的列名集合物件被稱為”投影“(

Projection

)(即變數

mProjection

)。

查詢資料的表示式被拆分為查詢條件和查詢引數。 查詢條件是由邏輯/布林表示式、列名、數值組成(即變數

mSelectionClause

)。 如果用引數

代替了具體數值,則查詢方法將會從查詢引數陣列(變數

mSelectionArgs

)中讀取實際的值。

在以下程式碼中,如果使用者沒有輸入單詞,則查詢語句將被置為

null

,這樣查詢將會返回 Provider 中的所有單詞。 如果使用者輸入了單詞,那麼查詢語句將會是

UserDictionary。Words。WORD + “ = ?”

,且查詢引數陣列中的第一個成員被設為使用者輸入的單詞。

1 /* 2 * 定義只有一個成員的字串陣列,用於存放查詢引數。 3 */ 4 String[] mSelectionArgs ={“”}; 5 6 // 從使用者介面讀取一個單詞 7 mSearchString = mSearchWord。getText()。toString(); 8 9 // 別忘了在這裡新增檢查輸入內容是否非法或惡意的程式碼10 11 // 如果單詞為空字串,則讀取所有資料12 if(TextUtils。isEmpty(mSearchString)){13 // 將查詢語句設為 null 將返回所有資料14 mSelectionClause =null;15 mSelectionArgs[0]=“”;16 17 }else{18 // 由使用者錄入單詞構建查詢語句19 mSelectionClause =UserDictionary。Words。WORD +“ = ?”;20 21 // 將使用者錄入的字串置入查詢引數陣列中22 mSelectionArgs[0]= mSearchString;23 24 }25 26 // 查詢資料並返回遊標(Cursor)物件27 mCursor = getContentResolver()。query(28 UserDictionary。Words。CONTENT_URI, // 單詞表的 Content URI29 mProjection, // 需返回的列30 mSelectionClause // 為 null 或是使用者錄入的單詞31 mSelectionArgs, // 為空或是使用者錄入的字串32 mSortOrder); // 定義返回資料的排序規則33 34 // 在出錯時,某些 Provider 返回 null,另一些會丟擲異常35 if(null== mCursor){36 /*37 * 在這裡插入處理錯誤的程式碼。38 * 請勿在這裡使用遊標!39 * 可能需要呼叫 40 */41 // 如果遊標中沒有內容,表示 Provider 沒找到匹配的記錄。42 }elseif(mCursor。getCount()<1){43 44 /*45 * 在這裡插入通知使用者查詢失敗的程式碼。46 * 這不一定是出錯了,可以讓使用者錄入新記錄,也可以重新輸入查詢條件。47 */48 49 }else{50 // 在這裡插入處理查詢結果的程式碼。51 52 }

查詢的語句與以下 SQL 語句類似:

SELECT _ID, word, locale FROM words WHERE word = ORDER BY word ASC;

這條 SQL 語句中使用的是真實的列名,而不是 Contract 類常量。

防止非法輸入

如果 Content Provider 管理的資料存放於 SQL 資料庫中,那麼在 SQL 語句中插入某些非法資訊可能會引發 SQL 注入問題。

請看下面這條查詢語句:

// 將使用者輸入內容拼接在列名之後,構造一條查詢語句。String mSelectionClause = “var = ”+ mUserInput;

這時,使用者就可以將惡意 SQL 拼接到查詢語句中。 比如,使用者可以將

mUserInput

輸入為“nothing; DROP TABLE *;”,這樣查詢語句就會成為“

var = nothing; DROP TABLE *;

”。 因為查詢語句將用作 SQL 語句,所以會導致 Provider 刪除底層 SQLite 資料庫中的所有資料表(除非 Provider 設定為捕獲 SQL 注入異常)。

為了避免這類問題,可以在查詢語句中使用

作為可替代引數,並用另一個數組作為實際的引數值。 這樣,使用者的輸入就與查詢直接關聯,而不會被解釋為 SQL 語句的一部分。 因為不再用作 SQL 語句,使用者輸入就無法注入惡意 SQL 了。 使用者的輸入內容不直接用於拼接 SQL 語句,查詢語句如下:

// 用可替代引數構造查詢語句String mSelectionClause = “var = ?”;

查詢引數陣列定義如下:

// 定義存放查詢引數值的陣列String[] selectionArgs ={“”};

在陣列中放入一個查詢引數值:

// 將查詢引數賦為使用者的輸入值selectionArgs[0]= mUserInput;

在構造查詢時,推薦使用這種將

作為形參、陣列提供實參的查詢語句,即使不是基於 SQL 資料庫的 Provider 也可以使用。

顯示查詢結果

客戶端方法

ContentResolver。query()

將返回一個

Cursor

,其中的資料列由對應查詢條件的 Projection 指定。

Cursor

物件支援對資料行和資料列的隨機讀取。透過

Cursor

的內部方法,可以遍歷結果資料行、獲取每一列的資料型別、讀取某一欄位的資料並檢查其他屬性。 某些

Cursor

物件可以在 Provider 的資料發生變化時進行自動更新,或是在

Cursor

資料變動時觸發其他監聽物件的方法。

注意:

根據建立查詢的物件性質, Provider 可以限制對資料列的訪問。 比如,聯絡人 Provider 就不允許 Sync Adapter 訪問某些資料列,也就不會在 Activity 和服務中返回這些列。

如果沒有找到符合條件的資料, Provider 就會返回一個

Cursor。getCount()

為 0 的

Cursor

物件(即空遊標)。

如果發生了內部錯誤,查詢返回的結果將視 Provider 的不同而定。 可能是返回

null

,也可能丟擲一個

Exception

因為

Cursor

是一個數據行的“列表”,所以一種較好的顯示方式就是透過

SimpleCursorAdapter

把它與

ListView

關聯起來。

以下程式碼將延續上面的程式碼。 建立了一個含有

Cursor

SimpleCursorAdapter

物件,並將其設定為一個

ListView

的資料來源介面卡(Adapter):

1 // 定義需要從 Cursor 讀取並顯示出來的資料列 2 String[] mWordListColumns = 3 { 4 UserDictionary。Words。WORD, // 對應 word 列的 Contract 類常量 5 UserDictionary。Words。LOCALE // 對應 locale 列的 Contract 類常量 6 }; 7 8 // 定義 View ID 列表,用於儲存 Cursor 返回的一行資料。 9 int[] mWordListItems ={ R。id。dictWord, R。id。locale};10 11 // 新建一個 SimpleCursorAdapter 物件12 mCursorAdapter =newSimpleCursorAdapter(13 getApplicationContext(), // 應用程式的 Context 物件14 R。layout。wordlistrow, // XML 格式的 Layout,用於 ListView 中每一行的佈局15 mCursor, // 查詢結果16 mWordListColumns, // 字串陣列,存放遊標中的列名17 mWordListItems, // 整形陣列,存放行佈局中的 View ID18 0); // 標誌位(一般用不上)19 20 // 設定 ListView 的 Adapter21 mWordList。setAdapter(mCursorAdapter);

注意:

要將

Cursor

用作

ListView

的後臺資料來源,遊標必須包含一個名為

_ID

的資料列。 因此,上述查詢從“word”表中讀取了

_ID

列,當然

ListView

並不會顯示這個欄位。 這也是大部分 Provider 中的資料表都帶有

_ID

列的原因所在。

從查詢結果中讀取資料

查詢結果不只是簡單地用於顯示,還可以用來完成其他操作。 比如,可以從使用者詞典中讀取單詞並在其他 Provider 中進行檢索。 這時就需要遍歷

Cursor

中的每行資料:

1 // 找到列名為“word”的欄位編號 2 int index = mCursor。getColumnIndex(UserDictionary。Words。WORD); 3 4 /* 5 * 僅當遊標可用時才會執行。 6 * 如果發生內部錯誤,User Dictionary Provider 將會返回 null。而其他 Provider 可能會丟擲異常。 7 */ 8 9 if(mCursor !=null){10 /*11 * 前進至下一行。 12 * 在第一次移動之前,“記錄指標”為 -1,如果這時讀取資料,將會觸發異常。13 */14 while(mCursor。moveToNext()){15 16 // 讀取值17 newWord = mCursor。getString(index);18 19 // 在這裡插入處理返回單詞的程式碼20 21 。。。22 23 // while 迴圈結束24 }25 }else{26 27 // 如果遊標為空或 Provider 丟擲異常,在這裡插入顯示錯誤的程式碼。28 }

Cursor

中有很多用於讀取不同型別資料的“get”方法。 例如,上述程式碼中用到了

getString()

。還有一個

getType()

方法用於返回欄位的型別。

本文原始碼獲取方式:私信 傳送 “底層原始碼” 即可 免費獲取

相關文章

頂部