一、前言
在 Framework 原始碼解析知識梳理(5) - startService 原始碼分析 中,我們分析了Service
啟動的內部實現原理,今天,我們趁熱打鐵,看一下Android
中的四大元件中另一個元件ContentProvider
。
二、原始碼解析
在分析之前,先上一張整個的流程圖,大家在後面繞暈了以後,可以參考這張圖進行對照:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/1f4b67c2a62840d4673003fb2436cccd29037b9343d3e7309f6b19a70fc08fd9.png)
2.1 ContentResolver 獲取過程
在使用ContentProvider
來進行資料的增刪改查時,第一步就是要通過getContentResolver()
,獲得一個ContentResolver
物件,該方法實際上呼叫了基類中的mBase
變數,也就是ContextImpl
中的getContentResolver()
方法,並返回它其中的mContentResolver
變數。
//ContextImpl.java
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
複製程式碼
而該mContentResolver
是在ContextImpl
的建構函式中初始化的,這其實和我們之前在 外掛化知識梳理(9) - 資源的動態載入示例及原始碼分析 中所分析的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
的子類:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/680ea4611365c5cd41e8b070ce23d1d8558d1c934308b8f0eafe2fee5fb2fdcd.png)
2.2 簡單的查詢過程
現在,我們以ContentResolver
所提供的query
方法為例,對ContentProvider
的呼叫過程進行一次簡單的走讀:
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.建立取消訊號量。
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
//3.呼叫IContentProvider的query方法。
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
//如果發生了異常,那麼銷燬unstableProvider物件,重新獲取一個stableProvider物件。
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
//如果stableProvider物件還是為空,那麼直接返回空。
if (stableProvider == null) {
return null;
}
//呼叫stableProvider進行查詢。
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);
}
}
}
複製程式碼
我們對上面的流程進行一個簡單的梳理:
- 通過
acquireUnstableProvider
獲取一個unstableProvider
例項,按字面上的翻譯它是一個不穩定的ContentProvider
。 - 通過第一步中獲取的
unstableProvider
例項進行查詢,如果查詢成功,那麼得到qCursor
物件;如果ContentProvider
所對應的程式已經死亡,那麼將會釋放unstableProvider
物件,再通過呼叫acquireProvider
方法重新得到一個stableProvider
,它和unstableProvider
相同,都是實現了IContentProvider
介面,之後在通過它來查詢得到qCursor
。 - 把第二步中獲得的
qCursor
用CursorWrapperInner
包裹起來,這裡需要注意的是第二個引數,如果是通過unstableProvider
查詢得到的qCursor
,那麼將需要呼叫acquireProvider
,並將返回值傳入。
那麼,我們接下來就要分析通過acquireUnstableProvider
、acquireProvider
獲取IContentProvider
的過程。
2.3 IContentProvider 獲取過程
首先,通過acquireUnstableProvider
方法根據Uri
中的authority
欄位,呼叫acquireUnstableProvider(Context c, String auth)
方法:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/2cfe79e7691f41fc39ced5d0585b65c0c24fcd7b965a107da2f67af5375729f9.png)
ApplicationContentResolver
所實現的:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/6acadaf6fbfca0b79f01c1076785c8fd2cf01414a9aef3ea10932b5912c56b70.png)
mMainThread
的acquireProvider
方法,它實際上是一個ActivityThread
例項,其實現為:
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 {
//如果快取當中沒有,那麼首先通過AMS進行獲取。
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;
}
//根據返回的holder資訊進行安裝。
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
複製程式碼
這裡,首先會去快取中查詢IContentProvider
,如果沒有找到,那麼在呼叫AMS
的方法去查詢,獲取一個ContentProviderHolder
物件。
2.3.1 呼叫者程式不存在快取的情況
在這種情況下面,會執行兩步操作:
- 第一步:通過
ActivityManagerService
獲取ContentProviderHolder
- 第二步:通過返回的
ContentProviderHolder
中的資訊進行安裝
第一步,通過 ActivityManagerService 獲取 ContentProviderHolder
這裡我們先假設沒有快取的情況,通過 Framework 原始碼解析知識梳理(1) - 應用程式與 AMS 的通訊實現 中學到的知識,我們知道它最終會呼叫到ActivityManagerService
的下面這個方法:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/f7efbf1da55e704b450fd9c9d85b7626ff8657d0b51d274df0dc9289b6c7bfa0.png)
getContentProviderImpl
方法返回一個ContentProviderHolder
物件,這個方法比較長,就不貼程式碼了,直接說結論,這裡會分為以下幾種情況:
(a) ContentProvider 所在程式已經啟動,並且已經該 ContentProvider 已經被安裝
這種情況下,直接返回該ContentProviderHolder
即可:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/2e0d08b01e667e905cf690d9211b9c200cd54c7f77e298b8532f53edeb8fb0c8.png)
此時,就需要通過ApplicationThread
物件,再和ContentProvider
所在的程式進行互動,以返回一個ContentProviderHolder
例項:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/9d80b6f08e69c977730f3fa9dfd0eaa63fb966cc044ae381d6a95f3f276e60ec.png)
Binder
通訊,那麼最終會呼叫到ContentProvider
所在程式的下面這個方法:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/3bcfa2d3d600d4b5eb56751c16ec9b93fe6a3e1ce46d35ac4690db8c09291582.png)
installContentProviders
方法:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/b7b3887a25921f37a5ab366265cff4b8d96c1f60ee30e719f068338da0d92040.png)
- 安裝:根據傳過來的
List<ProviderInfo>
物件,通過installProvider
方法進行安裝,並將結果存放在List<ContentProviderHolder>
列表中。 - 釋出:將安裝的結果,再通過一次訊息傳遞,返回給
ActivityManagerService
。
(b-1) 安裝過程
在這一步當中,傳入的第二個引數holder
為null
,因此會根據Provider
的名字,動態地載入該類,並呼叫它的attachInfo
方法:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/d9990f53da4c334f1829d07edd84ad2e728682b716f367f1803501977c8da5f3.png)
Provider
:
localProvider
,型別為ContentProvider
provider
,型別為Transport
provider
是通過localProvider
的getIContentProvider
方法獲得的,它是ContentProvider
的一個內部類,它的作用就是作為ContentProvider
在遠端呼叫者中的一個代理物件,也就是說,ContentProvider
的使用者是通過獲取ContentProvider
所在程式的一個代理類Transport
,再通過這個Transport
物件呼叫到ContentProvider
進行查詢的:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/6a57d4c3525c6ebcc4aae1dbbd9aabe1c686b46fc832bb82ebd9a4048e5178d8.png)
localProvider
的attachInfo
方法,這裡面會初始化許可權相關的資訊,最終會執行ContentProvider
的onCreate()
方法:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/c4317aaca90c48a8c8590c5f80302ea5e09b8507ef66d0ca983c63d669518afe.png)
localProvider
不為空,那麼會執行下面的邏輯:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/bd2cafa40489efef45356b6d9a24237fe2d0180123f524e150b4b076515bfd70.png)
ProviderClientRecord
物件,其內部包含了下面幾個變數:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/71764501735098b0fd968c660fd0a2b84f7a4d0636bd7ecfb5dbefb6a9c74e8e.png)
mNames
:ContentProvider
物件的authority
mProvider
:遠端代理物件mLocalProvider
:本地物件mHolder
:返回給AMS
的資料結構,AMS
再會把它返回給ContentProvider
的呼叫者,mHolder
的型別為IActivityManager.ContentProviderHolder
,其內部包含的資料結構為:
關於ContentProviderHolder
和ProviderClientRecord
,其繼承族譜如下圖所示:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/577d6a66ede82656f0809a0561fb95a88fdca25f4558d71b1dce0efa91401890.png)
釋出過程,其實就是呼叫了ActivityManagerService
的publishContentProviders
方法,將在ContentProvider
擁有者所建立的List<ContentProviderHolder>
儲存起來:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/94eda2253903bed1673d01e734390d0d51cbe9d5baba55bd4ca3713a54e5611c.png)
(c) ContentProvider 所在程式沒有啟動
在這種情況下,就需要先通過startProcessLocked
啟動ContentProvider
所在程式,等待程式啟動完畢之後,再進行安裝。
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/e3071519c5be46992004a67242a25d25b9f27331c41d8de44e400c836b66b91b.png)
第二步,利用返回的 ContentProviderHolder 中的資訊,進行安裝
在第一步中,通過ActivityManagerService
,我們最終獲得了ContentProviderHolder
物件,接下來就是呼叫installProvider
方法,這裡和我們之前在第一步中的(b-1)
中所看到的installProvider
其實是同一個方法,區別在於,之前我們分析的installProvider
傳入的holder
引數為空,下面,我們就來看一下當holder
引數不為空時最終會走到下面的邏輯:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/3b1affc7024fe2b235427d1e6fbc22962eca769c6e618e57e4a37ba8445620b1.png)
installProviderAuthoritiesLocked
方法中,會將它快取在mProviderMap
當中。
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/909425239db7b7025ad242fdd3d420ee6025337524a00bd95e0bf71bbc23c5a0.png)
2.3.2 呼叫者程式存在快取的情況
當呼叫者程式存在快取時,會呼叫acquireExistingProvider
方法,這裡面就會通過我們前面所看到的mProviderMap
進行查詢:
![Framework 原始碼解析知識梳理(6) ContentProvider 原始碼解析](https://i.iter01.com/images/1a5ba3e5cbdfa3cb8060f2339e0688e84754624882029f5dc6c39d9b7767c2ae.png)
三、小結
這篇文章拖了一個星期,總算是完成了,原始碼看的真的頭暈,其實最終看下來,發現整個呼叫過程,和我們之前分析過的 Framework 原始碼解析知識梳理(5) - startService 原始碼分析 很類似,究其根本,就是呼叫者程式、所有者程式和ActivityManagerService
程式的三方呼叫。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/