ContentProvider啟動流程分析(一)

0x01 扯東扯西的前言&概述


本片博客對應時序圖上的step1—5下接第二篇ContentProvider啟動流程分析二!

同時此系列博客同步在博客園發布:ContentProvider啟動流程分析系列!詳情戳這里即可訪問~

作為安卓設計的四大組件之一,是跨進程共享數據的一把利器,所謂跨進程共享數據,通俗理解就是,應用程序A可以訪問操作應用程序B共享出來的數據,這些共享出來的數據一般都有其對應的URI(統一資源標識符),那么就涉及到兩個過程:

  1. 提供數據內容的過程:
    • A應用(比如系統聯系人,日歷)如果希望共享自己的數據內容(比如聯系人列表,日歷信息),就需要通過子類重寫ContentProvider六個方法來實現,ContentProvider的六個方法的共性特征是,都接受一個URI參數,通過解析URI參數匹配相應的數據內容并將其返回;
    • 當一個跨進程訪問數據的請求(包含RUI參數)發出后,系統首先會校驗URI權限(authority),權限校驗通過后,把這個訪問請求傳遞到對應進程(具體來說是一個應用程序)的ContentProvider組件,ContetnProvider接收到URI參數后,會解析URI路徑(path),然后根據路徑解析結果,提供相應的數據內容并將其返回;
    • 此過程在A應用程序內部實現,當A應用程序封裝好了ContentProvider實現類,需要在Mainfest清單文件中進行注冊,至此,A應用程序所在的進程已經提供了訪問自己所在進程數據的接口,B應用程序只需要根據數據內容的URI,發出訪問請求即可;
  2. 發出訪問數據請求的過程:
    • 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組件是經過哪些調用,一步一步被啟動的呢?請看時序圖如下:

ContentProvider啟動時序圖

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)!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容