Android 數(shù)據(jù)共享標(biāo)準(zhǔn):ContentProvider 簡介

本文參考文獻(xiàn):《瘋狂Android講義 : 第2版

ContentProvider 是不同應(yīng)用程序之間進(jìn)行數(shù)據(jù)交換的標(biāo)準(zhǔn) API,ContentProvider 以某種 Uri 的形式對外提供數(shù)據(jù)暇番,允許其他應(yīng)用訪問或修改數(shù)據(jù)嗤放;其他應(yīng)用程序使用 ContentResolver 根據(jù) Uri 去訪問操作指定數(shù)據(jù)。

完整地開發(fā)一個(gè) ContentProvider 的步驟:

  1. 定義自己的 ContentProvider 類壁酬,該類需要繼承 Android 提供的 ContentProvider 基類次酌;
  2. 在 AndroidManifest.xml 文件中注冊這個(gè) ContentProvider恨课,在注冊時(shí)綁定一個(gè) Uri。

Uri 簡介

Uri 可以分為如下三個(gè)部分:

  1. content:// —— 這個(gè)部分是 Android 的 ContentProvider 規(guī)定的岳服,就像上網(wǎng)的協(xié)議默認(rèn)是 http:// 一樣剂公。暴露 ContentProvider、訪問 ContentProvider 的協(xié)議默認(rèn)是 content://吊宋;
  2. testContentProvider.toby.person —— 這個(gè)部分就是 content:// 的 authority纲辽。系統(tǒng)就是由這個(gè)部分來找到操作哪個(gè) ContentProvider。只要訪問哪個(gè)指定的 ContentProvider璃搜,這個(gè)部分總是固定的拖吼;
  3. words —— 資源(數(shù)據(jù))部分,當(dāng)訪問者需要訪問不同資源時(shí)这吻,這個(gè)部分是動(dòng)態(tài)改變的绿贞。
content://testContentProvider.toby.person/word/2

此時(shí)它要訪問的資源為 word/2,這意味著訪問 word 數(shù)據(jù)中 ID 為 2 的數(shù)據(jù)橘原。

content://testContentProvider.toby.person/word/2/word

此時(shí)它要訪問的資源為 word/2籍铁,這意味著訪問 word 數(shù)據(jù)中 ID 為 2 的數(shù)據(jù)的word字段。

ContentProvider趾断、ContentResolver拒名、Uri 的關(guān)系

圖片來源 http-_www.reibang.com_p_c6c52c3ba66e

從圖中可以看出,ContentResolver 可以實(shí)現(xiàn)”間接調(diào)用“ ContentProvider 的 CRUD 方法:

  1. 當(dāng) A 應(yīng)用調(diào)用 ContentResolver 的 insert() 方法時(shí)芋酌,實(shí)際上相當(dāng)于調(diào)用了該 Uri 對應(yīng)的 ContentProvider 的 insert() 方法增显;
  2. 當(dāng) A 應(yīng)用調(diào)用 ContentResolver 的 update() 方法時(shí),實(shí)際上相當(dāng)于調(diào)用了該 Uri 對應(yīng)的 ContentProvider 的 update() 方法脐帝;
  3. 當(dāng) A 應(yīng)用調(diào)用 ContentResolver 的 delete() 方法時(shí)同云,實(shí)際上相當(dāng)于調(diào)用了該 Uri 對應(yīng)的 ContentProvider 的 delete() 方法;
  4. 當(dāng)A應(yīng)用調(diào)用 ContentResolver 的 query() 方法時(shí)堵腹,實(shí)際上相當(dāng)于調(diào)用了該 Uri 對應(yīng)的 ContentProvider 的 query() 方法炸站。

開發(fā) ContentProvider 子類

開發(fā) ContentProvider 只要如下兩步:

  1. 開發(fā)一個(gè) ContentProvider 子類,該子類需要實(shí)現(xiàn) query()疚顷、insert()旱易、update() 和 delete() 等方法;
  2. 在AndroidManifest.xml 文件中注冊該 ContentProvider腿堤,指定 android:authorities 屬性阀坏。

配置 ContentProvider

只要為 <applicaton.../> 元素添加了 <provider.../> 子元素即可配置 ContentProvider。例如如下的配置片段:

<provider
        android:name=".FirstProvider"
        android:authorities="com.toby.personal.testlistview.FirstProvider"
        android:exported="true"/>

下面是一個(gè)簡單的使用示例笆檀,示例01:
示例01忌堂,主布局文件的內(nèi)容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:background="@color/colorGray"
    android:orientation="vertical"
    >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/query"
        android:onClick="query"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/insert"
        android:onClick="insert"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/update"
        android:onClick="update"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/delete"
        android:onClick="delete"
        />

</LinearLayout>

示例01 的 FirstProvider.java 文件的內(nèi)容:

package com.toby.personal.testlistview;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * Created by toby on 2017/4/23.
 */

public class FirstProvider extends ContentProvider {

    final private static String TAG = "Toby_Provider";

    @Override
    public boolean onCreate() {
        Log.d(TAG, "========= onCreate is called =========");
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "========= query is called selection =========> " + selection);
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d(TAG, "========= insert is called values =========> " + values);
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "========= delete is called selection =========> " + selection);
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "========= update is called selection =========> " + selection);
        return 0;
    }
}

我們需要在 AndroidManifest.xml 中的 application 節(jié)點(diǎn)下加入如下代碼:

<provider
            android:authorities="com.toby.personal.testlistview.FirstProvider"
            android:name=".FirstProvider"
            android:exported="true"
            />

最后是主程序文件的代碼:

package com.toby.personal.testlistview;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    final private static String TAG = "Toby_Test";

    ContentResolver contentResolver;
    Uri uri = Uri.parse("content://com.toby.personal.testlistview.FirstProvider/");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        contentResolver = getContentResolver();
    }

    public void query(View source) {
        Cursor c = contentResolver.query(uri, null, "query_where", null, null);
        Toast.makeText(this, "遠(yuǎn)端 ContentProvider 返回的 Cursor 為:" + c,
                Toast.LENGTH_LONG).show();
    }

    public void insert(View source) {
        ContentValues values = new ContentValues();
        values.put("name", "jsdhfkjsjh");
        Uri newUri = contentResolver.insert(uri, values);
        Toast.makeText(this, "遠(yuǎn)端 ContentProvider 新插入記錄的 Uri 為:" + newUri,
                Toast.LENGTH_LONG).show();
    }

    public void update(View source) {
        ContentValues values = new ContentValues();
        values.put("name", "jsdhfkjsjh");
        int count = contentResolver.update(uri, values, "update_where", null);
        Toast.makeText(this, "遠(yuǎn)端 ContentProvider 更新記錄數(shù)為:" + count,
                Toast.LENGTH_LONG).show();
    }

    public void delete(View source) {
        int count = contentResolver.delete(uri, "update_where", null);
        Toast.makeText(this, "遠(yuǎn)端 ContentProvider 刪除記錄數(shù)為:" + count,
                Toast.LENGTH_LONG).show();
    }

}

該示例運(yùn)行之后,控制臺的 log 監(jiān)控效果:

顯示效果

配置ContentProvider時(shí)通常指定如下屬性:

  1. name:指定該ContentProvider的實(shí)現(xiàn)類的類名酗洒。
  2. authorities:指定該ContentProvider對應(yīng)的Uri(相當(dāng)于為該ContentProvider分配一個(gè)域名士修。)
  3. android:exported:指定該ContentProvider是否允許其他應(yīng)用調(diào)用妄迁。如果將該屬性設(shè)為false,那么該ContentProvider將不允許其他應(yīng)用調(diào)用李命。

為了確定ContentProvider實(shí)際能處理的Uri登淘,以及確定每個(gè)方法中Uri參數(shù)所操作的數(shù)據(jù),Android系統(tǒng)提供了UriMatcher工具類封字,主要提供了如下兩個(gè)方法:

  1. void addURI(String authority ,String path ,int code):該方法用于向UriMatcher對象注冊Uri黔州。其中authority和path組合成一個(gè)Uri,而code則代表該Uri對應(yīng)的標(biāo)識碼阔籽。
  2. int match(Uri uri):根據(jù)前面注冊的Uri來判斷指定Uri對應(yīng)的標(biāo)識碼流妻。如果找不到匹配的標(biāo)識碼,就會(huì)返回-1笆制。

例如:

UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.toby.personal.testlistview.FirstProvider", "words", 1);
uriMatcher.addURI("com.toby.personal.testlistview.FirstProvider", "word/#", 2);

其中 # 為通配符绅这,這意味著如下匹配結(jié)果:

uriMatcher.match(Uri.parse("content://com.toby.personal.testlistview.FirstProvider/words"));
// 返回標(biāo)識碼 1
uriMatcher.match(Uri.parse("content://com.toby.personal.testlistview.FirstProvider/word/2"));
// 返回標(biāo)識碼 2
uriMatcher.match(Uri.parse("content://com.toby.personal.testlistview.FirstProvider/word/10"));
// 返回標(biāo)識碼 2

至于到底需要為 UriMatcher 注冊多少個(gè) Uri,取決于系統(tǒng)的業(yè)務(wù)需求在辆。
對于 content://com.toby.personal.testlistview.FirstProvider/words 這個(gè) Uri证薇,它的資源部分為 words,這種資源通常代表了訪問所有數(shù)據(jù)項(xiàng)匆篓;對于 content://com.toby.personal.testlistview.FirstProvider/word/2 這個(gè) Uri浑度,它的資源部分通常代表訪問指定數(shù)據(jù)項(xiàng),其中最后一個(gè)數(shù)值往往代表了該數(shù)據(jù)的 ID鸦概。

除此之外箩张,Android 還提供了一個(gè) ContentUris 工具類,它是一個(gè)操作 Uri 字符串的工具類窗市,提供了如下兩個(gè)工具方法:

  1. withAppendedId(uri , id):用于為路徑加上ID部分先慷,例如:
Uri uri = Uri.parse("content://com.toby.personal.testlistview.FirstProvider/word");
Uri resultUri = ContentUris.withAppendedId(uri, 2);
// 生成后的 Uri 為 "content://com.toby.personal.testlistview.FirstProvider/word/2"
  1. parseId(uri):用于從指定Uri中解析出所包含的ID值,例如:
Uri uri = Uri.parse("content://com.toby.personal.testlistview.FirstProvider/word/2");
long wordId = ContentUris.parseId(uri); // 獲取的結(jié)果為:2

操作系統(tǒng)的 ContentProvider

Android系統(tǒng)本身提供了大量的ContentProvider咨察,使用ContentResolver操作系統(tǒng)的ContentProvider數(shù)據(jù)的步驟也是兩步:

  1. 調(diào)用Context的getContentResolver()獲取ContentResolver對象论熙;
  2. 根據(jù)需要調(diào)用ContentResolver的insert()、delete()扎拣、update()和query()方法操作數(shù)據(jù)赴肚。

Android系統(tǒng)用于管理聯(lián)系人的ContentProvider的幾個(gè)Uri如下:

  1. ContactsContract.Contacts.CONTENT_URI:管理聯(lián)系人的Uri素跺;
  2. ContactsContract.CommonDataKinds.Phone.CONTENT_URI:管理聯(lián)系人的電話的Uri二蓝;
  3. ContactsContract.CommonDataKinds.Email.CONTENT_URI:管理聯(lián)系人的E-mail的Uri。

Android為多媒體提供的ContentProvider的Uri如下所示:

  1. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI:存儲在外部存儲其上的音頻文件內(nèi)容的ContentProvider的Uri指厌;
  2. MediaStore.Audio.Media.INTERNAL_CONTENT_URI:存儲在手機(jī)內(nèi)部存儲器上的音頻文件內(nèi)容的ContentProvider的Uri刊愚;
  3. MediaStore.Images.Media.EXTERNAL_CONTENT_URI:存儲在外部存儲器上的圖片文件內(nèi)容的ContentProvider的Uri;
  4. MediaStore.Images.Audio.Media.INTERNAL_CONTENT_URI:存儲在手機(jī)內(nèi)部存儲器上的圖片文件內(nèi)容的ContentProvider的Uri踩验;
  5. MediaStore.Video.Media.EXTERNAL_CONTENT_URI:存儲在外部存儲器上的視頻文件內(nèi)容的ContentProvider的Uri鸥诽;
  6. MediaStore.Video.Audio.Media.INTERNAL_CONTENT_URI:存儲在手機(jī)內(nèi)部存儲器上的視頻文件內(nèi)容的ContentProvider的Uri商玫。

監(jiān)聽 ContentProvider 的數(shù)據(jù)改變

在之前的介紹中,只要導(dǎo)致了 ContentProvider 數(shù)據(jù)發(fā)生了改變牡借,程序中就調(diào)用如下代碼:

getContext().getContentResolver(),notifyChange(uri ,null);

為了在應(yīng)用程序中監(jiān)聽ContentProvider數(shù)據(jù)的改變拳昌,需要利用Android提供的ContentObserver基類。監(jiān)聽ContentProvider數(shù)據(jù)改變的監(jiān)聽器需要繼承ContentObserver類钠龙,并重寫該基類所定義的onChange(boolean selfChange)方法--當(dāng)所監(jiān)聽的ContentProvider數(shù)據(jù)發(fā)生改變時(shí)炬藤,該onChange()方法將會(huì)被觸發(fā)。

為了監(jiān)聽指定ContentProvider的數(shù)據(jù)變化碴里,需要通過ContentResolver向指定Uri注冊ContentObserver監(jiān)聽器沈矿。ContentResolver提供了如下方法來注冊監(jiān)聽器:

registerContentObserver(Uri uri , boolean notifyForDescendents , ContentObserver observer)

這個(gè)方法的三個(gè)參數(shù)分別表示:

  1. uri —— 該監(jiān)聽器所監(jiān)聽的ContentProvider的Uri。
  2. notifyForDescendents —— 如果該參數(shù)設(shè)為true咬腋,假如注冊監(jiān)聽的Uri為content://abc羹膳,nameUri為contetn://abc/xyzcontent://abc/xyz/foo的數(shù)據(jù)改變時(shí)也會(huì)觸發(fā)該監(jiān)聽器根竿;如果設(shè)為false陵像,那么只有content://abc的數(shù)據(jù)發(fā)生改變時(shí)才會(huì)觸發(fā)該監(jiān)聽器。
  3. observer —— 監(jiān)聽器實(shí)例寇壳。

提供程序訪問的替代形式

提供程序訪問的三種替代形式在應(yīng)用開發(fā)的過程中十分重要:

  1. 批量訪問:可以通過 ContentProviderOperation 類中的方法創(chuàng)建一批訪問調(diào)用蠢壹,然后通過 ContentResolver.applyBatch() 執(zhí)行它們。
  2. 異步查詢:應(yīng)該在單獨(dú)線程中執(zhí)行查詢九巡。
  3. 通過 Intent 訪問數(shù)據(jù):盡管無法直接向提供程序發(fā)送 Intent图贸,但是可以向提供程序的應(yīng)用發(fā)送 Intent,后者通常具有修改提供程序數(shù)據(jù)的最佳配置冕广。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疏日,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撒汉,更是在濱河造成了極大的恐慌沟优,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睬辐,死亡現(xiàn)場離奇詭異挠阁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)溯饵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門侵俗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丰刊,你說我怎么就攤上這事隘谣。” “怎么了啄巧?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵寻歧,是天一觀的道長掌栅。 經(jīng)常有香客問我,道長码泛,這世上最難降的妖魔是什么猾封? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮噪珊,結(jié)果婚禮上忘衍,老公的妹妹穿的比我還像新娘。我一直安慰自己卿城,他們只是感情好枚钓,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瑟押,像睡著了一般搀捷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上多望,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天嫩舟,我揣著相機(jī)與錄音,去河邊找鬼怀偷。 笑死家厌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的椎工。 我是一名探鬼主播饭于,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼维蒙!你這毒婦竟也來了掰吕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤颅痊,失蹤者是張志新(化名)和其女友劉穎殖熟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斑响,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菱属,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舰罚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纽门。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沸停,靈堂內(nèi)的尸體忽然破棺而出膜毁,到底是詐尸還是另有隱情,我是刑警寧澤愤钾,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布瘟滨,位于F島的核電站,受9級特大地震影響能颁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一洲敢、第九天 我趴在偏房一處隱蔽的房頂上張望蛀恩。 院中可真熱鬧,春花似錦镜硕、人聲如沸运翼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽血淌。三九已至,卻和暖如春财剖,著一層夾襖步出監(jiān)牢的瞬間悠夯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工躺坟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沦补,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓咪橙,卻偏偏與公主長得像夕膀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子美侦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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