Framework 源碼解析知識梳理(6) - ContentProvider 源碼解析

一泽裳、前言

Framework 源碼解析知識梳理(5) - startService 源碼分析 中,我們分析了Service啟動的內(nèi)部實(shí)現(xiàn)原理剥扣,今天刻盐,我們趁熱打鐵,看一下Android中的四大組件中另一個組件ContentProvider导街。

二、源碼解析

在分析之前,先上一張整個的流程圖便脊,大家在后面繞暈了以后,可以參考這張圖進(jìn)行對照:


2.1 ContentResolver 獲取過程

在使用ContentProvider來進(jìn)行數(shù)據(jù)的增刪改查時光戈,第一步就是要通過getContentResolver()哪痰,獲得一個ContentResolver對象,該方法實(shí)際上調(diào)用了基類中的mBase變量久妆,也就是ContextImpl中的getContentResolver()方法晌杰,并返回它其中的mContentResolver變量。

    //ContextImpl.java

    @Override
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

而該mContentResolver是在ContextImpl的構(gòu)造函數(shù)中初始化的筷弦,這其實(shí)和我們之前在 插件化知識梳理(9) - 資源的動態(tài)加載示例及源碼分析 中所分析的getResources()方法返回一個Resources對象的過程類似肋演。

    private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
        //...
        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
    }

這上面的ApplicationContentResolverContentResolver的子類:

2.2 簡單的查詢過程

現(xiàn)在抑诸,我們以ContentResolver所提供的query方法為例,對ContentProvider的調(diào)用過程進(jìn)行一次簡單的走讀:

    public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
            @Nullable String selection, @Nullable String[] selectionArgs,
            @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
        Preconditions.checkNotNull(uri, "uri");
        //1.獲取ContentProvider接口爹殊。
        IContentProvider unstableProvider = acquireUnstableProvider(uri);
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        Cursor qCursor = null;
        try {
            long startTime = SystemClock.uptimeMillis();

            ICancellationSignal remoteCancellationSignal = null;
            if (cancellationSignal != null) {
                cancellationSignal.throwIfCanceled();
                //2.創(chuàng)建取消信號量蜕乡。
                remoteCancellationSignal = unstableProvider.createCancellationSignal();
                cancellationSignal.setRemote(remoteCancellationSignal);
            }
            try {
                //3.調(diào)用IContentProvider的query方法。
                qCursor = unstableProvider.query(mPackageName, uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            } catch (DeadObjectException e) {
                //如果發(fā)生了異常梗夸,那么銷毀unstableProvider對象层玲,重新獲取一個stableProvider對象。
                unstableProviderDied(unstableProvider);
                stableProvider = acquireProvider(uri);
                //如果stableProvider對象還是為空反症,那么直接返回空辛块。
                if (stableProvider == null) {
                    return null;
                }
                //調(diào)用stableProvider進(jìn)行查詢。
                qCursor = stableProvider.query(mPackageName, uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            }
            if (qCursor == null) {
                return null;
            }

            // Force query execution.  Might fail and throw a runtime exception here.
            qCursor.getCount();
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);

            //用CursorWrapperInner把qCursor包裹起來铅碍。
            CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
                    stableProvider != null ? stableProvider : acquireProvider(uri));
            stableProvider = null;
            qCursor = null;
            return wrapper;
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        } finally {
            if (qCursor != null) {
                qCursor.close();
            }
            if (cancellationSignal != null) {
                cancellationSignal.setRemote(null);
            }
            if (unstableProvider != null) {
                releaseUnstableProvider(unstableProvider);
            }
            if (stableProvider != null) {
                releaseProvider(stableProvider);
            }
        }
    }

我們對上面的流程進(jìn)行一個簡單的梳理:

  • 通過acquireUnstableProvider獲取一個unstableProvider實(shí)例润绵,按字面上的翻譯它是一個不穩(wěn)定的ContentProvider
  • 通過第一步中獲取的unstableProvider實(shí)例進(jìn)行查詢胞谈,如果查詢成功尘盼,那么得到qCursor對象;如果ContentProvider所對應(yīng)的進(jìn)程已經(jīng)死亡呜魄,那么將會釋放unstableProvider對象悔叽,再通過調(diào)用acquireProvider方法重新得到一個stableProvider,它和unstableProvider相同爵嗅,都是實(shí)現(xiàn)了IContentProvider接口娇澎,之后在通過它來查詢得到qCursor
  • 把第二步中獲得的qCursorCursorWrapperInner包裹起來睹晒,這里需要注意的是第二個參數(shù)趟庄,如果是通過unstableProvider查詢得到的qCursor,那么將需要調(diào)用acquireProvider伪很,并將返回值傳入戚啥。

那么,我們接下來就要分析通過acquireUnstableProvider锉试、acquireProvider獲取IContentProvider的過程猫十。

2.3 IContentProvider 獲取過程

首先,通過acquireUnstableProvider方法根據(jù)Uri中的authority字段呆盖,調(diào)用acquireUnstableProvider(Context c, String auth)方法:


該方法是由我們前面看到的ApplicationContentResolver所實(shí)現(xiàn)的:

可以看到拖云,這里調(diào)用了mMainThreadacquireProvider方法,它實(shí)際上是一個ActivityThread實(shí)例应又,其實(shí)現(xiàn)為:

       public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        //首先從緩存中獲取宙项,如果獲取到就直接返回。
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
            return provider;
        }

        IActivityManager.ContentProviderHolder holder = null;
        try {
            //如果緩存當(dāng)中沒有株扛,那么首先通過AMS進(jìn)行獲取尤筐。
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), auth, userId, stable);
        } catch (RemoteException ex) {
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
            return null;
        }

        //根據(jù)返回的holder信息進(jìn)行安裝汇荐。
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

這里,首先會去緩存中查找IContentProvider盆繁,如果沒有找到掀淘,那么在調(diào)用AMS的方法去查找,獲取一個ContentProviderHolder對象改基。

2.3.1 調(diào)用者進(jìn)程不存在緩存的情況

在這種情況下面繁疤,會執(zhí)行兩步操作:

  • 第一步:通過ActivityManagerService獲取ContentProviderHolder
  • 第二步:通過返回的ContentProviderHolder中的信息進(jìn)行安裝

第一步,通過 ActivityManagerService 獲取 ContentProviderHolder

這里我們先假設(shè)沒有緩存的情況秕狰,通過 Framework 源碼解析知識梳理(1) - 應(yīng)用進(jìn)程與 AMS 的通信實(shí)現(xiàn) 中學(xué)到的知識,我們知道它最終會調(diào)用到ActivityManagerService的下面這個方法:


接下來最終會調(diào)用到getContentProviderImpl方法返回一個ContentProviderHolder對象躁染,這個方法比較長鸣哀,就不貼代碼了,直接說結(jié)論吞彤,這里會分為以下幾種情況:

(a) ContentProvider 所在進(jìn)程已經(jīng)啟動我衬,并且已經(jīng)該 ContentProvider 已經(jīng)被安裝

這種情況下,直接返回該ContentProviderHolder即可:


(b) ContentProvider 所在進(jìn)程已經(jīng)啟動饰恕,但是該 ContentProvider 沒有被安裝

此時挠羔,就需要通過ApplicationThread對象,再和ContentProvider所在的進(jìn)程進(jìn)行交互埋嵌,以返回一個ContentProviderHolder實(shí)例:


經(jīng)過Binder通信破加,那么最終會調(diào)用到ContentProvider所在進(jìn)程的下面這個方法:

這里面調(diào)用有調(diào)用了內(nèi)部的installContentProviders方法:

這里的操作分為兩步:

  • 安裝:根據(jù)傳過來的List<ProviderInfo>對象,通過installProvider方法進(jìn)行安裝雹嗦,并將結(jié)果存放在List<ContentProviderHolder>列表中范舀。
  • 發(fā)布:將安裝的結(jié)果,再通過一次消息傳遞了罪,返回給ActivityManagerService锭环。

(b-1) 安裝過程

在這一步當(dāng)中,傳入的第二個參數(shù)holdernull泊藕,因此會根據(jù)Provider的名字辅辩,動態(tài)地加載該類,并調(diào)用它的attachInfo方法:


我們上面的有兩個Provider

  • localProvider娃圆,類型為ContentProvider
  • provider玫锋,類型為Transport

provider是通過localProvidergetIContentProvider方法獲得的,它是ContentProvider的一個內(nèi)部類踊餐,它的作用就是作為ContentProvider在遠(yuǎn)程調(diào)用者中的一個代理對象景醇,也就是說,ContentProvider的使用者是通過獲取ContentProvider所在進(jìn)程的一個代理類Transport吝岭,再通過這個Transport對象調(diào)用到ContentProvider進(jìn)行查詢的:


接下來三痰,還會去調(diào)用localProviderattachInfo方法吧寺,這里面會初始化權(quán)限相關(guān)的信息,最終會執(zhí)行ContentProvideronCreate()方法:

假設(shè)上面我們獲得的localProvider不為空散劫,那么會執(zhí)行下面的邏輯:

這里面稚机,我們會生成一個ProviderClientRecord對象,其內(nèi)部包含了下面幾個變量:

  • mNamesContentProvider對象的authority
  • mProvider:遠(yuǎn)程代理對象
  • mLocalProvider:本地對象
  • mHolder:返回給AMS的數(shù)據(jù)結(jié)構(gòu)获搏,AMS再會把它返回給ContentProvider的調(diào)用者赖条,mHolder的類型為IActivityManager.ContentProviderHolder,其內(nèi)部包含的數(shù)據(jù)結(jié)構(gòu)為:

關(guān)于ContentProviderHolderProviderClientRecord常熙,其繼承族譜如下圖所示:


(b-2) 發(fā)布過程

發(fā)布過程纬乍,其實(shí)就是調(diào)用了ActivityManagerServicepublishContentProviders方法,將在ContentProvider擁有者所創(chuàng)建的List<ContentProviderHolder>保存起來:

(c) ContentProvider 所在進(jìn)程沒有啟動

在這種情況下裸卫,就需要先通過startProcessLocked啟動ContentProvider所在進(jìn)程仿贬,等待進(jìn)程啟動完畢之后,再進(jìn)行安裝墓贿。

第二步茧泪,利用返回的 ContentProviderHolder 中的信息,進(jìn)行安裝

在第一步中聋袋,通過ActivityManagerService队伟,我們最終獲得了ContentProviderHolder對象,接下來就是調(diào)用installProvider方法幽勒,這里和我們之前在第一步中的(b-1)中所看到的installProvider其實(shí)是同一個方法嗜侮,區(qū)別在于,之前我們分析的installProvider傳入的holder參數(shù)為空代嗤,下面棘钞,我們就來看一下當(dāng)holder參數(shù)不為空時最終會走到下面的邏輯:


installProviderAuthoritiesLocked方法中,會將它緩存在mProviderMap當(dāng)中干毅。

2.3.2 調(diào)用者進(jìn)程存在緩存的情況

當(dāng)調(diào)用者進(jìn)程存在緩存時宜猜,會調(diào)用acquireExistingProvider方法,這里面就會通過我們前面所看到的mProviderMap進(jìn)行查找:

三硝逢、小結(jié)

這篇文章拖了一個星期姨拥,總算是完成了,源碼看的真的頭暈渠鸽,其實(shí)最終看下來叫乌,發(fā)現(xiàn)整個調(diào)用過程,和我們之前分析過的 Framework 源碼解析知識梳理(5) - startService 源碼分析 很類似徽缚,究其根本憨奸,就是調(diào)用者進(jìn)程、所有者進(jìn)程和ActivityManagerService進(jìn)程的三方調(diào)用凿试。


更多文章排宰,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末似芝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子板甘,更是在濱河造成了極大的恐慌党瓮,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盐类,死亡現(xiàn)場離奇詭異寞奸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)在跳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門枪萄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硬毕,你說我怎么就攤上這事呻引。” “怎么了吐咳?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長元践。 經(jīng)常有香客問我韭脊,道長,這世上最難降的妖魔是什么单旁? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任沪羔,我火速辦了婚禮,結(jié)果婚禮上象浑,老公的妹妹穿的比我還像新娘蔫饰。我一直安慰自己,他們只是感情好愉豺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布篓吁。 她就那樣靜靜地躺著,像睡著了一般蚪拦。 火紅的嫁衣襯著肌膚如雪杖剪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天驰贷,我揣著相機(jī)與錄音盛嘿,去河邊找鬼。 笑死括袒,一個胖子當(dāng)著我的面吹牛次兆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锹锰,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼芥炭,長吁一口氣:“原來是場噩夢啊……” “哼漓库!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚤认,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤米苹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砰琢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蘸嘶,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年陪汽,在試婚紗的時候發(fā)現(xiàn)自己被綠了训唱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡挚冤,死狀恐怖况增,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情训挡,我是刑警寧澤澳骤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站澜薄,受9級特大地震影響为肮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肤京,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一颊艳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忘分,春花似錦棋枕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舟山,卻和暖如春绸狐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背累盗。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工寒矿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人若债。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓符相,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子啊终,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容