ContentProvider詳解

概述

ContentProvider雖說我們平時用的并不多,但是作為安卓四大組件之一,其地位不容忽視。ContentProvider的作用是為不同的應用之間數據共享,提供統一的接口,我們知道安卓系統中應用內部的數據是對外隔離的,要想讓其它應用能使用自己的數據(例如通訊錄)這個時候就用到了ContentProvider。

ContentProvider如何共享數據

ContentProvider通過uri來標識其它應用要訪問的數據,通過ContentResolver的增、刪、改、查方法實現對共享數據的操作。還可以通過注冊ContentObserver來監聽數據是否發生了變化來對應的刷新頁面。下面分別說說每個類的作用。

ContentProvider講解

ContentProvider是一個抽象類,如果我們需要開發自己的內容提供者我們就需要繼承這個類并復寫其方法,需要實現的主要方法如下:
public boolean onCreate()
在創建ContentProvider時使用
public Cursor query()
用于查詢指定uri的數據返回一個Cursor
public Uri insert()
用于向指定uri的ContentProvider中添加數據
public int delete()
用于刪除指定uri的數據
public int update()
用戶更新指定uri的數據
public String getType()
用于返回指定的Uri中的數據MIME類型
數據訪問的方法insert,delete和update可能被多個線程同時調用,此時必須是線程安全的

uri講解

其它應用可以通過ContentResolver來訪問ContentProvider提供的數據,而ContentResolver通過uri來定位自己要訪問的數據,所以我們要先了解uri。URI(Universal Resource Identifier)統一資源定位符,如果您使用過安卓的隱式啟動就會發現,在隱式啟動的過程中我們也是通過uri來定位我們需要打開的Activity并且可以在uri中傳遞參數。URI的格式如下:
[scheme:][//host:port][path][?query]
單單看這個可能我們并不知道是什么意思,下面來舉個栗子就一目了然了
URI:http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
scheme:根據格式我們很容易看出來scheme為http
host:www.baidu.com
port:就是主機名后面path前面的部分為8080
path:在port后面?的前面為wenku/jiatiao.html
query:?之后的都是query部分為 id=123456$name=jack
uri的各個部分在安卓中都是可以通過代碼獲取的,下面我們就以上面這個uri為例來說下獲取各個部分的方法:
getScheme() :獲取Uri中的scheme字符串部分,在這里是http
getHost():獲取Authority中的Host字符串,即 www.baidu.com
getPost():獲取Authority中的Port字符串,即 8080
getPath():獲取Uri中path部分,即 wenku/jiatiao.html
getQuery():獲取Uri中的query部分,即 id=15&name=du
到這里關于uri的部分就講解完了,下面讓我們通過一個自定義ContentProvider實例來說下自定義ContentProvider的流程以及怎么使用ContentResolver操作ContentProverb的數據

自定義ContentProvider實例講解

我們先創建一個項目叫contentprovider,在該項目中我們自定義一個StudentContentProvider的類用來共享該應用的數據,自定義StudentContentProvider的代碼如下:

public class StudentContentProvider extends ContentProvider {

    //這里的AUTHORITY就是我們在AndroidManifest.xml中配置的authorities
    private static final String AUTHORITY = "com.jrmf360.studentProvider";

    //匹配成功后的匹配碼
    private static final int MATCH_CODE = 100;

    private static UriMatcher uriMatcher;

    private StudentDao studentDao;

    //數據改變后指定通知的Uri
    private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/student");

    static {
        //匹配不成功返回NO_MATCH(-1)
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        //添加我們需要匹配的uri
        uriMatcher.addURI(AUTHORITY,"student", MATCH_CODE);
    }


    @Override
    public boolean onCreate() {
        studentDao = StudentDao.getInstance(getContext());
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
                        @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        int match = uriMatcher.match(uri);
        if (match == MATCH_CODE){
            Cursor cursor = studentDao.queryStudent();
            return cursor;
        }
        return null;
    }

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

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        if (uriMatcher.match(uri) == MATCH_CODE){
            studentDao.insertStudent(values);
            notifyChange();
        }
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        if (uriMatcher.match(uri) == MATCH_CODE){
            int delCount = studentDao.deleteStudent();
            notifyChange();
            return delCount;
        }
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
                      @Nullable String[] selectionArgs) {
        return 0;
    }

    private void notifyChange(){
        getContext().getContentResolver().notifyChange(NOTIFY_URI,null);
    }
}

例子中有注釋,但還是要說明一下幾點:

  • ContentProvider是安卓四大組件之一,所以它和Activity一樣也需要我們再xml文件中聲明,聲明如下:
  <provider
        android:authorities="com.jrmf360.studentProvider"
        android:name=".StudentContentProvider"
        android:exported="true"/>

這里的authorities唯一標識該內容提供者,這樣其它的應用才可以找到該內容提供者并操作它的數據;exported為true當前內容提供者可以被其它應用使用,默認為true。

  • 在query、insert和delete方法中都是先調用uriMatcher.match(uri) 判斷當前uri是不是匹配,如果匹配才能操作數據(該例子沒有添加update功能,方式與其它三個方法一樣),MATCH_CODE是我們在調用
    public void addURI (String authority, String path, int code)
    方法添加uri時設置的,當外部應用傳遞過來的uri與對應add的uri一致時,會返回我們設置的code。
  • 該例子中整個數據的操作都是通過SQLite數據庫完成的,在onCreate方法中我們先獲得數據庫的操作對象,通過該對象完成數據庫的增、刪、改、查,數據庫的代碼比較簡單就不貼了。
  • 在數據庫發生變化的時候我們調用notifyChange方法
private void notifyChange(){
        getContext().getContentResolver().notifyChange(NOTIFY_URI,null);
    }

在該方法中我們通知ContentObserver那個uri數據發生了變化,以便及時更新頁面。

通過其他的應用操作ContentProvider中的數據

我們再創建另外一個項目,在該項目的MainActivity方法中我們來操作剛才自定義的ContentProvider中的數據

  • 獲得ContentObserver
   contentResolver = getContentResolver();
  • 注冊ContentObserver來監聽對應uri的數據變化,這步不是必須的,如果不需要監聽數據變化也可以不注冊
private static final String AUTHORITY = "com.jrmf360.studentProvider";
private static final Uri STUDENT_URI = Uri.parse("content://" + AUTHORITY + "/student");

contentResolver.registerContentObserver(STUDENT_URI, true, new MyContentObserver(handler));

可以看到在注冊ContentObserver的方法中我們需要傳遞一個ContentObserver對象,下面是我寫的MyContentObserver類

public class MyContentObserver extends ContentObserver {

    Handler mHandler;
    public MyContentObserver(Handler handler) {
        super(handler);
        mHandler = handler;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        Message message = Message.obtain();
        message.obj = uri;
        mHandler.sendMessage(message);
    }
}

自定義ContentObserver類,必須實現其構造函數,在構造函數中我們需要傳遞一個Handler,到此我們可以明白,ContentObserver在收到數據變化的通知后通過Handler機制來通知主線程更新UI

  • 通過ContentProvider來操作數據
@Override
public void onClick(View v) {
    int id = v.getId();
    if (id == R.id.btn_query) {
        Cursor cursor = contentResolver.query(STUDENT_URI, null, null, null, null, null);
        if (cursor != null && cursor.getCount() > 0) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndex("name"));
                int age = cursor.getInt(cursor.getColumnIndex("age"));
                String desc = cursor.getString(cursor.getColumnIndex("desc"));
                Log.e(getClass().getSimpleName(), "Student:" + "name = " + name + ",age = " + age + ",desc = " + desc);
            }
            cursor.close();
        }
    } else if (id == R.id.btn_insert) {
        ContentValues contentValues = new ContentValues();
        contentValues.put("name", "新插入");
        contentValues.put("age", "-100");
        contentValues.put("desc", "我是新插入的呦。。。");
        contentResolver.insert(STUDENT_URI, contentValues);
    } else if (id == R.id.btn_del) {
        contentResolver.delete(STUDENT_URI, null, null);
    }
}

這樣我們就可以對ContentProvider共享的數據進行增刪查的操作。至此,就完成了ConentProvider的自定義,通過其他應用操作數據,更新UI的流程,感謝您的閱讀。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容