ContentProvider使用拾遺

一、ContentProvider所在應用的啟動時機

ContentProvider可向多個進程提供數據,比如ContentProvider實現在應用A中,A的進程尚未啟動,另一個應用(進程)B在使用ContentProvider時就會啟動應用A。

在調用ContentResolver的query時,會先獲取A中ContentProvider在進程B中的binder代理IContentProvider。

acquireUnstableProvider的實現在類ApplicationContentResolver里。我們調用Context的getContentResovler返回的就是ApplicationContentResolver的實例,它繼承了ContentResolver,代碼實現在ContextImpl.java里。

Advertisements

這裡的mMainThread是ActivityThread,它最終會通過ActivityManagerNative調用到AMS里的getContentProvider。getContentProvider會調getContentProviderImpl。

因為是第一次使用ContentProvider,所以通過mProviderMap.getProviderByName得到的cpr為null,最下面providerRunning為false,說明沒provider尚未運行。

cpr是ContentProviderRecord類型,像ActivityRecord、ServiceRecord、BroadcastRecord一樣,ContentProviderRecord是ContentProvider在AMS里的記錄,裡面包含了客戶端到Provider的連接等信息。

Advertisements

因為providerRunning為false,接下來轉到PackageManagerService中,調用resolveContentProvider獲取cpi,cpi是ProviderInfo類型。在resolveContentProvider中,根據應用安裝時獲得的provider信息,由PackageParser得到ProviderInfo。然後把這個cpi作為ContentProviderRecord構造函數的參數, new出一個ContentProviderRecord的實例。

因為Provider所在進程還沒啟動,所以proc和proc.thread都為null,走else的分支,通過startProcessLocked來啟動provider所在的進程。

startProcessLocked需要用到ProviderInfo里的processName,ContentProviderRecord里的ApplicationInfo等信息。

二、ContentProvider自身啟動的時機

應用在啟動時,它裡面的ContentProvider也會立即被啟動,即便這時還沒有任何代碼會用到ContentProvider,ContentProvider的構造函數和onCreate也會被調用。

在應用啟動的入口ActivityThread的main函數中會調attach把客戶端的Application交由AMS管理,最終會調到AMS內部的attachApplicationLocked。

在AMS中又會回調客戶端ActivityThread這一側的bindApplication,完成應用最後的綁定工作。

bindApplication會調到ActivityThread中handleBindApplication,這個函數裡面會初始化客戶端Application需要用到的數據結構,其中就有provider。

installContentProviders就是初始化應用中的ContentProvider組件,第二個參數providers是應用內所有ContentProvider的ProviderInfo結構的列表。data是AppBindData類型,是handleBindApplication的參數,從AMS一側傳過來的。還可以看到ActivityThread中另一個重要的類Instrumentation也是在handleBindApplication里初始化的,並且還是在ContentProvider的後面。AppBindData的結構如下:

installContentProvider會對列表中的provider挨個調installProvider,installProvider中會通過反射構造出ContentProvider的實例,並調用它的onCreate方法。

ContentProvider的attachInfo中會有一些初始化的動作,最後調onCreate,它的實現如下:

可見,只要應用一啟動,無論是否馬上用到ContentProvider,它都會立即被構建出來,並調用onCreate,一般在onCreate中會做創建表、導入初始數據的動作,這樣會佔用應用啟動的時間,而ContentProvider初始化完成後可能並不會立即被用到,要是能控制並推遲ContentProvider的啟動時機就好了。

三、ContentProvider不自啟動

在AndroidManifest.xml中使用android:process和android:multiprocess這兩個屬性來描述provider可以讓ContentProvider不隨Application的初始化而啟動。我們知道默認不指定android:process的話,組件所在的進程名就是包名,multiprocess默認為false。如果對provider指定android:process=」fore」,同時android:multiprocess=」true」,那麼在應用啟動時provider就不會隨之啟動,並且provider並沒有在另一個新進程中,仍然在原來應用默認的進程中。只有在後面用到provider時,比如query、insert等調用才會啟動provider。

我們仔細看一下之前所述的ContentProvider隨應用啟動的流程。

1. AMS里的attachApplicationLocked

應用啟動時,ActivityThread的attach函數最終會調到AMS里的attachApplicationLocked,在它往回調用ActivityThread的bindApplication之前,先會準備好應用中包含的所有provider的list,這是通過函數generateApplicationProvidersLocked實現的。

generateApplicationProvidersLocked又是通過PMS的queryContentProviders得到apk中的provider列表的。

2. PMS里的queryContentProviders

queryContentProviders會用到ProviderIntentResolver類里保存的provider列表,mProviders是ProviderIntentResolver類的實例,mProviders.mProviders是ProviderIntentResolver類里的一個HashMap,存放著ComponentName和PackageParser.Provider的對應關係。

這個HashMap中的provider信息是在PMS的scanPackage時放入的。在android5.0上有scanPackageDirtyLI這個函數,它會調ProviderIntentResolver類的addProvider方法把PackageParser.Package中的provider信息加到mProviders這個HashMap里。

這裡的pkg就是PackageParser.Package類型,它裡面的providers是在安裝apk時解析AndroidManifest.xml文件所得到的。在parseBaseApplication函數中會解析manifest文件,把標籤中含有provider的信息加入到PackageParser.Package的mProviders中。

在知道queryContentProviders要遍歷的providers是從哪兒來的之後,接著再來看queryContentProviders下面的一個判斷條件。processName是從AMS里傳過來的,ProcessRecord里的進程名,也就是應用所在的進程的進程名,p.info.processNames是從PackageParser.Package中傳來的,解析manifest得到的provider組件指定的進程名,在這裡android:processName=」fore」,與應用的進程名不一致,所以沒被加到list中。

從第二部分的分析可知,後面在ActivityThread中會依次遍歷由AMS傳來的provider列表,調用installProvider,通過反射newInstance出每個ContentProvider,並調用它們的onCreate。這裡因為指定了不同的android:processName,所以不會被加到列表傳給ActivityThread,也就不會被啟動。

3. ContentProviderRecord里的canRunHere

那麼指定了android:multiprocess=」true」后,為什麼ContentProvider沒有在單獨的另一個進程中運行呢?前面說了ContentProvider如果不在應用初始化時一起啟動,那就在被使用時才啟動。調用query、insert等ContentResolver的方法時,客戶端會調acquireUnstableProvider之類的函數,在AMS端對應調用getContentProviderImpl。

第一部分有分析這個過程,如果ContentProvider實現在應用A中,那麼在B中使用provider時,在getContentProviderImpl里會調startProcessLocked來啟動應用A所在的進程,但在startProcessLocked之前會先判斷是否在同一進程中啟動provider就可以了,不用再把provider放在另一個進程中。本來ContentProvider可以分別給位於不同進程的不同客戶端提供數據,但如果不是必要的話,ContentProvider在同一進程內部被調用會更節省資源,所以在getContentProviderImpl時有做判斷,這個判斷就是ContentProviderRecord里的canRunHere函數。下面的代碼是在getContentProviderImpl中判斷如果canRunHere返回true,就直接返回ContentProviderHolder,不繼續往下走去startProcessLocked起新進程了。

canRunHere是在應用的uid和provider這個組件的uid相同的情況下(有可能是uid不同的另一個應用去訪問ContentProvider),要麼應用的進程名和provider的進程名相同,要麼provider聲明了multiprocess為true,這樣provider就和使用它的客戶端在同一進程里了。

這樣如果應用內有很多個provider,我們就可以不必應用啟動時在主線程里初始化這些provider並調用它們的onCreate,而是根據它們實際的使用情況,在調用時才啟動相應的provider。

Advertisements

你可能會喜歡