從原始碼理解ContentProvider的工作過程
前面閱讀了 Android/blob/master/%E6%8F%92%E4%BB%B6%E5%8C%96/VirtualApk/%E4%BB%8E%E6%BA%90%E7%A0%81%E4%BA%86%E8%A7%A3BroadcastReceiver%E7%9A%84%E5%B7%A5%E4%BD%9C%E8%BF%87%E7%A8%8B.md" target="_blank" rel="nofollow,noindex">BroadcastReceiver的原始碼 。
這篇文章也應該是繼續看 VirtualApk
中關於 外掛ContentProvider
的處理的。不過由於處理邏輯類似於 Activity
、 Service
,所以到最後再看。本文的目的是瞭解系統對於 ContentProvider
的整個處理的過程。
ContentProvider
是一個可以跨程序的元件,比如我們可以使用通訊錄的 ContentProvider
來獲取手機中的通訊錄資訊。 ContentResolver
封裝了 ContentProvider
跨程序通訊的邏輯,使我們在使用 ContentProvider
時不需要關心這些細節。
那我們在使用 context.getContentResolver().query(uri)
時發生了什麼呢?我們的程序是如何使用其他程序的ContentProvider的呢?
接下來我們就來分析Android系統原始碼對於 ContentProvider
的處理,來弄明白這些問題。
ContentProvider的例項化過程
我們從 ContextImp.getContentResolver().query()
開始看:
public final Cursor query(...) { IContentProvider unstableProvider = acquireUnstableProvider(uri); }
即首先要獲得一個 IContentProvider
。它是ContentProvider可以跨進互動的一個 aidl
介面。其實這裡拿到的就是一個 Binder
。所以接下來就看這個 IContentProvider(Binder)
是如果獲取的。
我們在呼叫 ContextImp.getContentResolver()
獲得的其實是 ApplicationContentResolver
。因此來看一下它的 acquireUnstableProvider()
:
protected IContentProvider acquireProvider(Context context, String auth) { return mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), true); }
繼續看原始碼, 切換到主執行緒 ActivityThread.java
:
public final IContentProvider acquireProvider(Context c, String auth, int userId, boolean stable) { // 如果已經快取過這個 auth對應的IContentProvider,則直接返回 final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); if (provider != null) return provider; ContentProviderHolder holder = null; holder = ActivityManager.getService().getContentProvider( getApplicationThread(), auth, userId, stable); //獲取失敗 if (holder == null) return null; //在向服務端獲取holder,服務端如果發現ContentProvider的程序和當前客戶端程序是同一個程序就會讓客戶端程序來例項化ContentProvider,具體細節可以在下面分析中看到 holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider; }
即在本地沒有獲得過 IContentProvider
時,直接向 ActivityManagerService
發起 getContentProvider
的請求,最終呼叫 ActivityManagerService.getContentProviderImpl()
, 這個方法就是 ContentProvider
例項化邏輯的核心了:
首先來看一下這個方法的宣告:
ContentProviderHolder getContentProviderImpl(IApplicationThread caller,String name, IBinder token, boolean stable, int userId)
即最終是返回一個 ContentProviderHolder
,它是什麼呢?它其實是一個可以在程序間傳遞的資料物件(aidl),看一下它的定義:
public class ContentProviderHolder implements Parcelable { public final ProviderInfo info; public IContentProvider provider; public IBinder connection; ...
繼續看 getContentProviderImpl()
,這個方法比較長,所以接下來我們分段來看這個方法, 順序是(1)、(2)、(3)... 這種 :
ActivityManagerService.getContentProviderImpl()(1)
//三個關鍵物件 ContentProviderRecord cpr; ContentProviderConnection conn = null; ProviderInfo cpi = null; ... cpr = mProviderMap.getProviderByName(name, userId); // 看看系統是否已經快取了這個ContentProvider
先來解釋一下 ContentProviderRecord
、 ContentProviderConnection
、 ProviderInfo
、 mProviderMap
它們大概是什麼:
ContentProviderRecord
: 它是系統(ActivityManagerService)用來記錄一個 ContentProvider
相關資訊的物件。
ContentProviderConnection
: 它是一個 Binder
。連線服務端(ActivityManagerService)和客戶端(我們的app)。裡面記錄著一個 ContentProvider
的狀態,比如是否已經死掉了等。
ProviderInfo
: 用來儲存一個 ContentProvider
的資訊(manifest中的 <provider>
), 比如 authority
、 readPermission
等。
mProviderMap
: 它的型別是 ProviderMap
。它裡面存在幾個map,這些map都是儲存 ContentProvider
的資訊的。
ok我們繼續來看原始碼:
ActivityManagerService.getContentProviderImpl()(2)
cpr = mProviderMap.getProviderByName(name, userId); // 看看系統是否已經快取了這個ContentProvider boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed; if (providerRunning) { ... } if (!providerRunning) { ... }
即根據 ContentProvider
所在的 程序是否是活躍
、 這個ContentProvider是否被啟動過(快取下來)
兩個狀態來進行不同的處理 :
ContentProvider已被載入並且所在的程序正在執行
即: if(providerRunning){ ... }
中的程式碼
ActivityManagerService.getContentProviderImpl()(3)
ProcessRecord r = getRecordForAppLocked(caller); //獲取客戶端(獲得content provider的發起者)的程序資訊 if (r != null && cpr.canRunHere(r)) { //如果請求的ContentProvider和客戶端位於同一個程序 ContentProviderHolder holder = cpr.newHolder(null); //ContentProviderConnection引數傳null holder.provider = null; //注意,這裡置空是讓客戶端自己去例項化!! return holder; } //客戶端程序正在執行,但是和ContentProvider並不在同一個程序 conn = incProviderCountLocked(r, cpr, token, stable); // 直接根據 ContentProviderRecord和ProcessRecord 構造一個 ContentProviderConnection ...
即如果請求的是同進程的 ContentProvider
則直接回到程序的主執行緒去例項化 ContentProvider
。否則使用 ContentProviderRecord
和 ProcessRecord
構造一個 ContentProviderConnection
ContentProvider所在的程序沒有執行並且服務端(ActivityManagerService)沒有載入過它
即: if(!providerRunning){ ... }
中的程式碼
ActivityManagerService.getContentProviderImpl()(4)
//先解析出來一個ProviderInfo cpi = AppGlobals.getPackageManager().resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId); ... ComponentName comp = new ComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); //這個content provider 沒有被載入過 final boolean firstClass = cpr == null; if (firstClass) { ... cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton); // 構造一個 ContentProviderRecord } ... final int N = mLaunchingProviders.size(); //mLaunchingProviders它是用來快取正在啟動的 ContentProvider的集合的 int i; for (i = 0; i < N; i++) { if (mLaunchingProviders.get(i) == cpr) {// 已經請求過一次了,provider正在啟動,不重複走下面的邏輯 break; } } //這個 ContentProvider 不是在啟動狀態,也就是還沒啟動 if (i >= N) { ProcessRecord proc = getProcessRecordLocked(cpi.processName, cpr.appInfo.uid, false); ... if (proc != null && proc.thread != null && !proc.killed) { //content provider所在的程序已經啟動 proc.thread.scheduleInstallProvider(cpi); //安裝這個 Provider , 即客戶端例項化它 } else { //啟動content provider 所在的程序, 並且喚起 content provider proc = startProcessLocked(cpi.processName,cpr.appInfo, false, 0, "content provider",new ComponentName(cpi.applicationInfo.packageName,cpi.name)...); } cpr.launchingApp = proc; mLaunchingProviders.add(cpr); //新增到正在啟動的佇列 } //快取 ContentProvider資訊 if (firstClass) { mProviderMap.putProviderByClass(comp, cpr); } mProviderMap.putProviderByName(name, cpr); //構造一個 ContentProviderConnection conn = incProviderCountLocked(r, cpr, token, stable); if (conn != null) { conn.waiting = true; //設定這個connection }
等待客戶端例項化 ContentProvider
ActivityManagerService.getContentProviderImpl()
(5)
// Wait for the provider to be published... synchronized (cpr) { while (cpr.provider == null) { .... if (conn != null) { conn.waiting = true; } cpr.wait(); } } return cpr != null ? cpr.newHolder(conn) : null; //返回給請求這個客戶端的程序
根據前面的分析,ContentProvider所在的程序沒有執行或者不是和 獲取者
同一個程序,就建立了一個 ContentProviderConnection
,那麼服務端就會掛起,啟動ContentProvider所在的程序,並等待它例項化 ContentProvider
:
在繼續看客戶端例項化ContentProvider之前,我們先用一張圖來總結一下客戶端程序請求服務端( ActivityManagerService
)啟動一個ContentProvider的邏輯 :

ActivityManagerService對於ContentProvider啟動請求的處理.png
客戶端例項化ContentProvider
ok,通過前面的分析我們知道 ContentProvider
最終是在它所在的程序例項化的。接下來就看一下客戶端相關程式碼,
同一個程序中的ContentProvider例項化過程
前面分析我們知道,如果 客戶端程序
和 請求的ContentProvider
位於同一個程序,則 ActivityManager.getService().getContentProvider(...);
,會返回一個內容為空的 ContentProviderHolder
,
我們再拿剛開始客戶端向服務端請求ContentProvider的程式碼看一下:
holder = ActivityManager.getService().getContentProvider( getApplicationThread(), auth, userId, stable); //在向服務端獲取holder,服務端如果發現ContentProvider的程序和當前客戶端程序是同一個程序就會讓客戶端程序來例項化ContentProvider,具體細節可以在下面分析中看到 holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable);
我們繼續看 ActivityThread.installProvider
, 這個方法其實有兩個邏輯, 下面我只擷取一些關鍵的邏輯,我們現在只看 同一個程序中的ContentProvider例項化過程
, 即會初始化 localProvider
的邏輯:
private ContentProviderHolder installProvider(...) { ContentProvider localProvider = null; IContentProvider provider; if (holder == null || holder.provider == null) { //服務端沒有快取過這個provider,客戶端需要初始化 final java.lang.ClassLoader cl = c.getClassLoader(); LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); localProvider = packageInfo.getAppFactory().instantiateProvider(cl, info.name);//例項化ContentProvider provider = localProvider.getIContentProvider(); ... localProvider.attachInfo(c, info); }else { provider = holder.provider; } ContentProviderHolder retHolder; IBinder jBinder = provider.asBinder(); if (localProvider != null) { //同一個程序的ContentProvider ProviderClientRecord pr = mLocalProvidersByName.get(cname); pr = installProviderAuthoritiesLocked(provider, localProvider, holder); //把Provider快取起來 retHolder = pr.mHolder; }else{ ProviderRefCount prc = mProviderRefCountMap.get(jBinder); //其他程序的ContentProvider ... if(prc == null){ prc = new ProviderRefCount(holder, client, 1000, 1000); //也快取起來 ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder); mProviderRefCountMap.put(jBinder, prc); ... } retHolder = prc.holder; } return retHolder; }
對於這個方法我們暫且知道在 客戶端程序
和 請求的ContentProvider
位於同一個程序時它會: 例項化ContentProvider,快取起來
不在同一個程序中的ContentProvider例項化過程
如果 客戶端程序
和 請求的ContentProvider
不在同一個程序,根據前面我們分析 ActivityManagerService
的邏輯可以知道, ActivityManagerService
會呼叫 ContentProvider
所在程序的 proc.thread.scheduleInstallProvider(cpi)
,
其實最終呼叫到 installContentProviders()
private void installContentProviders(Context context, List<ProviderInfo> providers) { final ArrayList<ContentProviderHolder> results = new ArrayList<>(); //ActivityManagerService 讓客戶端啟動的是一個ContentProvider列表 for (ProviderInfo cpi : providers) { ContentProviderHolder cph = installProvider(context, null, cpi,false, true ,true); if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } } ActivityManager.getService().publishContentProviders(getApplicationThread(), results); //通知服務端,content provider ok啦 }
即它會呼叫 installProvider
來例項化 ContentProvider
,並通知服務端 ContentProvider
ok了,可以給其他程序使用了。
那 installProvider
具體做了什麼呢? 前面已經分析過了,其實就是 快取下其他程序的 IContentProvider(Binder)
。你可以去看一下上面
installProvider
方法的 localProvider == null
的那個邏輯。
到這裡,客戶端其實就拿到了 IContentProvider(Binder)
。 即 ContextImp.getContentResolver().query()
拿到了 IContentProvider
。可執行 query
了。
是不是有點雲裡霧裡的,我們看一下下面這張圖,來理一下思路吧:

客戶端安裝ContentProvider.png
到這裡我們算理解了 ContentProvider
的工作原理, 我們以一個 ContentProvider
第一次啟動為例來總結一下:
- 程序在啟動ContentProvider時會向
ActivityManagerService
要,ActivityManagerService
如果沒有就會讓客戶端啟動這個ContentProvider
- 客戶端程序啟動
ContentProvider
後就會快取起來, 方便後續獲取 -
ActivityManagerService
只會快取那些可能跨程序訪問的ContentProvider
- 和不同程序的
ContentProvider
通訊是通過Binder
實現的
VirtualApk關於ContentProvider的處理
VirtualApk
它是一個外掛化框架,它所需要支援的特性是: 外掛中的ContentProvider
如何跑起來? 它又沒有在manifest中註冊。
其實很簡單,類似於它對 外掛Service的支援
:
- 定義一個佔坑的ContentProvider(執行在一個獨立的程序)
- hook掉
外掛Activity的Context
,並返回自定義的PluginContentResolver
-
PluginContentResolver
在獲取ContentProvider
時,先把個佔坑的ContentProvider
喚醒。即讓它在ActivityManagerService
中跑起來 - 返回給外掛一個
IContentProvider
的動態代理。 - 外掛通過這個
IContentProvider動態代理
來對ContentProvider
做增刪改查 - 在動態代理中把外掛的增刪改查的Uri,重新拼接定位到
佔坑的ContentProvider
- 在
佔坑的ContentProvider
例項化外掛請求的ContentProvider
,並做對應的增刪該查。
所以:
-
外掛的ContentProvider
是執行在佔坑的ContentProvider
程序中的。 -
外掛的ContentProvider
是不會執行在自己自定的程序中的,即沒有多程序ContentProvider
的概念。
歡迎Star我的 Android進階計劃 ,看更多幹貨