為什么要將我們程序中的數(shù)據(jù)共享給其他程序呢序宦?當(dāng)然睁壁,這個要視情況而定的,比如說賬號和密碼這樣的隱私數(shù)據(jù)顯然是不能共享給其他程序的互捌,不過一些可以讓其他程序進(jìn)行二次開發(fā)的基礎(chǔ)性數(shù)據(jù)堡僻,我們還是可以選擇將其共享的。例如系統(tǒng)的電話簿程序疫剃,它的數(shù)據(jù)庫中保存了很多的聯(lián)系人信息,如果這些數(shù)據(jù)都不允許第三方的程序進(jìn)行訪問的話硼讽,恐怕很多應(yīng)用的功能都要大打折扣了巢价。除了電話簿之外,還有短信固阁、媒體庫等程序都實(shí)現(xiàn)了跨程序數(shù)據(jù)共享的功能壤躲,而使用的技術(shù)當(dāng)然就是內(nèi)容提供器。
內(nèi)容提供器簡介
內(nèi)容提供器(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能备燃,它提供了一套完整的機(jī)制碉克,允許一個程序訪問另一個程序中的數(shù)據(jù),同時還能保證被訪數(shù)據(jù)的安全性并齐。目前漏麦,使用內(nèi)容提供器是 Android 實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式。
內(nèi)容提供器可以選擇只對哪一部分?jǐn)?shù)據(jù)進(jìn)行共享况褪,從而保證我們程序中的隱私數(shù)據(jù)不會有泄漏的風(fēng)險撕贞。
用法一般有兩種:
- 一種是使用現(xiàn)有的內(nèi)容提供器來讀取和操作相應(yīng)程序中的數(shù)據(jù)
- 另一種是創(chuàng)建自己的內(nèi)容提供器給我們程序的數(shù)據(jù)提供外部訪問接口。
訪問其他程序中的數(shù)據(jù)
當(dāng)一個應(yīng)用程序通過內(nèi)容提供器對其數(shù)據(jù)提供了外部訪問接口测垛,任何其他的應(yīng)用程序就
都可以對這部分?jǐn)?shù)據(jù)進(jìn)行訪問捏膨。Android 系統(tǒng)中自帶的電話簿、短信食侮、媒體庫等程序都提供了類似的訪問接口.
ContentResolver 的基本用法
如果想要訪問內(nèi)容提供器中共享的數(shù)據(jù)号涯,就一定要借助ContentResolve 類,可以通過 Context 中的 getContentResolver()方法獲取到該類的實(shí)例锯七。ContentResolver 中提供了一系列的方法用于對數(shù)據(jù)進(jìn)行 CRUD 操作链快,其中 insert()方法用于添加數(shù)據(jù),update()方法用于更新數(shù)據(jù)起胰,delete()方法用于刪除數(shù)據(jù)久又,query()方法用于查詢數(shù)據(jù)巫延。
ContentResolver 中的增刪改查方法都是不接收表名參數(shù)的,而是使用一個 Uri 參數(shù)代替地消,這個參數(shù)被稱為內(nèi)容 URI炉峰。內(nèi)容 URI 給內(nèi)容提供器中的數(shù)據(jù)建立了唯一標(biāo)識符,它主要由兩部分組成脉执,權(quán)限(authority)和路徑(path)疼阔。權(quán)限是用于對不同的應(yīng)用程序做區(qū)分的,一般為了避免沖突半夷,都會采用程序包名的方式來進(jìn)行命名婆廊。比如某個程序的包名是 com.example.app,那么該程序?qū)?yīng)的權(quán)限就可以命名為 com.example.app.provider巫橄。路徑則是用于對同一應(yīng)用程序中不同的表做區(qū)分的淘邻,通常都會添加到權(quán)限的后面。
實(shí)例 協(xié)議聲明content://權(quán)限/路徑
content://com.example.app.provider/table1
content://com.example.app.provider/table2
內(nèi)容 URI 可以非常清楚地表達(dá)出我們想要訪問哪個程序中哪張表里的數(shù)據(jù)湘换。在得到了內(nèi)容 URI 字符串之后宾舅,我們還需要將它解析成 Uri 對象才可以作為參數(shù)傳入。解析的方法也相當(dāng)簡單彩倚,代碼如下所示:
解析URI字符串成Uri對象
Uri uri = Uri.parse("content://com.example.app.provider/table1")
現(xiàn)在我們就可以使用這個 Uri 對象來查詢 table1 表中的數(shù)據(jù)了
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
查詢完成后返回的仍然是一個 Cursor 對象筹我,這時我們就可以將數(shù)據(jù)從 Cursor 對象中逐個讀取出來了。讀取的思路仍然是通過移動游標(biāo)的位置來遍歷 Cursor 的所有行帆离,然后再取出每一行中相應(yīng)列的數(shù)據(jù)
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new
String[] {"text", "1"});
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
###讀取系統(tǒng)聯(lián)系人
// 查詢聯(lián)系人數(shù)據(jù)
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, null, null, null);
while (cursor.moveToNext()) {
// 獲取聯(lián)系人姓名
String displayName =
cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 獲取聯(lián)系人手機(jī)號
String number =
cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
創(chuàng)建自己的內(nèi)容提供器
創(chuàng)建內(nèi)容提供器的步驟
實(shí)現(xiàn)跨程序共享數(shù)據(jù)的功能蔬蕊,官方推薦的方式就是使用內(nèi)容提供器,可以通過新建一個類去繼承 ContentProvider 的方式來創(chuàng)建一個自己的內(nèi)容提供器哥谷。ContentProvider 類中有六個抽象方法岸夯,我們在使用子類繼承它的時候,需要將這六個方法全部重寫们妥。
1.onCreate()
初始化內(nèi)容提供器的時候調(diào)用囱修。通常會在這里完成對數(shù)據(jù)庫的創(chuàng)建和升級等操作,返回 true 表示內(nèi)容提供器初始化成功王悍,返回 false 則表示失敗破镰。注意,只有當(dāng)存在ContentResolver 嘗試訪問我們程序中的數(shù)據(jù)時压储,內(nèi)容提供器才會被初始化鲜漩。
2.query()
從內(nèi)容提供器中查詢數(shù)據(jù)。使用 uri 參數(shù)來確定查詢哪張表集惋,projection 參數(shù)用于確定查詢哪些列孕似,selection 和 selectionArgs 參數(shù)用于約束查詢哪些行,sortOrder 參數(shù)于對結(jié)果進(jìn)行排序刮刑,查詢的結(jié)果存放在 Cursor 對象中返回喉祭。
3.insert()
向內(nèi)容提供器中添加一條數(shù)據(jù)养渴。使用 uri 參數(shù)來確定要添加到的表,待添加的數(shù)據(jù)保存在 values 參數(shù)中泛烙。添加完成后理卑,返回一個用于表示這條新記錄的 URI。
4.update()
更新內(nèi)容提供器中已有的數(shù)據(jù)蔽氨。使用 uri 參數(shù)來確定更新哪一張表中的數(shù)據(jù)藐唠,新數(shù)據(jù)保存在 values 參數(shù)中,selection 和 selectionArgs 參數(shù)用于約束更新哪些行鹉究,受影響的行數(shù)將作為返回值返回宇立。
5.delete()
從內(nèi)容提供器中刪除數(shù)據(jù)。使用 uri 參數(shù)來確定刪除哪一張表中的數(shù)據(jù)自赔,selection和 selectionArgs 參數(shù)用于約束刪除哪些行妈嘹,被刪除的行數(shù)將作為返回值返回。
6.getType()
根據(jù)傳入的內(nèi)容 URI 來返回相應(yīng)的 MIME 類型绍妨。
幾乎每一個方法都會帶有 Uri這個參數(shù)蟋滴,這個參數(shù)也正是調(diào)用 ContentResolver的增刪改查方法時傳遞過來的。
content://com.example.app.provider/table1/1
表示調(diào)用方期望訪問的是 com.example.app 這個應(yīng)用的 table1 表中 id 為 1 的數(shù)據(jù)痘绎。
(以 id 結(jié)尾就表示期望訪問該表中擁有相應(yīng) id 的數(shù)據(jù))
content://com.example.app.provider/table1
以路徑結(jié)尾就表示期望訪問該表中所有的數(shù)據(jù)
使用通配符的方式來分別匹配這兩種格式的內(nèi)容 URI,規(guī)則如下:
1.*:表示匹配任意長度的任意字符
2.#:表示匹配任意長度的數(shù)字
所以肖粮,一個能夠匹配任意表的內(nèi)容 URI 格式就可以寫成:
而一個能夠匹配 table1 表中任意一行數(shù)據(jù)的內(nèi)容 URI 格式就可以寫成:
借助UriMatcher這個類就可以輕松地實(shí)現(xiàn)匹配內(nèi)容URI的功能孤页。UriMatcher中提供了一個 addURI()方法,這個方法接收三個參數(shù)涩馆,可以分別把權(quán)限行施、路徑和一個自定義代碼傳進(jìn)去。這樣魂那,當(dāng)調(diào)用 UriMatcher 的 match()方法時蛾号,就可以將一個 Uri 對象傳入,返回值是某個能夠匹配這個 Uri 對象所對應(yīng)的自定義代碼涯雅,利用這個代碼鲜结,我們就可以判斷出調(diào)用方期望訪問的是哪張表中的數(shù)據(jù)了。
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider ", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider ", "table2", TABLE2_ITEM);
uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查詢table1 表中的所有數(shù)據(jù)
break;
case TABLE1_ITEM:
// 查詢table1 表中的單條數(shù)據(jù)
break;
case TABLE2_DIR:
// 查詢table2 表中的所有數(shù)據(jù)
break;
case TABLE2_ITEM:
// 查詢table2 表中的單條數(shù)據(jù)
break;
default:
break;
}
……
}
……
還有一個方法你會比較陌生活逆,即 getType()方法精刷。它是所有的內(nèi)容提供器都必須提供的一個方法,用于獲取 Uri 對象所對應(yīng)的 MIME 類型蔗候。一個內(nèi)容 URI 所對應(yīng)的 MIME字符串要由三部分組分怒允,Android 對這三個部分做了如下格式規(guī)定。
必須以 vnd 開頭锈遥。
如果內(nèi)容 URI 以路徑結(jié)尾纫事,則后接 android.cursor.dir/勘畔,如果內(nèi)容 URI 以 id 結(jié),
則后接 android.cursor.item/丽惶。最后接上 vnd.<authority>.<path>炫七。
所以,對于 content://com.example.app.provider/table1 這個內(nèi)容 URI蚊夫,它所對應(yīng)的 MIME類型就可以寫成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
對于 content://com.example.app.provider/table1/1 這個內(nèi)容 URI诉字,它所對應(yīng)的 MIME 類型就可以寫成:
vnd.android.cursor.item/vnd. com.example.app.provider.table1
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
default:
break;
}
return null;
}
如何才能保證隱私數(shù)據(jù)不會泄漏出去呢?其實(shí)多虧了內(nèi)容提供器的良好機(jī)制知纷,這個問題在不知不覺中已經(jīng)被解決了壤圃。因為所有的 CRUD 操作都一定要匹配到相應(yīng)的內(nèi)容 URI 格式才能進(jìn)行的,而我們當(dāng)然不可能向 UriMatcher 中添加隱私數(shù)據(jù)的 URI琅轧,所以這部分?jǐn)?shù)據(jù)根本無法被外部程序訪問到伍绳,安全問題也就不存在了。