概述
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的流程,感謝您的閱讀。