本文參考了https://blog.csdn.net/qq_35578940/article/details/52445170 在此感謝作者
內容提供者ContentProvider
? ? ? 在Android中如果想將自己應用的數據(多為數據庫中的數據)提供給第三方應用,那么我們只能通過ContentProvider來實現了。
? ContentProvider是應用程序之間共享數據的接口,使用的時候首先自定義一個類繼承ContentProvider,然后重寫query,insert,updata,delete等方法。
ContentProvider?封裝了數據的跨進程傳輸,我們可以直接使用?getContentResolver()?拿到?ContentResolver?進行增刪改查即可。
ContentProvider?以一個或多個表(與在關系型數據庫中的表類似)的形式將數據呈現給外部應用。 行表示提供程序收集的某種數據類型的實例,行中的每個列表示為實例收集的每條數據。
實現一個?ContentProvider?時需要實現以下幾個方法:
onCreate():初始化 provider
query():查詢數據
insert():插入數據到 provider
update():更新 provider 的數據
delete():刪除 provider 中的數據
getType():返回 provider 中的數據的 MIME 類型
注意:?
1.?onCreate()?默認執行在主線程,別做耗時操作,query()?也最好異步執行?
2. 上面的 4 個增刪改查操作都可能會被多個線程并發訪問,因此需要注意線程安全
ContentProvider 與 URI
ContentProvider?使用 URI 標識要操作的數據,這里的內容 URI 主要包括兩部分:
authority:整個提供程序的符號名稱
path:指向表的名稱/路徑
內容 URI 統一的形式就是:
content://authority/path
1
例如:
content://user_dictionary/words
1
當你調用?ContentResolver?方法來訪問?ContentProvider?中的表時,需要傳遞要操作表的 URI。
在通過?ContentResolver?進行數據請求時(比如?contentResolver.insert(uri, contentValues);), 系統會檢查指定 URI 的 authority 信息,然后將請求傳遞給注冊監聽這個 authority 的?ContentProvider?。這個?ContentProvider?可以監聽 URI 想要操作的內容,Android 中為我們提供了?UriMatcher?來解析 URI。
因為內容提供者是四大組件之一,因此必須在AndroidMainfest文件中進行注冊。
AndroidMainfest中注冊:
<provider?android:name=".StudentProvider"3 android:authorities="com.example.contentproviderdemo.StudentProvider">? </provider?>
下面通過一個示例來講解一下ContentProvider,在這個例子中,需要用到SQLite數據庫來存儲數據,定義了一個StudentDAO類,用于進行對SQLite的CRUD操作,這里就不提供數據訪問的源碼了,有興趣的朋友可以在下載源碼查看:
ContentProvider實現:
package com.example.contentproviderdemo;
import com.example.dao.StudentDAO;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
public class StudentProvider extends ContentProvider {
? ? private final String TAG = "main";
? ? private StudentDAO studentDao = null;
? ? private static final UriMatcher URI_MATCHER = new UriMatcher(
? ? ? ? ? ? UriMatcher.NO_MATCH);
? ? private static final int STUDENT = 1;
? ? private static final int STUDENTS = 2;
? ? static {
? ? ? ? //添加兩個URI篩選
? ? ? ? URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider",
? ? ? ? ? ? ? ? "student", STUDENTS);
? ? ? ? //使用通配符#,匹配任意數字
? ? ? ? URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider",
? ? ? ? ? ? ? ? "student/#", STUDENT);? ? ? ?
? ? }
? ? public StudentProvider() {
? ? }? ?
? ? @Override
? ? public boolean onCreate() {
? ? ? ? // 初始化一個數據持久層
? ? ? ? studentDao = new StudentDAO(getContext());
? ? ? ? Log.i(TAG, "---->>onCreate()被調用");
? ? ? ? return true;
? ? }
? ? @Override
? ? public Uri insert(Uri uri, ContentValues values) {
? ? ? ? Uri resultUri = null;
? ? ? ? //解析Uri,返回Code
? ? ? ? int flag = URI_MATCHER.match(uri);
? ? ? ? if (flag == STUDENTS) {
? ? ? ? ? ? long id = studentDao.insertStudent(values);
? ? ? ? ? ? Log.i(TAG, "---->>插入成功, id="+id);
? ? ? ? ? ? resultUri = ContentUris.withAppendedId(uri, id);
? ? ? ? }
? ? ? ? return resultUri;
? ? }
? ? @Override
? ? public int delete(Uri uri, String selection, String[] selectionArgs) {
? ? ? ? int count = -1;
? ? ? ? try {
? ? ? ? ? ? int flag = URI_MATCHER.match(uri);
? ? ? ? ? ? switch (flag) {
? ? ? ? ? ? case STUDENT:
? ? ? ? ? ? ? ? // delete from student where id=?
? ? ? ? ? ? ? ? //單條數據,使用ContentUris工具類解析出結尾的Id
? ? ? ? ? ? ? ? long id = ContentUris.parseId(uri);
? ? ? ? ? ? ? ? String where_value = "id = ?";
? ? ? ? ? ? ? ? String[] args = { String.valueOf(id) };
? ? ? ? ? ? ? ? count = studentDao.deleteStudent(where_value, args);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case STUDENTS:
? ? ? ? ? ? ? ? count = studentDao.deleteStudent(selection, selectionArgs);? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? Log.i(TAG, "---->>刪除成功,count="+count);
? ? ? ? return count;
? ? }
? ? @Override
? ? public int update(Uri uri, ContentValues values, String selection,
? ? ? ? ? ? String[] selectionArgs) {
? ? ? ? int count = -1;
? ? ? ? try {? ? ? ? ? ?
? ? ? ? ? ? int flag = URI_MATCHER.match(uri);
? ? ? ? ? ? switch (flag) {
? ? ? ? ? ? case STUDENT:
? ? ? ? ? ? ? ? long id = ContentUris.parseId(uri);
? ? ? ? ? ? ? ? String where_value = " id = ?";
? ? ? ? ? ? ? ? String[] args = { String.valueOf(id) };
? ? ? ? ? ? ? ? count = studentDao.updateStudent(values, where_value, args);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case STUDENTS:
? ? ? ? ? ? ? ? count = studentDao.updateStudent(values, selection,
? ? ? ? ? ? ? ? ? ? ? ? selectionArgs);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? Log.i(TAG, "---->>更新成功,count="+count);
? ? ? ? return count;
? ? }
? ? @Override
? ? public Cursor query(Uri uri, String[] projection, String selection,
? ? ? ? ? ? String[] selectionArgs, String sortOrder) {
? ? ? ? Cursor cursor = null;
? ? ? ? try {
? ? ? ? ? ? int flag = URI_MATCHER.match(uri);
? ? ? ? ? ? switch (flag) {
? ? ? ? ? ? case STUDENT:
? ? ? ? ? ? ? ? long id = ContentUris.parseId(uri);
? ? ? ? ? ? ? ? String where_value = " id = ?";
? ? ? ? ? ? ? ? String[] args = { String.valueOf(id) };
? ? ? ? ? ? ? ? cursor = studentDao.queryStudents(where_value, args);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case STUDENTS:
? ? ? ? ? ? ? ? cursor = studentDao.queryStudents(selection, selectionArgs);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? Log.i(TAG, "---->>查詢成功,Count="+cursor.getCount());
? ? ? ? return cursor;
? ? }
? ? @Override
? ? public String getType(Uri uri) {
? ? ? ? int flag = URI_MATCHER.match(uri);
? ? ? ? String type = null;
? ? ? ? switch (flag) {
? ? ? ? case STUDENT:
? ? ? ? ? ? type = "vnd.android.cursor.item/student";
? ? ? ? ? ? Log.i(TAG, "----->>getType return item");
? ? ? ? ? ? break;
? ? ? ? case STUDENTS:
? ? ? ? ? ? type = "vnd.android.cursor.dir/students";
? ? ? ? ? ? Log.i(TAG, "----->>getType return dir");
? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? return type;
? ? }
? ? @Override
? ? public Bundle call(String method, String arg, Bundle extras) {
? ? ? ? Log.i(TAG, "------>>"+method);
? ? ? ? Bundle bundle=new Bundle();
? ? ? ? bundle.putString("returnCall", "call被執行了");
? ? ? ? return bundle;
? ? }
}
ContentProvider可以理解為一個Android應用對外開放的接口,只要是符合它所定義的Uri格式的請求,均可以正常訪問執行操作。其他的Android應用可以使用ContentResolver對象通過與ContentProvider同名的方法請求執行,被執行的就是ContentProvider中的同名方法。所以ContentProvider很多對外可以訪問的方法,在ContentResolver中均有同名的方法,是一一對應的
Uri
在Android中,Uri是一種比較常見的資源訪問方式。而對于ContentProvider而言,Uri也是有固定格式的:
<srandard_prefix> ://<authority>/<data_path>/<id>
<srandard_prefix>:ContentProvider的srandard_prefix始終是content://。
<authority>:ContentProvider的名稱。
<data_path>:請求的數據類型。
<id>:指定請求的特定數據。
ContentProvider
ContentProvider也是Android應用的四大組件之一,所以也需要在AndroidManifest.xml文件中進行配置。而且某個應用程序通過ContentProvider暴露了自己的數據操作接口,那么不管該應用程序是否啟動,其他應用程序都可以通過這個接口來操作它的內部數據。
Android附帶了許多有用的ContentProvider,但是本篇博客不會涉及到這些內容的,以后有時間會再講解。Android附帶的ContentProvider包括:
Browser:存儲如瀏覽器的信息。
CallLog:存儲通話記錄等信息。
Contacts:存儲聯系人等信息。
MediaStore:存儲媒體文件的信息。
Settings:存儲設備的設置和首選項信息。
在Android中,如果要創建自己的內容提供者的時候,需要擴展抽象類ContentProvider,并重寫其中定義的各種方法。然后在AndroidManifest.xml文件中注冊該ContentProvider即可。
ContentProvider是內容提供者,實現Android應用之間的數據交互,對于數據操作,無非也就是CRUD而已。下面是ContentProvider必須要實現的幾個方法:
onCreate():初始化提供者。
query(Uri, String[], String, String[], String):查詢數據,返回一個數據Cursor對象。
insert(Uri, ContentValues):插入一條數據。
update(Uri, ContentValues, String, String[]):根據條件更新數據。
delete(Uri, String, String[]):根據條件刪除數據。
getType(Uri) 返回MIME類型對應內容的URI。
除了onCreate()和getType()方法外,其他的均為CRUD操作,這些方法中,Uri參數為與ContentProvider匹配的請求Uri,剩下的參數可以參見SQLite的CRUD操作,基本一致,SQLite的內容在另外一篇博客中有講解:Android--數據持久化之SQLite。、
Tips:還有兩個非常有意思的方法,必須要提一下,call()和bulkInsert()方法,使用call,理論上可以在ContentResolver中執行ContentProvider暴露出來的任何方法,而bulkInsert()方法用于插入多條數據。
在ContentProvider的CRUD操作,均會傳遞一個Uri對象,通過這個對象來匹配對應的請求。那么如何確定一個Uri執行哪項操作呢?需要用到一個UriMatcher對象,這個對象用來幫助內容提供者匹配Uri。它所提供的方法非常簡單,僅有兩個:
void addURI(String authority,String path,int code):添加一個Uri匹配項,authority為AndroidManifest.xml中注冊的ContentProvider中的authority屬性;path為一個路徑,可以設置通配符,#表示任意數字,*表示任意字符;code為自定義的一個Uri代碼。
int match(Uri uri):匹配傳遞的Uri,返回addURI()傳遞的code參數。
在創建好一個ContentProvider之后,還需要在AndroidManifest.xml文件中對ContentProvider進行配置,使用一個節點,一般只需要設置兩個屬性即可訪問,一些額外的屬性就是為了設置訪問權限而存在的,后面會詳細講解:
android:name:provider的響應類。
android:authorities:Provider的唯一標識,用于Uri匹配,一般為ContentProvider類的全名。
getType()中的MIME
MIME類型就是設定某種擴展名的文件用一種應用程序來打開的方式類型。在ContentProvider中的getType方法,返回的就是一個MIME類型的字符串。如果支持需要使用ContentProvider來訪問數據,就上面這個Demo,getType()完全可以只返回一個Null,并不影響效果,但是覆蓋ContentProvider的getType方法對于用new Intent(String action, Uri uri)方法啟動activity是很重要的,如果它返回的MIME type和activity在中定義的data的MIME type不一致,將造成activity無法啟動。這就涉及到Intent和Intent-filter的內容了,以后有機會再說,這里不再詳解。
從官方文檔了解到,getType返回的字符串,如果URI針對的是單條數據,則返回的字符串以vnd.android.cursor.item/開頭;如果是多條數據,則以vnd.adroid.cursor.dir/開頭。