0x00 前言
這篇關(guān)于 ContentProvider 的總結(jié)文章峻呛,早在四月份就想寫了迫肖,但是一直沒有時(shí)間和精力去寫景醇,而且我也不想隨便寫一下欲侮,還是希望能保證一定的文章質(zhì)量崭闲。恰巧昨天為自己定了一個(gè)目標(biāo),就是每周學(xué)習(xí)一個(gè)主題威蕉,并輸出相關(guān)總結(jié)文章或例程刁俭。所以,第一周的主題就定為ContentProvider韧涨。下面看下本文會(huì)涉及到的幾點(diǎn)內(nèi)容:
- 什么是 ContentProvider牍戚;
- 使用 ContentProvider 的優(yōu)點(diǎn);
- 一個(gè)使用系統(tǒng) ContentProvider 的例程虑粥。
0x01 ContentProvider簡(jiǎn)介
關(guān)于 ContentProvider翘魄,搞 Android 的人都不會(huì)陌生,它是 Andriod 中的四大組件之一舀奶,但或許是存在感最低的一個(gè)組件暑竟。為什么這么說呢?因?yàn)橐话銇碚f我們開發(fā)應(yīng)用很少會(huì)去自定義這種組件育勺,但是其實(shí)它非常重要和有用但荤。我們先來看下它的作用:
一看到它的名字——內(nèi)容提供者,就能大概猜到它與數(shù)據(jù)是相關(guān)的涧至,它的作用就是實(shí)現(xiàn)數(shù)據(jù)共享腹躁,這個(gè)共享不僅僅限于本應(yīng)用內(nèi)部,還包括系統(tǒng)中的其他應(yīng)用(即跨進(jìn)程的數(shù)據(jù)共享)南蓬。為此纺非,它實(shí)現(xiàn)了一套標(biāo)準(zhǔn)的接口方法用于存取(增、刪赘方、改烧颖、查,這些操作其實(shí)和標(biāo)準(zhǔn)的 SQL 操作是類似的)它管理的數(shù)據(jù)窄陡,而底層的數(shù)據(jù)如何存儲(chǔ)(存儲(chǔ)于文件系統(tǒng)或者 SQLite 數(shù)據(jù)庫(kù)等)炕淮,調(diào)用者根本不用去關(guān)心:
// 查詢記錄
Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
// 插入記錄
Uri insert (Uri uri, ContentValues values)
// 更新記錄
int update (Uri uri, ContentValues values, String selection, String[] selectionArgs)
// 刪除記錄
int delete (Uri uri, String selection, String[] selectionArgs)
雖然 ContentProvider 定義了一系列的方法,但是我們的應(yīng)用并不直接調(diào)用這些方法跳夭,而是使用一個(gè)ContentResolver對(duì)象涂圆,通過調(diào)用它的方法(與 ContentProvider 中的方法一一對(duì)應(yīng))作為替代们镜,如圖1所示。
圖 1:圖片來自 Udacity 學(xué)院課程截圖
說到這里大家肯定會(huì)奇怪润歉,ContentResolver 是如何定位到我們想要的那個(gè)的ContentProvider 的呢模狭?你要知道Android系統(tǒng)已經(jīng)為我們提供了很多的 ContentProvider,例如日歷踩衩、聯(lián)系人胞皱、多媒體(音頻、視頻以及圖像)等數(shù)據(jù)九妈。
其實(shí)反砌,答案就是各個(gè)方法參數(shù)中的 Uri,就像你在瀏覽器的地址欄中輸入一個(gè)網(wǎng)址 (URL)萌朱,按下回車后宴树,瀏覽器就會(huì)向服務(wù)器發(fā)起請(qǐng)求,而對(duì)應(yīng)的服務(wù)器會(huì)返回相應(yīng)的結(jié)果給瀏覽器晶疼。類似地酒贬,當(dāng)ContentResolver
也是通過一個(gè)給定的 URI (UR L是 URI 的一個(gè)子集)發(fā)起請(qǐng)求,Android 系統(tǒng)會(huì)根據(jù)給定URI中的authority(URI 的組成部分找到那個(gè) ContentProvider 翠霍,并將該請(qǐng)求傳遞給該 ContentProvider锭吨。每個(gè) ContentProvider 的 authority 都是唯一的,而且都會(huì)在 Android 系統(tǒng)注冊(cè)寒匙。ContentProvider 收到請(qǐng)求后零如,會(huì)解析 URI 剩余的部分(通過輔助類UriMatcher實(shí)現(xiàn)),然后返回相應(yīng)數(shù)據(jù)锄弱。
通過以上內(nèi)容考蕾,我想大家對(duì) ContentProvider 應(yīng)該有了一個(gè)大概的了解了吧,下面我將通過用戶詞典(user dictionary:用于存儲(chǔ)一些用戶想保存的非標(biāo)準(zhǔn)單詞的拼寫)這個(gè)系統(tǒng)內(nèi)置的 ContentProvider來向大家說明如何使用它会宪。
0x02 如何使用 ContentProvider
我們要知道 ContentProvider 提供的數(shù)據(jù)的形式與一般關(guān)系型的數(shù)據(jù)庫(kù)是一樣的肖卧,都是通過一張或多張的數(shù)據(jù)庫(kù)表來實(shí)現(xiàn)的,例如用戶詞典的數(shù)據(jù)表是這樣的掸鹅,其中每一行代碼一個(gè)單詞實(shí)例塞帐,每一列代表每個(gè)單詞的屬性:
表1:用戶詞典數(shù)據(jù)表樣例引用
word | app id | frequency | locale | _ID |
---|---|---|---|---|
mapreduce | user12 | 100 | en_US | 1 |
applet | user2 | 200 | pt_BR | 2 |
const | user1 | 255 | en_UK | 3 |
上面這個(gè)表跟我們平時(shí)操作的數(shù)據(jù)表沒有什么區(qū)別嘛,說下本節(jié)我們要實(shí)現(xiàn)的功能就是通過一個(gè)ListView
來顯示表1中的數(shù)據(jù)巍沙,就這么簡(jiǎn)單葵姥,具體步驟如下:
(1) 獲取相應(yīng) ContentProvider 的權(quán)限
Android 系統(tǒng)中內(nèi)置了很多的 ContentProvider,當(dāng)我們?cè)L問它們時(shí)赎瞎,需要在AndroidManifest.xml
文件中聲明相應(yīng)的讀/寫權(quán)限牌里,如果你聲明了寫權(quán)限颊咬,應(yīng)該不需要再申請(qǐng)讀權(quán)限务甥,用戶詞典的具體權(quán)限聲明如下所示:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
(2) 訪問正確的 ContentProvider
如前面所述牡辽,我們的應(yīng)用不會(huì)直接訪問 ContentProvider,我們需要通過 ContentResolver 以及正確的 URI敞临,如圖2所示态辛,同時(shí)調(diào)用相應(yīng)的方法:
query
、insert
挺尿、update
和delete
奏黑。
圖 2:圖片來自 Udacity 學(xué)院課程截圖
我們這個(gè)例子只是簡(jiǎn)單地查詢數(shù)據(jù),具體示例代碼如下所示:
// 獲取 ContentResolver 用于向 ContentProvider 發(fā)送數(shù)據(jù)請(qǐng)求
ContentResolver resolver = getContentResolver();
// 獲取 Cursor编矾,它包含了Words數(shù)據(jù)表的所有行
Cursor cursor = resolver.query(UserDictionary.Words.CONTENT_URI, // Uri
null, // projection
null, // selection
null, // selectionArgs
null // sortOrder
);
Content URI
上述代碼中query
方法第一個(gè)參數(shù)UserDictionary.Words.CONTENT_URI
熟史,即之前我們提到的 URI,它一般由三部分組成content://authority/path
窄俏,它用于標(biāo)識(shí) ContentProvider 中的數(shù)據(jù)記錄蹂匹,詳細(xì)請(qǐng)參考Content URIs。
(3) 在 UI 上展現(xiàn)數(shù)據(jù)
我們的目標(biāo)是在 ListView 中顯示這些單詞凹蜈,在第(2)個(gè)步驟得到的cursor
其實(shí)就相當(dāng)于一個(gè)數(shù)據(jù)源限寞,所以只要實(shí)現(xiàn)相應(yīng)的 Adapter 即可,具體代碼如下所示:
// 使用系統(tǒng)提供的 SimpleCursorAdapter
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.two_line_list_item,
cursor,
COLUMNS_TO_BE_BOUND,
LAYOUT_ITEMS_TO_FILL,
0
);
dictListView.setAdapter(adapter);
0x03 總結(jié)
根據(jù)以上這些的內(nèi)容仰坦,我們稍微總結(jié)一下使用 ContentProvider 的兩個(gè)優(yōu)點(diǎn):
- 底層的數(shù)據(jù)源對(duì)上層是透明的履植,可以非常方便地替換;
- 允許系統(tǒng)中的多個(gè)應(yīng)用對(duì)同一個(gè)數(shù)據(jù)源進(jìn)行(安全的)訪問和修改悄晃,而相應(yīng)的修改對(duì)于多個(gè)應(yīng)用來說都是可見的玫霎。
看到這里我想很多人會(huì)想:如果我們并不想與其他應(yīng)用共享我們的應(yīng)用的數(shù)據(jù),那么我們應(yīng)該是不需要開發(fā)這種類型的組件妈橄。以前我也是這么想的鼠渺,既然 ContentProvider 底層數(shù)據(jù)的存儲(chǔ)還是通過 SQLite 數(shù)據(jù)庫(kù),為什么要用它呢眷细?其實(shí)不然拦盹,首先我們使用 SQLite 數(shù)據(jù)庫(kù)存儲(chǔ)我們的數(shù)據(jù),我們還是需要在 SQLite 數(shù)據(jù)庫(kù)之上封裝一層類似 ContentProvider 提供的那套接口方法溪椎;其次普舆,我們還可以利用 Android 系統(tǒng)提供的一些相關(guān)功能類與 ContentProvider 配合起來使用。 例如校读,SyncAdaters
, Loaders
, CursorAdapter
沼侣。說到這里我推薦大家一個(gè)視頻吧,來自2010年 Google IO 大會(huì)歉秫,講的是 Android 應(yīng)用的設(shè)計(jì)模式(架構(gòu))蛾洛,其中一共提出了三種設(shè)計(jì)模式,而 ContentProvider 在其中扮演了非常重要的角色: