ContentProvider
簡(jiǎn)介
ContentProvider(內(nèi)容提供者)
是提供跨程序數(shù)據(jù)訪問(wèn)的Android組件靖避。簡(jiǎn)而言之潭枣,相當(dāng)于一個(gè)數(shù)據(jù)接口,可以將程序內(nèi)部的數(shù)據(jù)向其他應(yīng)用程序公開(kāi)幻捏,讓其他應(yīng)用程序可以增刪查改操作盆犁。
在Android
原生的應(yīng)用很多都應(yīng)用了ContentProvider
例如通訊錄,文件系統(tǒng)等篡九。
存在的原因
- 解決應(yīng)用程序的數(shù)據(jù)庫(kù)在私有目錄下谐岁,其他應(yīng)用程序使用常規(guī)方法無(wú)法訪問(wèn)數(shù)據(jù)庫(kù)。
- 程序內(nèi)部的數(shù)據(jù)庫(kù)結(jié)構(gòu)復(fù)雜榛臼,即使可以訪問(wèn)伊佃,但是如果不了解表的構(gòu)造也很難訪問(wèn),所以通過(guò)
ContentProvider
封裝對(duì)數(shù)據(jù)的訪問(wèn)沛善,給外部程序訪問(wèn)數(shù)據(jù)的一個(gè)直觀的接口航揉。 - 如果直接提供其他應(yīng)用程序訪問(wèn)數(shù)據(jù)的權(quán)限不加以控制,那么數(shù)據(jù)安全也是一個(gè)問(wèn)題金刁,所以通過(guò)
ContentProvider
即可以提供數(shù)據(jù)接口并且可以控制訪問(wèn)權(quán)限帅涂。
簡(jiǎn)單的使用
主要步驟:數(shù)據(jù)庫(kù)提供方:繼承ContentProvider
實(shí)現(xiàn)有關(guān)數(shù)據(jù)庫(kù)的相關(guān)操作议薪,然后再AndroidMainfest.xml
文件中注冊(cè)。 數(shù)據(jù)庫(kù)訪問(wèn)方: 通過(guò)'URI'訪問(wèn)對(duì)應(yīng)的ContentProvider
媳友。
PS: 一定要在AndroidMainfest.xml
文件中注冊(cè)斯议,每一個(gè)ContentProvider
對(duì)應(yīng)一個(gè)authorities
,需要注意的是authorities
在整個(gè)系統(tǒng)必須是唯一的,否則安裝會(huì)有沖突庆锦。并且要注明是android:exported="true"
才能被外部訪問(wèn)。
注冊(cè)例子如下:
<provider
android:name="com.doris.support.provider.SettingsProvider"
android:authorities="com.doris.contact.provider"
android:exported="true" >
</provider>
上面只是粗略的總結(jié)了一下用法轧葛,不過(guò)肯定這幾行字搂抒,你們還是不太清楚具體如何使用。接下來(lái)我會(huì)一步一步的帶著大家來(lái)自定義ContentProvider
以及使用它尿扯。接下來(lái)要長(zhǎng)篇大論了求晶,做好準(zhǔn)備啦,千萬(wàn)別暈衷笋!
這里我們統(tǒng)一一下芳杏,把提供數(shù)據(jù)的應(yīng)用程序稱之為服務(wù)端,把請(qǐng)求數(shù)據(jù)的應(yīng)用程序稱之為客戶端辟宗。
服務(wù)端
- 在服務(wù)器自定義一個(gè)ContentProvider提供數(shù)據(jù)訪問(wèn)接口爵赵。
1.自定義ContentProvider
自定義ContentPorvider
繼承自ContentPorvider
,這里我們自定義一個(gè)ContactProvider
用于獲取Contact
數(shù)據(jù)表的數(shù)據(jù),Contact
表用于存儲(chǔ)聯(lián)系人信息泊脐。先貼一下完整代碼:
package com.doris.provide;
import com.orhanobut.logger.Logger;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
/**
* Created by Doris on 2017/2/24.
*/
public class ContactProvider extends ContentProvider {
public static final int CODE_CONTACT = 0X108;
private static final String TAG = ContactProvider.class.getSimpleName();
private static final String AUTHORITYS = "com.doris.contact.provider";
private static final String TABLE_NAME = "Contact";
//通過(guò)UriMatcher匹配Uri從而執(zhí)行對(duì)應(yīng)的操作空幻。
private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
mUriMatcher.addURI(AUTHORITYS, TABLE_NAME, CODE_CONTACT);
}
@Override
public boolean onCreate() {
Logger.d(TAG, "onCreate");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
switch (mUriMatcher.match(uri)) {
case CODE_CONTACT :
cursor = DBHelper.getInstance().query(TABLE_NAME, selection, selectionArgs, sortOrder);
break;
}
return cursor;
}
@Override
public String getType(Uri uri) {
return uri.toString();
}
@Override
public Uri insert(Uri uri, ContentValues values) {
switch (mUriMatcher.match(uri)) {
case static {
mUriMatcher.addURI(AUTHORITYS, TABLE_NAME, CODE_CONTACT);
} : {
long rowId2 = DBHelper.getInstance().insert(TABLE_NAME, values);
if (rowId2 > 0) {
Uri noteUri = ContentUris.withAppendedId(uri, rowId2);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
}
break;
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (mUriMatcher.match(uri)) {
case CODE_CONTACT :
count = DBHelper.getInstance().delete(TABLE_NAME, selection, selectionArgs);
break;
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch (mUriMatcher.match(uri)) {
case CODE_CONTACT :
count = DBHelper.getInstance().update(CPosterManager.TABLE_NAME, values, selection, selectionArgs);
break;
}
return count;
}
}
在上面這段代碼中,我們自定義了一個(gè)ContactProvider
提供了對(duì)Contact
表的增刪查改操作容客,而增刪查改動(dòng)作又是通過(guò)DBHelper
類來(lái)實(shí)現(xiàn)的秕铛,DBHelper
類封裝了對(duì)數(shù)據(jù)庫(kù)的操作。
我們來(lái)解析一下代碼缩挑。以上代碼的關(guān)鍵就是UriMatch
如其名但两,用來(lái)匹配Uri
來(lái)解讀對(duì)應(yīng)的操作CODE,從而對(duì)應(yīng)到某個(gè)操作上供置。因?yàn)槠渌绦蚨际峭ㄟ^(guò)uri來(lái)訪問(wèn)ContactProvider
的所以需要匹配區(qū)分谨湘,訪問(wèn)同一個(gè)Provider
的時(shí)候分別是要請(qǐng)求什么數(shù)據(jù)。比如:content:\\com.doris.contact.provider\contact
,通過(guò)UriMathc
匹配則是匹配到CODE_CONTACT
.
在ContactProvider
里面只有一個(gè)CODE
就是CODE_CONTACT
芥丧,通過(guò)這個(gè)來(lái)標(biāo)示操作Contact
表悲关。
static {
//在這句代碼里面就是通過(guò)addURI來(lái)添加UriMatcher的一個(gè)匹配規(guī)則。
mUriMatcher.addURI(AUTHORITYS, TABLE_NAME, CODE_CONTACT);
}
//addURI的方法如下:
/**
* Add a URI to match, and the code to return when this URI is
* matched. URI nodes may be exact match string, the token "*"
* that matches any text, or the token "#" that matches only
* numbers.
* <p>
* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
* this method will accept a leading slash in the path.
*
* @param authority the authority to match
* @param path the path to match. * may be used as a wild card for
* any text, and # may be used as a wild card for numbers.
* @param code the code that is returned when a URI is matched
* against the given components. Must be positive.
*/
public void addURI(String authority, String path, int code){
.......
}
//從上面的注解可以知道娄柳,addURI就是用于添加一個(gè)匹配的URi給URIMatch寓辱,第三個(gè)參數(shù)code就是當(dāng)uri匹配的時(shí)候返回的CODE。然后開(kāi)發(fā)人員通過(guò)CODE 來(lái)做相應(yīng)的處理赤拒。
所以如果你想要ContentProvider
能夠接受訪問(wèn)某個(gè)表的請(qǐng)求是就要用addURI
添加匹配規(guī)則秫筏,這樣就其他程序就可以通過(guò)這個(gè)ContentProvider
來(lái)訪問(wèn)該表的數(shù)據(jù)了诱鞠。Are you get it?
在上面的代碼中的query,delete, insert,update
方法中都是利用UriMatch
來(lái)匹配第一個(gè)uri
參數(shù)这敬,然后根據(jù)返回的Code
來(lái)匹配的對(duì)應(yīng)的操作航夺。這里當(dāng)我們匹配到是CODE_CONTACT
則通過(guò)DBHelper
來(lái)操作Contact
表,從這里也可以看出來(lái)一個(gè)ContentProvider
可以對(duì)應(yīng)多個(gè)表崔涂,多個(gè)數(shù)據(jù)阳掐。
-
在服務(wù)端AndroidMainfest.xml文件里面注冊(cè)自定義的ContentProvider
ContentProvider
也是Android
組件之一,所以也需要在AndroidMainfest.xml
文件中聲明才能使用冷蚂。聲明語(yǔ)句如下:
<provider
android:name="com.doris.support.provider.ContactProvider"
android:authorities="com.doris.contact.provider"
android:exported="true" >
</provider>
此處的authorities
是類似于ContactProvider
的訪問(wèn)鏈接或者是可以理解為一個(gè)指針當(dāng)你在uri組合這個(gè)authorities
來(lái)使用的時(shí)候就指向了ContactProvider
缭保。android:exported="true"
這句也是很關(guān)鍵的一句,它表明你同意將這個(gè)Provider
被外部程序訪問(wèn)蝙茶,如果這個(gè)是false
則其他應(yīng)用就不能訪問(wèn)數(shù)據(jù)艺骂,所以一定要是true
。
經(jīng)過(guò)上面兩步隆夯,你就可以在其他程序中訪問(wèn)ContactProvider
了钳恕。至于DBHelper
其實(shí)就是一個(gè)訪問(wèn)數(shù)據(jù)庫(kù)的幫助類。
客戶端
其實(shí)客戶端訪問(wèn)ContentProvder
是通過(guò)ContentResolver
來(lái)訪問(wèn)的蹄衷,這個(gè)類就是通過(guò)URI來(lái)跨進(jìn)程連接ContentProvider
的忧额。所以客戶端要訪問(wèn)需要通過(guò)ContentResolver
來(lái)請(qǐng)求數(shù)據(jù)±⒖冢客戶端示例程序如下:
// 考慮到程序的拓展性宙址,多態(tài)等特性我們先寫一個(gè)抽象的類里面對(duì)ContentResolver訪問(wèn)數(shù)據(jù)進(jìn)行了封裝。
public abstract class BaseContentHelper {
protected Context mContext;
public BaseContentHelper(Context context) {
this.mContext = context.getApplicationContext();
}
protected abstract Uri getUri();
public Cursor query(String[] projection, String whereSql, String[] whereValue, String orderBy) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.query(getUri(), projection, whereSql, whereValue, orderBy);
}
public Uri insert(ContentValues cv) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.insert(getUri(), cv);
}
public int update(ContentValues cv, String where, String[] selectionArgs) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.update(getUri(), cv, where, selectionArgs);
}
public int delete(String whereSQL, String[] whereValue) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.delete(getUri(), whereSQL, whereValue);
}
/**
* clear all record
*
* @return
*/
public int delete() {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.delete(getUri(), null, null);
}
}
從上面的代碼可以看出调卑,增刪查改都是通過(guò)ContentResolver
以及getUri()
傳遞相關(guān)的參數(shù)來(lái)完成的抡砂。
其中getUri()
是有子類實(shí)現(xiàn)用來(lái)提供,子類對(duì)應(yīng)的ContentProvider
的訪問(wèn)Uri的恬涧。
//我們定義一個(gè)ContactManager來(lái)獲取服務(wù)端的數(shù)據(jù)
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.doris.db.BaseContentHelper;
import com.orhanobut.logger.Logger;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class ContactManager extends BaseContentHelper {
//定義ContactProvider的authoritys常量用于拼接URi
public static final String AUTHORITYS = "com.doris.support.provider.ContactProvider";
public static final String URI_BASE = "content://" + AUTHORITYS + "/";
public static final String TABLE_NAME = "contact";
public static final String PERSON_NAME = "name";
public static final String PERSON_TELE = "telephone";
public static final String PERSON_DESCRIPTION = "description";
private static String TAG = ContactManager.class.getSimpleName();
private static Uri mUri = Uri.parse(URI_BASE + "contact"); //最終拼接出來(lái)的URI
private static ContactManager mInstance;
private ContactManager(Context ctx) {
super(ctx);
}
/**
* singelten
*
* @return
*/
public static synchronized ContactManager obtain(Context ctx) {
if (mInstance == null)
mInstance = new ContactManager(ctx);
return mInstance;
}
public List<String> getAllPersonName() {
List<String> allName = new ArrayList<>();
Cursor cursor = query(null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Persion vo = parserPersonVoFromCursor(cursor);
allName.add(vo.getName());
}
cursor.close();
}
return allName;
}
public List<Person> getAllPerson() {
List<Person> allList = new ArrayList<Person>();
Cursor cursor = query(null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Person vo = parserPersonVoFromCursor(cursor);
allList.add(vo);
}
cursor.close();
}
return allList;
}
/**
* CN:根據(jù)name獲取person列表
*
* @param name
* eg:doris
* @return
*/
public List<Person> getPersonList(String name) {
List<Person> allList = new ArrayList<Person>();
Cursor cursor = query(null, PERSON_NAME + "=?", new String[]{name}, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Person vo = parserPersonVoFromCursor(cursor);
allList.add(vo);
}
cursor.close();
}
return allList;
}
private Person parserPersonVoFromCursor(Cursor cursor) {
Person vo = new Person();
vo.setName(cursor.getString(cursor.getColumnIndex(PERSON_NAME)));
vo.setTelephone(cursor.getString(cursor.getColumnIndex(PERSON_TELE)));
vo.setDescription(cursor.getString(cursor.getColumnIndex(PERSON_DESCRIPTION)));
return vo;
}
/**
重寫父類的getUri 返回uri供ContentResolver使用注益。
**/
@Override
protected Uri getUri() {
return mUri;
}
}
上面的BaseContentHelper
封裝了增刪查改,ContactManager
通過(guò)繼承BaseContentHelper
封裝了一些數(shù)據(jù)操作的接口溯捆,比如通過(guò)名字獲得Person
對(duì)象,獲取所有的Person列表丑搔。
其實(shí)上面就把客戶端如何請(qǐng)求數(shù)據(jù)以及服務(wù)端如何提供ContentProvider
給講完了,只要你開(kāi)始用起來(lái)提揍,你就會(huì)發(fā)現(xiàn)其實(shí)很簡(jiǎn)單啤月,只要自定義一個(gè)ContentProvider
并且在AndroidMainfest.xml
文件里注冊(cè)一下就可以被跨程序訪問(wèn)了。 客戶端也只需結(jié)合ContentResolver
以及Uri
便可以訪問(wèn)數(shù)據(jù)了劳跃。 是不是炒雞簡(jiǎn)單谎仲。一開(kāi)始我也搞不懂,但是用了幾次之后刨仑,也知道是怎么回事了郑诺。又一次驗(yàn)證了重在實(shí)踐夹姥,所以還沒(méi)用的快用起來(lái)吧。寫這篇文章有兩個(gè)目的: 1.讓還不熟悉ContentProvider的你能了解如何使用辙诞。 2.給自己留下一個(gè)涉獵的足跡辙售,畢竟好記性不如爛筆頭,以后有疑惑可以翻出來(lái)再看看飞涂。
疑問(wèn)點(diǎn)
-
ContentProvider 何時(shí)創(chuàng)建旦部?
在應(yīng)用一開(kāi)始的時(shí)候,
ContentProvider
就由系統(tǒng)創(chuàng)建较店。在應(yīng)用中士八,無(wú)需直接為它創(chuàng)建一個(gè)實(shí)例。 -
其他程序是如何訪問(wèn)到ContentProvider的泽西?
通過(guò)
ContentResolver
結(jié)合uri來(lái)訪問(wèn)曹铃,當(dāng)ContentResolver
訪問(wèn)某個(gè)uri時(shí)缰趋,系統(tǒng)會(huì)識(shí)別uri解析出authoritys來(lái)匹配對(duì)應(yīng)ContentProvider
. 然后傳遞請(qǐng)求與參數(shù)捧杉。 注意,同一個(gè)安卓系統(tǒng)中秘血,
ContentProvider
的authoritys
必須是唯一的味抖,其實(shí)你很容易就可以知道是不是唯一的,如果你運(yùn)行一個(gè)應(yīng)用灰粮,運(yùn)行不上的時(shí)候仔涩,就很有可能是因?yàn)?code>authoritys沖突了,不過(guò)首先你得看看提示信息粘舟,再做判斷熔脂。其實(shí)
ContentProvider
不僅可以訪問(wèn)數(shù)據(jù)庫(kù)中的數(shù)據(jù),可以是調(diào)用其他程序中的某個(gè)方法然后獲取返回值柑肴。示例如下:
//在ContactProvider里面重寫 call方法
private static final String FUNC_GET_MESSAGE = "func_get_messge";
private static final String MESSAGE = "message";
@Override
public Bundle call(String method, String arg, Bundle extras) {
Bundle ret = null;
if (method.equals(FUNC_GET_MESSAGE)) {
String message = getMessage();
ret = new Bundle();
ret.putString(MESSAGE, message);
}
return ret;
}
客戶端調(diào)用:
//在ContactManager里面添加getMessage方法
private static final String FUNC_GET_MESSAGE = "func_get_messge";
private static final String MESSAGE = "message";
public String getMessage() {
Bundle ret = mContext.getContentResolver().call(getUri(), FUNC_GET_MESSAGE, null, null);
ret.setClassLoader(getClass().getClassLoader());
String list = ret.getString(MESSAGE);
return list;
}
//通過(guò)這種方式可以調(diào)用另一個(gè)應(yīng)用的某個(gè)方法來(lái)獲取某些數(shù)據(jù)霞揉,不過(guò)本質(zhì)上都是獲取一些數(shù)據(jù)。
合理使用
ContentProvider
晰骑,暴露應(yīng)該暴露的接口适秩,敏感接口不要暴露給用戶。可以通過(guò)
notifyChange()
來(lái)通知數(shù)據(jù)的改變硕舆,這個(gè)很有用的秽荞,當(dāng)你通過(guò)ContentProvider
來(lái)訪問(wèn)某個(gè)數(shù)據(jù)時(shí),但是又希望在數(shù)據(jù)改變的時(shí)候監(jiān)聽(tīng)到抚官,那么就可以通過(guò)設(shè)置resolver.registerContentObserver
來(lái)監(jiān)聽(tīng)數(shù)據(jù)的改變.
//方法聲明如下:第一個(gè)是uri是用來(lái)匹配要監(jiān)聽(tīng)的數(shù)據(jù)的扬跋,第二個(gè)參數(shù)是一個(gè)ContentObserver對(duì)象通過(guò)繼承ContentObserver,重寫onChange方法來(lái)再監(jiān)聽(tīng)到數(shù)據(jù)改變的時(shí)候做相應(yīng)的操作凌节。
/**
* Register an observer class that gets callbacks when data identified by a
* given content URI changes.
*
* @param uri The URI to watch for changes. This can be a specific row URI, or a base URI
* for a whole class of content.
* @param notifyForDescendents When false, the observer will be notified whenever a
* change occurs to the exact URI specified by <code>uri</code> or to one of the
* URI's ancestors in the path hierarchy. When true, the observer will also be notified
* whenever a change occurs to the URI's descendants in the path hierarchy.
* @param observer The object that receives callbacks when changes occur.
* @see #unregisterContentObserver
*/
registerContentObserver(@NonNull Uri uri, boolean notifyForDescendents,
@NonNull ContentObserver observer) ;
實(shí)例:
private class SettingsObserver extends ContentObserver {
public SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
String option = uri.getLastPathSegment();
Slog.d(TAG, "onChange, option = " + option);
switch (option) {
...........
}
}
}
在安卓的一些自帶的應(yīng)用中胁住,其實(shí)很多監(jiān)聽(tīng)系統(tǒng)屬性的變化的時(shí)候趁猴,都是通過(guò)ContentProvider
以及監(jiān)聽(tīng)來(lái)做相應(yīng)的處理的。所以你可以運(yùn)用到你的應(yīng)用中彪见。讓你的應(yīng)用更加完美儡司。
許久未更新博客,總覺(jué)得自己知道的不夠高深余指,但是想想每一個(gè)記錄都是為別人為自己積累知識(shí)捕犬,不管簡(jiǎn)單還是復(fù)雜。所以再一次碼起了文字酵镜。希望大家多多指教碉碉,有不對(duì)的提出來(lái),一起學(xué)習(xí)成長(zhǎng)淮韭。O(∩_∩)O哈哈~垢粮。