Android四大組件之ContentProvider

一、ContentProvider

ContentProvider為存儲(chǔ)和獲取數(shù)據(jù)提供統(tǒng)一的接口,可以在不同的應(yīng)用程序之間共享數(shù)據(jù)。如果你不需要在多個(gè)應(yīng)用之間共享數(shù)據(jù),你可以直接通過(guò)SQLite來(lái)操作數(shù)據(jù)庫(kù)。

使用ContentProvider,主要有以下幾個(gè)理由:

  • ContentProvider提供了對(duì)底層數(shù)據(jù)存儲(chǔ)方式的抽象。比如下圖中,底層使用了SQLite數(shù)據(jù)庫(kù),在用了ContentProvider封裝后,即使你把數(shù)據(jù)庫(kù)換成MongoDB,也不會(huì)對(duì)上層數(shù)據(jù)使用層代碼產(chǎn)生影響。


  • Android框架中的一些類需要ContentProvider類型數(shù)據(jù)。如果你想讓你的數(shù)據(jù)可以使用在如SyncAdapter, Loader, CursorAdapter等類上,那么你就需要為你的數(shù)據(jù)做一層ContentProvider封裝
  • 最主要的原因,是ContentProvider為應(yīng)用間的數(shù)據(jù)交互提供了一個(gè)安全的環(huán)境。它準(zhǔn)許你把自己的應(yīng)用數(shù)據(jù)根據(jù)需求開(kāi)放給其他應(yīng)用進(jìn)行增、刪、改、查,而不用擔(dān)心直接開(kāi)放數(shù)據(jù)庫(kù)權(quán)限而帶來(lái)的安全問(wèn)題。
onCreate() boolean //當(dāng)?shù)谝淮卧L問(wèn)ContentProvider,創(chuàng)建完對(duì)象之后調(diào)用,唯一的生命周期方法
insert(Uri uri, ContentValues values) Uri  //根據(jù)Uri插入values對(duì)應(yīng)的數(shù)據(jù)
delete(Uri uri, String selection, String[] selectionArgs) int    //根據(jù)Uri刪除select條件所匹配的全部記錄
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)    int //根據(jù)Uri修改select條件所匹配的全部記錄
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor    //查詢,projection:選擇的指定的列
getType(Uri uri) String    //返回當(dāng)前Uri所代表的MIME類型。

二、ContentResolver

Android為我們提供了ContentResolver來(lái)統(tǒng)一管理與不同ContentProvider間的操作,通過(guò)URI來(lái)區(qū)別不同的ContentProvider
ContentResolver 類也提供了與ContentProvider類相對(duì)應(yīng)的四個(gè)方法:

insert(Uri uri, ContentValues values) Uri    
delete(Uri uri, String selection, String[] selectionArgs) int   
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  int   
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor 
ContentResolver角色

ContentProvider中的URI有固定格式,如下圖:

Scheme:URI的命名空間標(biāo)識(shí)
Authority:授權(quán)信息,用以區(qū)別不同的ContentProvider
Path:表名,用以區(qū)分ContentProvider中不同的數(shù)據(jù)表
Id:Id號(hào),用以區(qū)別表中的不同數(shù)據(jù)

三、URI

1、URI

URI:通用資源標(biāo)志符 —— Uniform Resource Identifier
URI類:java.net.URI,是Java提供的一個(gè)類,代表了URI的一個(gè)實(shí)例
Uri類:android.net.Uri,擴(kuò)展了JAVA中URI的一些功能來(lái)適用于Android開(kāi)發(fā)

2、URI的結(jié)構(gòu)

2.1、基本結(jié)構(gòu)
[scheme:]scheme-specific-part[#fragment]  
[scheme:][//authority][path][?query][#fragment]  
[scheme:][//host:port][path][?query][#fragment]
http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic

在android中,除了scheme、authority是必須要有的,其他都可以不要,Android中可用的每種資源( 圖像、視頻片段等)都可以用URI來(lái)表示

2.2、代碼中提取
Uri.parse(String uriString)    //返回一個(gè)Uri對(duì)象
getScheme()     //獲取Uri中的scheme字符串部分,在這里即,http
getSchemeSpecificPart()   //獲取Uri中的scheme-specific-part:部分,這里是://www.java2s.com:8080/yourpath/fileName.htm?
getFragment()   //獲取Uri中的Fragment部分,即harvic
getAuthority()   //獲取Uri中Authority部分,即www.java2s.com:8080
getPath()   //獲取Uri中path部分,即/yourpath/fileName.htm
getQuery()   //獲取Uri中的query部分,即stove=10&path=32&id=4
getHost()   //獲取Authority中的Host字符串,即www.java2s.com
getPost()   //獲取Authority中的Port字符串,即8080
getPathSegments() List<String>    //依次提取出Path的各個(gè)部分的字符串,以字符串?dāng)?shù)組的形式輸出
getQueryParameter(String key)   //根據(jù)傳進(jìn)去path中某個(gè)Key的字符串,返回對(duì)應(yīng)的值
2.3、絕對(duì)URI和相對(duì)URI

絕對(duì)URI:以scheme組件起始的完整格式,如http://fsjohnhuang.cnblogs.com。表示以對(duì)標(biāo)識(shí)出現(xiàn)的環(huán)境無(wú)依賴的方式引用資源
相對(duì)URI:不以scheme組件起始的非完整格式,如fsjohnhuang.cnblogs.com。表示以對(duì)標(biāo)識(shí)出現(xiàn)的環(huán)境有依賴的方式引用資源

2.4、不透明URI和分層URI

不透明URI:scheme-specific-part組件不是以正斜杠(/)起始的,如mailto:fsjohnhuang@xxx.com
分層URI:scheme-specific-part組件是以正斜杠(/)起始的,如http://fsjohnhuang.com

2.5、UriMatcher工具類

因?yàn)閁ri代表了要操作的數(shù)據(jù),所以我們很經(jīng)常需要解析Uri,并從Uri中獲取數(shù)據(jù)。Android系統(tǒng)提供了兩個(gè)用于操作Uri的工具類,分別為UriMatcher和ContentUris。掌握它們的使用,會(huì)便于我們的開(kāi)發(fā)工作。

UriMatcher

UriMatcher本質(zhì)上是一個(gè)文本過(guò)濾器,用在contentProvider中幫助我們過(guò)濾,分辨出查詢者想要查詢哪個(gè)數(shù)據(jù)表

UriMatcher(int code)   //code:匹配未成功時(shí)返回的標(biāo)志碼,一般置為:UriMatcher.NO_MATCH(值為-1)
//向UriMatcher中注冊(cè)Uri,其中authority和path組合成一個(gè)Uri,code標(biāo)識(shí)該Uri對(duì)應(yīng)的標(biāo)識(shí)碼,path中*表示可匹配任意文本,#表示只能匹配數(shù)字
addURI(String authority, String path, int code) void    
match(Uri uri) int   //該Uri是否匹配,若匹配返回注冊(cè)時(shí)候的標(biāo)志碼,否則返回-1

matcher.addURI("com.xx","person/id/#", 2) 匹配content://com.xx/person/id/1,any://com.xx/person/id/12

ContentUris工具類

其實(shí)就是在末尾加上一個(gè)id

ContentUris.withAppendedId(Uri contentUri, long id) //為路徑加上ID部分
ContentUris.parseId(Uri contentUri) //解析Uri中的ID值

3、URL

URL = URI(scheme組件為部分已知的網(wǎng)絡(luò)協(xié)議) + 與scheme組件標(biāo)識(shí)的網(wǎng)絡(luò)協(xié)議匹配的協(xié)議處理器(URL Protocol Handler),是URI子集。

  • URI的scheme組件在URL中稱為protocol組件,一般http、https、ftp、file、data、jar等。
  • URL Protocol Handler則是一種資源定位器和根據(jù)協(xié)議建立的約束規(guī)則與資源通信的讀寫機(jī)制,用于定位、讀寫資源。

四、ContentObserver

1、基本認(rèn)知

內(nèi)容觀察者,觀察指定的Uri引起數(shù)據(jù)庫(kù)變化后通知主線程,然后根據(jù)需求做處理。首先在需要監(jiān)測(cè)ContentProvider的應(yīng)用中進(jìn)行注冊(cè)(ContentResolver調(diào)用方法的地方),在ContentProvider中要做的就是當(dāng)數(shù)據(jù)變化時(shí)進(jìn)行通知。

ContentResolver相關(guān)方法:

//傳遞一個(gè)ContentObserver的子類對(duì)象進(jìn)去,會(huì)回調(diào)其onChange(boolean selfChange)
registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
//取消對(duì)注冊(cè)的那個(gè)Uri的觀察,這里傳進(jìn)去的就是在registerContentObserver中傳遞進(jìn)去的ContentObserver對(duì)象。
unregisterContentObserver(ContentObserver observer) 
notifyChange(Uri uri, ContentObserver observer)//在Provider中調(diào)用,通知觀察uri的觀察者,observer可以傳null

registerContentObserver方法是注冊(cè)一個(gè)觀察者實(shí)例,當(dāng)指定的Uri發(fā)生改變時(shí),這個(gè)實(shí)例會(huì)回調(diào)實(shí)例對(duì)象做相應(yīng)處理。uri:需要觀察的Uri,notifyForDescendents:如果為true表示以這個(gè)Uri為開(kāi)頭的所有Uri都會(huì)被匹配到,如果為false表示精確匹配,即只會(huì)匹配這個(gè)給定的Uri。
舉個(gè)例子,假如有這么幾個(gè)Uri:
content://com.example.studentProvider/student
content://com.example.studentProvider/student/#
content://com.example.studentProvider/student/10
content://com.example.studentProvider/student/teacher
假如觀察的Uri為content://com.example.studentProvider/student,當(dāng)notifyForDescendents為true時(shí)則以這個(gè)Uri開(kāi)頭的Uri的數(shù)據(jù)變化時(shí)都會(huì)被捕捉到,在這里也就是①②③④的Uri的數(shù)據(jù)的變化都能被捕捉到,當(dāng)notifyForDescendents為false時(shí)則只有①中Uri變化時(shí)才能被捕捉到。

2、實(shí)現(xiàn)一個(gè)ContentObserver

直接創(chuàng)建一個(gè)類繼承ContentObserver,實(shí)現(xiàn)其onChange(boolean selfChange)方法,當(dāng)指定的Uri的數(shù)據(jù)發(fā)生變化時(shí)會(huì)回調(diào)這個(gè)方法。在此方法中,調(diào)用構(gòu)造函數(shù)ContentObserver(Handlerhandler)中傳入的 Handler對(duì)象發(fā)送消息到Handler中,做相應(yīng)的處理。

五、數(shù)據(jù)共享

如何讓其他應(yīng)用也可以訪問(wèn)此應(yīng)用中的數(shù)據(jù)?

1、android:sharedUserId

向此應(yīng)用設(shè)置一個(gè)android:sharedUserId,然后需要訪問(wèn)此數(shù)據(jù)的應(yīng)用也設(shè)置同一個(gè)sharedUserId,具有同樣的sharedUserId的應(yīng)用間可以共享數(shù)據(jù)。

不足:
1)不夠安全
2)無(wú)法做到對(duì)不同數(shù)據(jù)設(shè)置不同讀寫權(quán)限的管理

2、android:exported

  • android:exported 設(shè)置此provider是否可以被其他應(yīng)用使用。
  • android:readPermission 該provider的讀權(quán)限的標(biāo)識(shí)
  • android:writePermission 該provider的寫權(quán)限標(biāo)識(shí)
  • android:permission 該provider的讀寫權(quán)限標(biāo)識(shí)
  • android:grantUriPermissions 臨時(shí)權(quán)限標(biāo)識(shí),true時(shí),意味著該provider下所有數(shù)據(jù)均可被臨時(shí)使用;false時(shí),則反之。但可以通過(guò)設(shè)置<grantUriPermission>標(biāo)簽來(lái)指定哪些路徑可以被臨時(shí)使用。舉個(gè)例子,比如你開(kāi)發(fā)了一個(gè)郵箱應(yīng)用,其中含有附件需要第三方應(yīng)用打開(kāi),但第三方應(yīng)用又沒(méi)有向你申請(qǐng)?jiān)摳郊淖x權(quán)限,但如果你設(shè)置了此標(biāo)簽,則可以在start第三方應(yīng)用時(shí),傳入FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION來(lái)讓第三方應(yīng)用臨時(shí)具有讀寫該數(shù)據(jù)的權(quán)限。

實(shí)例:
1)聲明一個(gè)權(quán)限
<permission android:name="me.pengtao.READ" android:protectionLevel="normal"/>
2)配置

<provider
android:authorities="me.pengtao.contentprovidertest"
android:name=".provider.TestProvider"
android:readPermission="me.pengtao.READ"
android:exported="true">
</provider>

3)其他應(yīng)用中可以使用以下權(quán)限來(lái)對(duì)TestProvider進(jìn)行訪問(wèn)<uses-permission android:name="me.pengtao.READ"/>

對(duì)不同的數(shù)據(jù)表有不同的權(quán)限操作,要如何做呢?Android為這種場(chǎng)景提供了provider的子標(biāo)簽<path-permission>,path-permission包括了以下幾個(gè)標(biāo)簽。

<path-permission android:path="string"
android:pathPrefix="string"
android:pathPattern="string"
android:permission="string"
android:readPermission="string"
android:writePermission="string" />

六、開(kāi)發(fā)ContentProvider步驟

1、步驟一:暴露數(shù)據(jù)

  1. 開(kāi)發(fā)一個(gè)ContentProvider的子類,默認(rèn)需要實(shí)現(xiàn)上面的6個(gè)方法。
    數(shù)據(jù)訪問(wèn)的方法(如:insert和update)可能被多個(gè)線程同時(shí)調(diào)用,此時(shí)必須是線程安全的。其他方法(如: onCreate())只能被應(yīng)用的主線程調(diào)用,它應(yīng)當(dāng)避免冗長(zhǎng)的操作。ContentResolver(內(nèi)容解析者)請(qǐng)求被自動(dòng)轉(zhuǎn)發(fā)到合適的內(nèi)容提供者實(shí)例,所以子類不需要擔(dān)心跨進(jìn)程調(diào)用的細(xì)節(jié)。

實(shí)例代碼
實(shí)現(xiàn)的ContentProvider子類中:

private final static int TEST = 100;

static UriMatcher buildUriMatcher() {
    final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    final String authority = TestContract.CONTENT_AUTHORITY;//必須和清單文件中配置的一致
    matcher.addURI(authority, TestContract.PATH_TEST, TEST);
    return matcher;
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    Cursor cursor = null;
    switch (buildUriMatcher().match(uri)) {
        case TEST://匹配不同的表
            cursor = db.query(TestContract.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);
            break;
    }
    return cursor;
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    Uri returnUri;
    long _id;
    switch ( buildUriMatcher().match(uri)) {
        case TEST:
            _id = db.insert(TestContract.TestEntry.TABLE_NAME, null, values);
            if ( _id > 0 )
                returnUri = TestContract.TestEntry.buildUri(_id);
            else
                throw new android.database.SQLException("Failed to insert row into " + uri);
            break;
        default:
            throw new android.database.SQLException("Unknown uri: " + uri);
    }
    return returnUri;
}

  1. 在清單文件中配置
<provider    
    android:authorities="me.pengtao.contentprovidertest" //一般為包名.含義 
    android:name=".provider.TestProvider"
    android:exprorted="true"/>//是否允許其他應(yīng)用調(diào)用

2、步驟二:獲取ContentResolver對(duì)象,并使用

  1. 獲取ContentResolver對(duì)象。Context的方法:getContentResolver(),因此Activity,Service都能獲得該對(duì)象
  2. 調(diào)用ContentResolver的insert,delete,update,query方法
ContentValues contentValues = new ContentValues();
contentValues.put(TestContract.TestEntry.COLUMN_NAME, "peng");
contentValues.put(TestContract.TestEntry._ID, System.currentTimeMillis());
getContentResolver().insert(TestContract.TestEntry.CONTENT_URI, contentValues);
Cursor cursor = getContentResolver().query(TestContract.TestEntry.CONTENT_URI, null, null, null, null);
try {
    Log.e("ContentProviderTest", "total data number = " + cursor.getCount());
    cursor.moveToFirst();
    Log.e("ContentProviderTest", "total data number = " + cursor.getString(1));
} finally {
    cursor.close();
}

參考文獻(xiàn)

ContentProvider從入門到精通
Uri詳解之——Uri結(jié)構(gòu)與代碼提取
Android開(kāi)發(fā)之內(nèi)容提供者——?jiǎng)?chuàng)建自己的ContentProvider(詳解)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容