0x01 扯東扯西的前言&概述
本片博客對應時序圖上的step1—5:下接第二篇ContentProvider啟動流程分析二!
同時此系列博客同步在博客園發布:ContentProvider啟動流程分析系列!詳情戳這里即可訪問~
作為安卓設計的四大組件之一,是跨進程共享數據的一把利器,所謂跨進程共享數據,通俗理解就是,應用程序A可以訪問操作應用程序B共享出來的數據,這些共享出來的數據一般都有其對應的URI(統一資源標識符),那么就涉及到兩個過程:
- 提供數據內容的過程:
- A應用(比如系統聯系人,日歷)如果希望共享自己的數據內容(比如聯系人列表,日歷信息),就需要通過子類重寫ContentProvider六個方法來實現,ContentProvider的六個方法的共性特征是,都接受一個URI參數,通過解析URI參數匹配相應的數據內容并將其返回;
- 當一個跨進程訪問數據的請求(包含RUI參數)發出后,系統首先會校驗URI權限(authority),權限校驗通過后,把這個訪問請求傳遞到對應進程(具體來說是一個應用程序)的ContentProvider組件,ContetnProvider接收到URI參數后,會解析URI路徑(path),然后根據路徑解析結果,提供相應的數據內容并將其返回;
- 此過程在A應用程序內部實現,當A應用程序封裝好了ContentProvider實現類,需要在Mainfest清單文件中進行注冊,至此,A應用程序所在的進程已經提供了訪問自己所在進程數據的接口,B應用程序只需要根據數據內容的URI,發出訪問請求即可;
- 發出訪問數據請求的過程:
- B應用程序如果希望跨進程訪問A應用程序共享出來的數據,需要調用Context#getContentResolver()#query()|update()|insert()|delete(),無非就是對數據內容進行增刪改查操作,涉及到一個類ContentResolver,具體調用的是ContentResolver的增刪改查方法;與SQLite數據庫的增刪改查不同,ContentResolver的增刪改查方法需要接受一個URI參數,這個URI參數就是希望訪問的數據內容的URI;
- 此過程在B應用程序內部實現,通過在B進程訪問A進程的私有數據,完成跨進程共享數據的過程!
模擬一個跨進程請求數據的場景:A應用程序(AxxApp)在MainActivity中向B應用程序(BxxApp,也即系統自帶的聯系人應用)的聯系人數據發起跨進程的訪問請求。B應用程序中,SubContentProvider類繼承ContentProvider組件類,并重寫了ContentProvider的六個成員函數。
根據以上場景,當A應用程序發出訪問請求,請求攜帶系統聯系人數據URI,系統聯系人數據對應的URI值是 ContactsContract.CommonDataKinds.Phone.CONTENT_URI,當訪問請求發出后,系統會根據對應的URI,啟動B應用程序中ContentProvider組件SubContentProvider,并把聯系人數據返回給A應用程序。那么B程序的SubContentProvider組件是經過哪些調用,一步一步被啟動的呢?請看時序圖如下:
0x02ContentProvider啟動流程分析
再來結合源碼分步梳理一遍詳細經過,對應時序圖的step1-->step5,過程如下:
時序圖step1 --> Context#getContentResolver()
在A程序進程的MainActivity中調用getContentResolver函數,根據MainActivity的多重繼承關系,MainActivity繼承了Activity,而Activity又繼承了ContextWrapper,所以我們可以發現,因為當前Activity持有Context的引用,所以實際上調用的是ContextWrapper.getContentProvider()函數來獲得ContentResolver對象,記作resolver變量。
ContextWrapper類的成員函數getContentResolver()源碼如下:
public class ContextWrapper extends Context {
Context mBase;
....
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
....
}
可以看到ContextWrapper類的成員變量mBase指向了一個ContextImpl對象,它是在MainActivity組件啟動時創建的,因此mBase.getContentResolver()實際上的調用是ContextImpl.getContentResolver()函數來獲得一個ContentResolver對象resolver的。
ContextImpl類的成員函數getContentResolver()源碼如下:
class ReceiverRestrictedContext extends ContextWrapper {
....
private final ApplicationContentResolver mContentResolver;
....
/*構造函數*/
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
....
/*創建ApplicationContentResolver對象*/
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
/*返回ContentResolver對象*/
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
}
- ContextImpl類的成員變量mContentResolver,是一個ApplicationContentResolver對象,在構造方法中被創建后,直接在getContentResolver()函數中簡單的被返回給調用者。
- 也即,MainActivity中getContentResolver()函數,最終獲得的是一個ApplicationContentResolver對象;
- 接著就調用這個ApplicationContentResolver對象的acquireProvider()函數獲取BxxApp應用程序的SubContentProvider組件的一個代理對象;
- 也就是獲得與ContactsContract.CommonDataKinds.Phone.CONTENT_URI聯系人數據URI對應的一個ContentProvider組件對象。
時序圖step2,3 --> ContentResolver#acquireProvider()
ApplicationContentResolver是ContentResolver的實現類,它重寫了父類的acquireProvider()函數,所以實際上調用的是ContentResolver子類ApplicationContentResolver的成員函數acquireProvider(),ApplicationContentResolver#acquireProvider()源碼如下:
private static final class ApplicationContentResolver extends ContentResolver {
private final ActivityThread mMainThread;
....
public ApplicationContentResolver(
Context context, ActivityThread mainThread, UserHandle user) {
super(context);
mMainThread = Preconditions.checkNotNull(mainThread);
....
}
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
}
- 可以看出ApplicationContentResolver類的成員變量mMainThread指向了一個ActivityThread對象,它是在構造函數中初始化的。
- 在函數acquireProvider內部,其實調用的是ActivityThread類的成員函數acquireProvider(),這個函數會返回一個ContentProvider組件的代理對象,而這個代理對象代理的,正是聯系人數據URI對應的COntentProvider組件!
時序圖step4—5 --> ActivityThread#acquireProvider()/acquireExistingProvider()
接下來看ActivityThread類的acquireProvider()函數的源碼如下:
public final class 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 {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} ....
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
....
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable);
}
return provider;
}
}
}
ActivityThread類的acquireProvider()函數中,首先通過acquireExistingProvider()函數得到一個ContentProvider組件的代理對象IContentProvider對象provider;
acquireExistingProvider()函數持有一個全局的mProviderMap變量,表示一個HashMap<ProviderClientRecord>哈希表,用來保存在當前應用程序進程中訪問過的ContentProvider組件的代理ProviderClientRecord對象;
此函數的邏輯是,從mProviderMap表中獲取與權限參數(auth)以及用戶ID參數(userId)對應的ProviderClientRecord代理對象,并將其返回!
再回到acquireProvider()函數中,得到provider對象后進行判空,如果provider非空則直接將其返回給調用者了;
如果provider為空,接下來會先獲得ActivityManagerService類的一個代理對象,接著調用ActivityManagerService代理對象的成員函數getContentProvider()來請求與權限參數(auth)以及用戶ID參數(userId)對應的ContentProvider組件的代理對象;
并通過ActivityManagerService將這個代理對象返回。ActivityManagerService返回的ContentProvider組件的代理對象,使用一個ContentProviderHolder對象來進行描述。
接下來,會調用ActivityThread類的成員函數installProvider(),將這個ContentProviderHolder對象封裝成一個ContentClientRecord對象,并把這個ContentClientRecord對象存入全局變量mProviderMap中。
這樣如果后面再次接收到,對相同URI對應的ContentProvider的訪問請求,對于每個ContentProvider組件來說了,只需要被ActivityThread請求一次即可,當再次收到相同的請求只需要從全局變量mProviderMap中將其取出來返回給調用者即可!
然后按照函數執行的先后順序,分為兩個片段,先分析ActivityManagerService代理對象(記作ActivityManagerProxy)的成員函數getContentProvider()的具體實現(對應時序圖step6—19),然后再分析ActivityThread類的成員函數installProvider()的具體實現(對應時序圖step20)。
0x03 參考文獻與簡單的結語
未完,轉接下一篇:ContentProvider啟動流程分析二(對應時序圖step6—14)!