一、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

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ù)
- 開(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;
}
- 在清單文件中配置
<provider
android:authorities="me.pengtao.contentprovidertest" //一般為包名.含義
android:name=".provider.TestProvider"
android:exprorted="true"/>//是否允許其他應(yīng)用調(diào)用
2、步驟二:獲取ContentResolver對(duì)象,并使用
- 獲取ContentResolver對(duì)象。Context的方法:getContentResolver(),因此Activity,Service都能獲得該對(duì)象
- 調(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(詳解)
