日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

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

作者:Android技術棧 更新時間: 2022-10-29 編程語言

概述

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 URI
4     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 值即可。 例如,要讀取用戶詞典中 _ID4 的數據行,可以使用以下 Content URI:

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

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

注意: UriUri.Builder 類中已內置了一些工具性的方法,可以由字符串搭建合乎規則的 Uri 對象。 ContentUris 中有一些在 URI 后面追加 ID 值的常用方法。 上述代碼就用了 withAppendedId() 把 ID 追加到 UserDictionary 的 Content URI 之后。

從 Provider 讀取數據


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

為了清晰起見,本節中的代碼將會調用“UI 線程”中的 ContentResolver.query() 。但是在實際的代碼中,應該在單獨的線程中實現異步查詢。 一種方案是利用 CursorLoader 類,而且,以下只給出了部分代碼,而非一個完整的應用程序。

從 Provider 中讀取數據的基本步驟如下所示:

  1. 申請讀取 Provider 的權限。
  2. 編寫向 Provider 發送查詢請求的代碼。

申請讀取權限

要從 Provider 讀取數據,應用程序需要擁有對 Provider 的“讀權限”。 在運行時是無法申請該權限的,只能在 Manifest 文件中通過 <uses-permission> 指定。在 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;<s;
11 
12 // 初始化存放查詢參數的數組
13 String[]an class="pln"> 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 URI
29     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 =<userinput> 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 關聯起來。

以下代碼將延續上面的代碼。 創建了一個含有 CursorSimpleCursorAdapter 對象,并將其設置為一個 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 ID
18     0);                                    // 標志位(一般用不上)
19 
20 // 設置 ListView 的 Adapter
21 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() 方法用于返回字段的類型。

本文源代碼獲取方式:私信 發送 “底層源碼” 即可 免費獲取

原文鏈接:https://blog.csdn.net/m0_70748845/article/details/127571463

欄目分類
最近更新