網站首頁 編程語言 正文
概述
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 值即可。 例如,要讀取用戶詞典中 _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 文件中通過 <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
關聯起來。
以下代碼將延續上面的代碼。 創建了一個含有 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 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
相關推薦
- 2022-11-26 React常見跨窗口通信方式實例詳解_React
- 2022-08-06 使用Gorm操作Oracle數據庫踩坑記錄_Golang
- 2022-12-14 Jetpack?Compose重寫TopAppBar實現標題多行折疊詳解_Android
- 2022-11-27 C語言中花式退出程序的方式總結_C 語言
- 2022-03-30 用Python判斷奇偶數示例_python
- 2024-01-09 poi操作Excel給列設置下拉菜單(數據驗證)
- 2022-11-08 hooks中useEffect()使用案例詳解_React
- 2022-06-27 ABP引入SqlSugar框架的簡單版創建使用_實用技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支