Android四大組件之ContentProvider

1.簡介

They encapsulate data and provide it to applications through the single ContentResolver interface. A content provider is only required if you need to share data between multiple applications.

  • 用于應(yīng)用間數(shù)據(jù)共享,Android內(nèi)置了電話簿、短信、日歷事件、媒體庫等的內(nèi)容提供器共享數(shù)據(jù)
  • 可自行實(shí)現(xiàn)共享數(shù)據(jù)庫、文件、網(wǎng)絡(luò)資源,如為其它應(yīng)用提供數(shù)據(jù)庫讀寫數(shù)據(jù)、某些天氣服務(wù)應(yīng)用給其它app提供天氣共享數(shù)據(jù)等,以及手機(jī)間的數(shù)據(jù)克隆也是通過內(nèi)容提供器方式讀取后傳輸寫入新設(shè)備
  • ContentProvider本質(zhì)上是提供了一套跨進(jìn)程請(qǐng)求/數(shù)據(jù)共享框架,對(duì)數(shù)據(jù)的真實(shí)處理操作由應(yīng)用自身實(shí)現(xiàn)


    ContentProvider

三劍客:

  • ContentProvider內(nèi)容提供者
    對(duì)外提供數(shù)據(jù),其他應(yīng)用可以通過ContentProvider對(duì)你應(yīng)用中的數(shù)據(jù)進(jìn)行添刪改查
  • ContentResolver內(nèi)容解析者
    按一定規(guī)則訪問內(nèi)容提供者的數(shù)據(jù)
  • ContentObserver內(nèi)容監(jiān)聽器
    監(jiān)聽指定Uri引起的變化,當(dāng)ContentObserver所觀察的Uri發(fā)生變化時(shí),便會(huì)觸發(fā)

2.使用

(1)URI簡介

統(tǒng)一資源標(biāo)志符(Uniform Resource Identifier,縮寫:URI)
一種統(tǒng)一約定的格式化字符串


ContentProvider通過URI區(qū)分不同的數(shù)據(jù),部分解析方法如下
Uri uri = Uri.parse("content://sms/inbox"); // URI對(duì)應(yīng)的封裝類
List<String> pathSegments = uri.getPathSegments(); // 獲取URI的Segment列表
String dtbName = pathSegments.get(0); // 按序讀取Segment值,此處返回值為inbox,一般authority字段之后跟著具體的表名,可解析讀取后進(jìn)行自身邏輯處理
Android中內(nèi)置了許多常用URI,比如

管理聯(lián)系人的Uri:
ContactsContract.Contacts.CONTENT_URI 
管理聯(lián)系人的電話的Uri:
ContactsContract.CommonDataKinds.Phone.CONTENT_URI 
管理聯(lián)系人的Email的Uri:
ContactsContract.CommonDataKinds.Email.CONTENT_URI 
發(fā)送箱中的短信URI:
Content://sms/outbox
收信箱中的短信URI:
Content://sms/sent
草稿中的短信URI:
Content://sms/draft
(2)舉例1:讀取電話簿
  • 注意先請(qǐng)求讀取聯(lián)系人權(quán)限
try (Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
        null, null, null, null)) {
    while (cursor.moveToNext()) {
        // 姓名
        String displayName = cursor.getString(
                cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        // 手機(jī)號(hào)
        String number = cursor.getString(
                cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        Log.d(TAG, "readContacts: displayName = " + displayName + " --- number = " + number);
    }
} catch (Exception e) {
    e.printStackTrace();
}
(3)舉例2:監(jiān)聽設(shè)置變化

設(shè)置APP中的開關(guān)會(huì)將設(shè)置值寫入xml文件,可通過ContentResolver進(jìn)行監(jiān)聽
兩大方法:
context.getContentResolver().notifyChange(uri, null) // 通知所有uri數(shù)據(jù)變更
context.getContentResolver().registerContentObserver(uri, boolean notifyForDescendants, contentObserver) // 監(jiān)聽消息
registerContentObserver()方法第二個(gè)參數(shù)官方注釋如下:由于URI有多段,用于確認(rèn)匹配URI的精度
When false, the observer will be notified whenever a change occurs to the exact URI specified by uri or to one of the URI's ancestors in the path hierarchy.
當(dāng)傳false 時(shí),只要uri指定的確切 URI 或路徑層次結(jié)構(gòu)中 URI 的祖先之一發(fā)生更改,就會(huì)通知觀察者。
When true, the observer will also be notified whenever a change occurs to the URI's descendants in the path hierarchy.
傳true時(shí),只要路徑層次結(jié)構(gòu)中 URI 的后代發(fā)生更改,觀察者也會(huì)收到通知

假設(shè)我們當(dāng)前需要觀察的Uri為content://com.me.test/student,如果發(fā)生數(shù)據(jù)變化的Uri為
content://com.me.test/student/schoolchild,當(dāng)notifyForDescendents為false,那么該ContentObserver會(huì)監(jiān)聽不到
但是當(dāng)notifyForDescendents為ture,能捕捉該Uri的數(shù)據(jù)變化。

具體讀取邏輯 Android系統(tǒng)屬性和設(shè)置項(xiàng)讀取 可參考第二節(jié)

(4)舉例3:實(shí)現(xiàn)ContentProvider

AndroidManifest配置組件信息

<provider
    android:name="com.me.test.server.storagemanager.MyContentProvider"
    android:readPermission="com.me.test.permission.READ_PROVIDER"
    android:writePermission="com.me.test.permission.WRITE_PROVIDER"
    android:authorities="com.me.test.provider"
    android:enabled="true"
    android:exported="true" />

可借助UriMatcher匹配對(duì)應(yīng)的入?yún)RI,也可以將傳入的URI toString后自行處理URI字符串

public class MyContentProvider extends ContentProvider {
    private static UriMatcher uriMatcher;

    @Nullable
    @Override
    public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
        switch (method) {
            case "xxx":
            default:
        }
        return null;
    }

    @Override
    public boolean onCreate() {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.me.test.provider", "table1", 1);
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return uri;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                //查詢 table1 表中的所有數(shù)據(jù)
                break;
        }
        Cursor cursor = null;
        // 實(shí)現(xiàn)數(shù)據(jù)操作邏輯...
        return cursor;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        return 0;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }
}

外部應(yīng)用操作數(shù)據(jù)

ContentResolver cr = context.getContentResolver();
cr.query();
cr.insert();  // 單條插入
cr.bulkInsert();  // 批量插入
cr.update();
cr.delete();
cr.call();  // 可調(diào)用call方法來調(diào)用ContentProvider的自定義方法,call有一個(gè)字符串用來標(biāo)識(shí)方法名,入?yún)⒊鰠⑼ㄟ^Bundle傳遞
cr.getType();  // 根據(jù)傳入的內(nèi)容URI返回相應(yīng)的數(shù)據(jù),可用于獲取某些給外部應(yīng)用預(yù)知的數(shù)據(jù),

3.原理

通過IBinder實(shí)現(xiàn)通信過程
getContentResolver獲得到的是ApplicationContentResolver(在ContextImpt中實(shí)現(xiàn))
Client端ApplicationContentResolver使用ContentProviderProxy作為IBinder的Proxy(ContentProviderNative中實(shí)現(xiàn))
Provider端通過Transport作為IBinder的實(shí)現(xiàn)端(ContentProvider中實(shí)現(xiàn))
getContentResolve->ApplicationContentResolver->ContentProviderProxy<===IBidner====>Transport->NameProvider

注意ContentProvider onCreate()執(zhí)行在Application onCreate()之前,避免ContentProvider創(chuàng)建時(shí)對(duì)其它還未初始化對(duì)象有依賴,否則可能導(dǎo)致crash

MyApplication: Application attachBaseContext()
MyApplication: MyContentProvider onCreate()
MyApplication: MyContentProvider attachInfo()
MyApplication: Application onCreate()

部分Android庫如Picasso、LeakCanary將自身初始化操作放到ContentProvider onCreate()中自動(dòng)初始化

參考

Android探索之ContentProvider熟悉而又陌生的組件
對(duì)ContentProvider中g(shù)etType方法的一點(diǎn)理解
LeakCanary源碼分析以及ContentProvider的優(yōu)化方案

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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