天天看點

VirtualApk源碼分析-ContentProvider插件化

android通過ContentProvider可以實作程序間的資料共享,例如APP通過MediaProvider可以通路多媒體資料庫的内容。通常我們在Activity通過getContentResolver().query來跨程序通路資料庫,ContentImpl.getContentResolver會傳回ContentResolver對象,

VirtualApk源碼分析-ContentProvider插件化

ContentImpl.getContentResolver

mContentResolver的具體類型為ApplicationContentResolver,它繼承自ContentResolver;在擷取到ContentResolver通過其query方法可以跨程序查詢資料庫,ContentResolver的query方法如下:

VirtualApk源碼分析-ContentProvider插件化

ContentResolver.query

首先擷取了unstableProvider對象,擷取經過:ContentResolver.acquireUnstableProvider-->ApplicationContentResolver.acquireUnstableProvider

VirtualApk源碼分析-ContentProvider插件化

ApplicationContentResolver.acquireUnstableProvider

ApplicationContentResolver.acquireUnstableProvider調用了mMainThread.acquireProvider來獲得IContentProvider,該對象可以遠端通路對方程序的ContentProvider,mMainThread就是目前程序的ActivityThread類。

VirtualApk源碼分析-ContentProvider插件化

ActivityThread.acquireProvider

acquireExistingProvider函數如下:

VirtualApk源碼分析-ContentProvider插件化

acquireExistingProvider

acquireExistingProvider查詢目前程序是否已經儲存過該ContentProvider的副本,副本的存在可以提高多次通路同一個ContentProvider的性能,如果不存在副本,ActivityThread.acquireProvider就會調用AMS.getContentProvider函數來進行查找。查找過程如下:AMS.getContentProvider-->AMS.getContentProviderImpl。

getContentProviderImpl函數很長,大概的流程如下:

1、mProviderMap.getProviderByName(name, userId)查找目前是否已經plublish,如果沒有,進入第2步

2、 調用AppGlobals.getPackageManager().resolveContentProvider()擷取ProviderInfo,其實就是調用PMS.resolveContentProvider,在APK安裝的時候,PMS會解析AndroidManifest.xml檔案。并将APK的ContentProvider資訊儲存成ProviderInfo。擷取大屏ProviderInfo後進入第3步,

3、建立ContentProviderRecord對象,通過getProcessRecordLocked擷取ContentProviderRecord對應的程序是否啟動,如果程序已經啟動,就将ContentProviderRecord儲存到ContentProvider所屬程序的pubProviders中,并調用AppliationThread.scheduleInstallProvider(會調用ActivityThread.installContentProviders)進行ContentProvider的安裝。

VirtualApk源碼分析-ContentProvider插件化

getProcessRecordLocked

如果ContentProvider對應的程序沒有啟動,就會調用startProcessLocked啟動程序,建立程序就是通過Zygote建立,并運作程序的入口類ActivtyThread.main。startProcessLocked是異步的,是以無法立即确定程序是否啟動完成。是以getContentProviderImpl函數中實作了如下代碼來等待ContentProvider所屬的程序建立并完成ContentProvider的安裝:

VirtualApk源碼分析-ContentProvider插件化

getContentProviderImpl等待程序完成Provider的安裝

那麼ContentProvider是什麼時機安裝的呢,答案就是ActivityThread.handleBindApplication:

VirtualApk源碼分析-ContentProvider插件化

ActivityThread.handleBindApplication

程序建立後會運作Application,這裡調用了installContentProviders完成ContentProvider的安裝,注意安裝ContentProvider發生在Application的onCreate之前。

VirtualApk源碼分析-ContentProvider插件化

ActivityThread.installContentProviders

ContentProvider安裝完成後調用AMS.publishContentProvider,該操作将目前程序的ContentProvider儲存到了AMS,這樣有其餘程序想通路這個ContentProvider的時候,AMS就可以直接傳回,不需要再次安裝了。

是以說,一個ContentProvider在安裝完成後會有兩個副本,AMS中一個副本(不同程序通路同一個ContentProvider時可以複用AMS中的對象),通路這個ContentProvider的APP程序中有一個副本(同一個程序的多次通路同一個ContentProvider可以重複使用這個副本)

在了解了ContentProvider的運作流程後,如何對其進行插件化呢?這裡有如下思路:

1、能否自己解析插件的内部的ContentProvider資訊,然後安裝到自己程序内部呢?即安裝到ActivityThread的mProviderMap中。

這樣做隻能保證目前程序能夠通路這個ContentProvider,其他程序則無法通路,要解決ContentProvider的共享必須有經過AMS的getContentProvider過程,而且要保證ProviderInfo在PMS已經存在,這顯然是不可能的。

2、代理轉發,在宿主APP中攔截ContentProvider的操作,然後将操作轉給宿主占坑的ContentProvider,然後再由占坑ContentProvider進行轉發,調用插件中具體的ContentProvider對象。沒錯,VirtualApk就是這麼搞的。

VirtualApk源碼分析-ContentProvider插件化

占坑的ContentProvider

占坑的ContentProvider已經有了,那麼接下來該怎麼做呢?這裡分為兩個方面:

1、插件内部通路插件ContentProvider

插件内部使用的Context是PluginContext,VirtualApk複寫了getContentResolver函數,傳回了自己實作的PluginContentResolver。PluginContentResolver内部重寫了acquireProvider、acquireExistingProvider、acquireUnstableProvider函數,這裡以acquireUnstableProvider為例:

VirtualApk源碼分析-ContentProvider插件化

acquireUnstableProvider

mPluginManager.resolveContentProvider判斷是否是查詢插件的ContentProvider,如果是就使用Hook過對象:

VirtualApk源碼分析-ContentProvider插件化

hook的IContentProvider對象

hookIContentProviderAsNeed完成hook工作:

VirtualApk源碼分析-ContentProvider插件化

hookIContentProviderAsNeeded

hookIContentProviderAsNeeded替換了ActivityThread中已經安裝好的auth對應的IContentProvider,改用自己的IContentProviderProxy。

mContext.getContentResolver().call(uri,"wakeup",null,null);十分重要,uri="content://packageName.VirtualAPK.Provider",他完成了RemoteContentProvider的安裝,這樣在ActivityThread中就儲存了RemoteContentProvider對應的IContentProvider對象,然後給這個對象設定代理即可。這樣當目前程序再通路RemoteContentProvider時就直接使用這個代理。

VirtualApk源碼分析-ContentProvider插件化

IContentProviderProxy.invoke

invoke函數首先将通路插件的Uri轉到宿主占坑Uri:

VirtualApk源碼分析-ContentProvider插件化

wrapperUri

Uri的個數變成了content://host_authority/plugin_authority,其中host_authority表示宿主占坑ContentProvider對應的Auth,plugin_authority代表了實際要啟動的插件ContentProvider的一些資訊。

這樣就插件内部在擷取插件ContentProvider時的請求就轉到了宿主ContentProvider中。

2、插件外部通路插件ContentProvider

插件外部通路插件ContentProvider,首先需要調用PluginContentResolver.wrapperUri将對插件通路的URI轉為content://host_authority/plugin_authority格式,然後再由占坑ContentProvider進行轉發。

在完成URI轉換後,所有請求都轉給了宿主占坑ContentProvider,下面就需要進行代理轉發:

VirtualApk源碼分析-ContentProvider插件化

RemoteContentProvider.query

VirtualApk源碼分析-ContentProvider插件化

RemoteContentProvider.getContentProvider

getContentProvider通過反射建立出插件ContentProvider對象,最後調用ContentProvider.query完成查詢。