一泽裳、前言
在 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);
}
這上面的ApplicationContentResolver
是ContentResolver
的子類:
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
。 - 把第二步中獲得的
qCursor
用CursorWrapperInner
包裹起來睹晒,這里需要注意的是第二個參數(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)用了
mMainThread
的acquireProvider
方法,它實(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ù)holder
為null
泊藕,因此會根據(jù)Provider
的名字辅辩,動態(tài)地加載該類,并調(diào)用它的attachInfo
方法:
我們上面的有兩個
Provider
:
-
localProvider
娃圆,類型為ContentProvider
-
provider
玫锋,類型為Transport
provider
是通過localProvider
的getIContentProvider
方法獲得的,它是ContentProvider
的一個內(nèi)部類踊餐,它的作用就是作為ContentProvider
在遠(yuǎn)程調(diào)用者中的一個代理對象景醇,也就是說,ContentProvider
的使用者是通過獲取ContentProvider
所在進(jìn)程的一個代理類Transport
吝岭,再通過這個Transport
對象調(diào)用到ContentProvider
進(jìn)行查詢的:
接下來三痰,還會去調(diào)用
localProvider
的attachInfo
方法吧寺,這里面會初始化權(quán)限相關(guān)的信息,最終會執(zhí)行ContentProvider
的onCreate()
方法:假設(shè)上面我們獲得的
localProvider
不為空散劫,那么會執(zhí)行下面的邏輯:這里面稚机,我們會生成一個
ProviderClientRecord
對象,其內(nèi)部包含了下面幾個變量:-
mNames
:ContentProvider
對象的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)于ContentProviderHolder
和ProviderClientRecord
常熙,其繼承族譜如下圖所示:
(b-2) 發(fā)布過程
發(fā)布過程纬乍,其實(shí)就是調(diào)用了ActivityManagerService
的publishContentProviders
方法,將在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 知識梳理系列:
- Android 知識梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/