跨程序共享數(shù)據(jù)踢涌,探究內(nèi)容提供器,ContentProvider

為什么要將我們程序中的數(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);
QQ截圖20160226223815.jpg

查詢完成后返回的仍然是一個 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 格式就可以寫成:

content://com.example.app.provider/*

而一個能夠匹配 table1 表中任意一行數(shù)據(jù)的內(nèi)容 URI 格式就可以寫成:

content://com.example.app.provider/table1/#

借助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ī)定。

  1. 必須以 vnd 開頭锈遥。

  2. 如果內(nèi)容 URI 以路徑結(jié)尾纫事,則后接 android.cursor.dir/勘畔,如果內(nèi)容 URI 以 id 結(jié),
    則后接 android.cursor.item/丽惶。

  3. 最后接上 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ù)根本無法被外部程序訪問到伍绳,安全問題也就不存在了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乍桂,一起剝皮案震驚了整個濱河市冲杀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌睹酌,老刑警劉巖权谁,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異憋沿,居然都是意外死亡旺芽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門辐啄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來采章,“玉大人,你說我怎么就攤上這事壶辜∶踔郏” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵砸民,是天一觀的道長抵怎。 經(jīng)常有香客問我,道長岭参,這世上最難降的妖魔是什么便贵? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮冗荸,結(jié)果婚禮上承璃,老公的妹妹穿的比我還像新娘。我一直安慰自己蚌本,他們只是感情好盔粹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布隘梨。 她就那樣靜靜地躺著,像睡著了一般舷嗡。 火紅的嫁衣襯著肌膚如雪轴猎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天进萄,我揣著相機(jī)與錄音捻脖,去河邊找鬼。 笑死中鼠,一個胖子當(dāng)著我的面吹牛可婶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播援雇,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼矛渴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惫搏?” 一聲冷哼從身側(cè)響起具温,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筐赔,沒想到半個月后铣猩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茴丰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年达皿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较沪。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖失仁,靈堂內(nèi)的尸體忽然破棺而出尸曼,到底是詐尸還是另有隱情,我是刑警寧澤萄焦,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布控轿,位于F島的核電站,受9級特大地震影響拂封,放射性物質(zhì)發(fā)生泄漏茬射。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一冒签、第九天 我趴在偏房一處隱蔽的房頂上張望在抛。 院中可真熱鬧,春花似錦萧恕、人聲如沸刚梭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朴读。三九已至屹徘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衅金,已是汗流浹背噪伊。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氮唯,地道東北人鉴吹。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像您觉,于是被迫代替她去往敵國和親拙寡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容