深入分析ContentProvider

原文地址在博客圓肴裙,已經(jīng)不用了跑筝,遷移過來。

ContentProvider是Android四大組件之一推励,承擔著跨進程數(shù)據(jù)訪問的重要職責。本文就從一次ContentProvider訪問入手肉迫,分析下它是怎么完成跨進程數(shù)據(jù)訪問的验辞。

既然是跨進程,那就必須有一個客戶端進程和一個ContentProvider進程喊衫,我們先從客戶端進程分析跌造,看它如何訪問ContentProvider進程。以Query操作為例族购,一般情況下壳贪,當我們需要訪問ContentProvider的時候一般都會執(zhí)行這么一句:

getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);

ContentResolver是什么陵珍?Google的解釋是“This class provides applications access to the content model”。實際上我們正是通過ContentResolver來獲取一個IContentProvider對象违施,通過IContentProvider對象我們盡可以進行IPC通訊了互纯。getContentResolver()方法定義Context類中,實際上Context是一個抽象類醉拓,在客戶端應用程序中getContext()實際上返回的是一個ContextImp對象伟姐,getContentResolver()方法就定義在ContextImp.java中,并且最終返回ContextImp的內(nèi)部類ApplicationContentResolver亿卤,從名字上看這是一個Application級別的對象。
ApplicationContentResolver我們稍后再說鹿霸,先看下query方法都干了什么:

public final Cursor query(final Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder,
            CancellationSignal cancellationSignal) {
        IContentProvider unstableProvider = acquireUnstableProvider(uri);
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        try {
            long startTime = SystemClock.uptimeMillis();

            ICancellationSignal remoteCancellationSignal = null;
            if (cancellationSignal != null) {
                cancellationSignal.throwIfCanceled();
                remoteCancellationSignal = unstableProvider.createCancellationSignal();
                cancellationSignal.setRemote(remoteCancellationSignal);
            }
            Cursor qCursor;
            try {
                qCursor = unstableProvider.query(uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            } catch (DeadObjectException e) {
                // The remote process has died...  but we only hold an unstable
                // reference though, so we might recover!!!  Let's try!!!!
                // This is exciting!!1!!1!!!!1
                unstableProviderDied(unstableProvider);
                stableProvider = acquireProvider(uri);
                if (stableProvider == null) {
                    return null;
                }
                qCursor = stableProvider.query(uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            }
            if (qCursor == null) {
                return null;
            }
            // force query execution
            qCursor.getCount();
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
            // Wrap the cursor object into CursorWrapperInner object
            CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
                    stableProvider != null ? stableProvider : acquireProvider(uri));
            stableProvider = null;
            return wrapper;
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        } finally {
            if (unstableProvider != null) {
                releaseUnstableProvider(unstableProvider);
            }
            if (stableProvider != null) {
                releaseProvider(stableProvider);
            }
        }
    }

上面的代碼有四個關(guān)鍵步驟:

1.acquireUnstableProvider
2.unstableProvider.query(......)
3.qCursor.getCount();
4.return new CursorWrapperInner(......)

接下來我們一步一步分析這四個關(guān)鍵步驟排吴。

第一步:acquireUnstableProvider

乍看上去感覺怪怪的,好端端的為什么加上了一個Unstable的標簽?難道還有stable的不成懦鼠?事實確實如此钻哩,我們知道此時的ContentResolver實際上是一個ApplicationContentResovler對象,來看下ApplicationContentResovler

private static final class ApplicationContentResolver extends ContentResolver {
        private final ActivityThread mMainThread;
        private final UserHandle mUser;

        public ApplicationContentResolver(
                Context context, ActivityThread mainThread, UserHandle user) {
            super(context);
            mMainThread = Preconditions.checkNotNull(mainThread);
            mUser = Preconditions.checkNotNull(user);
        }

        @Override
        protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context, auth, mUser.getIdentifier(), true);
        }

        @Override
        protected IContentProvider acquireExistingProvider(Context context, String auth) {
            return mMainThread.acquireExistingProvider(context, auth, mUser.getIdentifier(), true);
        }

        @Override
        public boolean releaseProvider(IContentProvider provider) {
            return mMainThread.releaseProvider(provider, true);
        }

        @Override
        protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c, auth, mUser.getIdentifier(), false);
        }

        @Override
        public boolean releaseUnstableProvider(IContentProvider icp) {
            return mMainThread.releaseProvider(icp, false);
        }

        @Override
        public void unstableProviderDied(IContentProvider icp) {
            mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
        }
    }

實際上肛冶,是否是stable的街氢,都將調(diào)用ActivityThread的acquireProvider方法,區(qū)別就是最后的一個參數(shù)boolean stable睦袖。這個機制是API 16引入的珊肃,文章的最后會對此進行說明。現(xiàn)在我們只要知道它最終走到了ActivityThread的acquireProvider就可以了馅笙。在ActivityThread的acquireProvider方法中伦乔,我們首先會去acquireExistingProvider,從字面上就可以看出這是從一個類緩存的地方讀取已經(jīng)保存的ContentProvider對象董习,如果不存在烈和,就會調(diào)用ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);從這兒開始,ActivityManagerService就要登場了皿淋,上面的代碼最終會通過binder通信調(diào)用ActivityManagerService的getContentProviderImpl招刹,這塊的邏輯比較復雜,涉及到了Android組件啟動的過程窝趣,我們只需知道客戶端調(diào)用會阻塞在ActivityManagerNative.getDefault().getContentProvider疯暑,ActivityManagerService啟動目標ContentProvider進程后(如果ContentProvider進程已經(jīng)存在則不必重啟),返回一個目標ContentProvider的實例高帖。在這兒需要說明的是缰儿,ContentProvider可以再Manifest中配置一個叫做android:multiprocess的屬性,默認值是false散址,表示ContentProvider是單例的乖阵,無論哪個客戶端應用的訪問都將是一個ContentProvider對象(當然宣赔,必須是同一個ContentProvider,即Uri或者Authority name是一個)瞪浸,如果設(shè)為true儒将,系統(tǒng)會為每一個訪問該ContentProvider的進程創(chuàng)建一個實例。因為android:multiprocess的默認值是false对蒲,所以我們在寫自己的ContentProvider的時候還是要注意并發(fā)的情況钩蚊。

扯得有點遠了,回到我們之前步驟蹈矮,我們已經(jīng)得到了ContentProvider的實例砰逻,這個時候第一步就完成了,接下來看第二步泛鸟。

第二步:unstableProvider.query(......)

unstableProvider實際上是IContentProvider實例蝠咆,IContentProvider是進行IPC通訊的接口,這個query實際上調(diào)用的是目標ContentProvider中的query方法北滥,當然刚操,在真正調(diào)用目標ContentProvider的query方法之前,還需要經(jīng)過enforceReadPermission方法再芋,這一步主要是看下該ContentProvier有沒有export菊霜,讀寫權(quán)限等等(enforceReadPermission方法只判斷讀權(quán)限)。隨后執(zhí)行query方法济赎,并且返回一個cursor對象鉴逞。

以上就是第二步的大致邏輯,不過不要以為這么簡單就結(jié)束了联喘。IContentProvider的query可是跨進程的华蜒,我們知道ContentProvider的query方法可是五花八門,有訪問數(shù)據(jù)庫返回SQLiteCursor的豁遭,有返回MatrixCursor的等等叭喜,那么IContentProvider返回的那個cursor到底是什么呢?我們來看下IContentProvider的這個IPC通信到底是怎么回事

IPC通信需要兩端蓖谢,對于我們的例子捂蕴,這兩端分別是ContentProviderProxy和ContentProviderNative,首先會執(zhí)行ContentProviderProxy的query方法闪幽,然后通過binder通信執(zhí)行ContentProviderNative的onTransact方法啥辨。ContentProviderProxy的query方法有一下五個主要步驟:

  1. new一個BulkCursorToCursorAdaptor對象——adaptor
  2. 填充data用于binder通信
  3. 調(diào)用mRemote.transact,這是一個阻塞的過程盯腌,直到ContentProviderNative的onTransact方法返回
  4. 讀取reply數(shù)據(jù)溉知,new一個BulkCursorDescriptor并以此初始化adaptor
  5. return adaptor

ContentProviderNative的onTransact會調(diào)用ContentProvider的query方法,并根據(jù)query返回的cursor初始化一個CursorToBulkCursorAdaptor對象,最終將BulkCursorDescriptor對象寫入reply中级乍。

至此我們知道了舌劳,不管我們的ContentProvider query方法返回的到底是什么樣的cursor,最終在客戶端進程都將會被封裝在一個BulkCursorToCursorAdaptor對象中玫荣,那么這個BulkCursorToCursorAdaptor對象是不是就是我們在客戶端調(diào)用query返回的最終類型呢甚淡?別急,往下看捅厂。

第三步:qCursor.getCount();

這一步看似雞肋贯卦,實際上涉及到了SQLiteCursor的一個設(shè)計要點,那就是SQLiteCursor的內(nèi)存共享焙贷。getCount會調(diào)用SQLiteCursor的fillWindow撵割,在以后的文章中我會在講到SQLiteCursor,在此我們只要知道它是強制執(zhí)行數(shù)據(jù)庫query就可以了辙芍。

第四步:return new CursorWrapperInner(......)

哈睁枕,看到了吧,我們在客戶端調(diào)用query最終返回的是一個CursorWrapperInner類型沸手,它是ContentResolver的一個內(nèi)部類。實際上我們常用的getCount注簿,onMove等一些列方法都是通過BulkCursorToCursorAdaptor和CursorToBulkCursorAdaptor的交互實現(xiàn)的契吉。

到此,ContentProvider的訪問流程就結(jié)束了诡渴,下面說一下開頭買的坑:unstable和stable的ContentProvider捐晶。

在4.1之前,我們都可能會遇到過這樣的場景妄辩,我們的應用程序訪問了ContentProvider惑灵,但是這個ContentProvider意外掛了,這個時候我們的應用程序也將被連帶殺死眼耀!這是Android處于對數(shù)據(jù)安全的考慮而做的決定英支,不過貌似Google也感覺這樣的方式不太友好,所以在4.1以后提出了stable和unstable的概念哮伟。對于ContentResolver的query方法干花,我們將默認使用unstable的ContentProvider±慊疲看看下面的代碼

Cursor qCursor;
            try {
                qCursor = unstableProvider.query(uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            } catch (DeadObjectException e) {
                // The remote process has died...  but we only hold an unstable
                // reference though, so we might recover!!!  Let's try!!!!
                // This is exciting!!1!!1!!!!1
                unstableProviderDied(unstableProvider);
                stableProvider = acquireProvider(uri);
                if (stableProvider == null) {
                    return null;
                }
                qCursor = stableProvider.query(uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
            }

上面是ContentResolver query中的一部分池凄,可以看出unstable ContentProvider在query過程中如果發(fā)生了DeadObjectExeption則會被捕獲,進而重新獲取一個stable的ContentProvider鬼廓。其實深入分析stable和unstable的ContentProvider還會有很多內(nèi)容肿仑,以后有時間再說。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尤慰,隨后出現(xiàn)的幾起案子馏锡,更是在濱河造成了極大的恐慌,老刑警劉巖割择,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眷篇,死亡現(xiàn)場離奇詭異,居然都是意外死亡荔泳,警方通過查閱死者的電腦和手機蕉饼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玛歌,“玉大人昧港,你說我怎么就攤上這事≈ё樱” “怎么了创肥?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長值朋。 經(jīng)常有香客問我叹侄,道長,這世上最難降的妖魔是什么昨登? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任趾代,我火速辦了婚禮,結(jié)果婚禮上丰辣,老公的妹妹穿的比我還像新娘撒强。我一直安慰自己,他們只是感情好笙什,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布飘哨。 她就那樣靜靜地躺著,像睡著了一般琐凭。 火紅的嫁衣襯著肌膚如雪芽隆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天淘正,我揣著相機與錄音摆马,去河邊找鬼。 笑死鸿吆,一個胖子當著我的面吹牛囤采,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惩淳,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蕉毯,長吁一口氣:“原來是場噩夢啊……” “哼乓搬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起代虾,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤进肯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后棉磨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體江掩,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年乘瓤,在試婚紗的時候發(fā)現(xiàn)自己被綠了环形。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡衙傀,死狀恐怖抬吟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情统抬,我是刑警寧澤火本,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站聪建,受9級特大地震影響钙畔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜金麸,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一刃鳄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钱骂,春花似錦、人聲如沸挪鹏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讨盒。三九已至解取,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間返顺,已是汗流浹背禀苦。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遂鹊,地道東北人振乏。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像秉扑,于是被迫代替她去往敵國和親慧邮。 傳聞我的和親對象是個殘疾皇子调限,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

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