Android 7.0 中 ContentProvider 實現原理


歡迎大家前往騰訊雲社區,獲取更多騰訊海量技術實踐干貨哦~

作者:汪毅雄

導語: 本文描述了ContentProvider發布者和調用者這兩在Framework層是如何實現的。

作為Android的四大組件之一,ContentProvider作為進程之間靜態數據傳遞的重要手段,其在系統級別的應用中起了重大的作用。毫無疑問,ContentProvider核心機制之一也是Binder,但是和其它3大組件又有區別。因為ContentProvider涉及數據的增刪查改,當數據量比較大的時候,繼續用Parcel做容器效率會比較低,因此它還使用了匿名共享內存的方式。

但是有一個問題是,ContentProvider的提供者進程不再存活時,其他進程通過Provider讀一個非常簡單的數據時,都需要先把提供者進程啟動起來(除非指定multiprocess=true),這對用戶是相當不友好的。又因為其是間接通過db進行數據操作,所以效率也遠不如直接操作db。因此在用戶app中,不是很建議經常使用ContentProvider。不過對於系統級的app,它統一了數據操作的規范,利是遠大於弊的。

ContentProvider發布

當進程第一次啟動時候會調用handleBindApplication

if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                }
            }

 

當xml中有provider時,進行provider的發布

final ArrayList<IActivityManager.ContentProviderHolder> results =
            new ArrayList<IActivityManager.ContentProviderHolder>();
        for (ProviderInfo cpi : providers) {
            IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }
        try {
            ActivityManagerNative.getDefault().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
        }

 

@installProvider(這個方法先簡單過一下,后面會繼續說)

final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();

 

@installProviderAuthoritiesLocked

for (String auth : auths) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord existing = mProviderMap.get(key);
            if (existing != null) {
            } else {
                mProviderMap.put(key, pcr);
            }
        }

 

這里兩步把ProviderInfo通過installProvider轉換成ContentProvider的Binder對象IContentProvider,並放於ContentProviderHolder中。並根據auth的不同,把發布進程的ProviderClientRecord保存在一個叫mProviderMap的成員變量中,方便第二次調用同一個ContentProvider時,無需重新到AMS中去查詢。

AMS @publishContentProviders

final int N = providers.size();
            for (int i = 0; i < N; i++) {
                ContentProviderHolder src = providers.get(i);
                ...
                ContentProviderRecord dst = r.pubProviders.get(src.info.name);
                if (dst != null) {
                    ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                    mProviderMap.putProviderByClass(comp, dst);
                    String names[] = dst.info.authority.split(";");
                    for (int j = 0; j < names.length; j++) {
                        mProviderMap.putProviderByName(names[j], dst);
                    }
                    int launchingCount = mLaunchingProviders.size();
                    int j;
                    boolean wasInLaunchingProviders = false;
                    for (j = 0; j < launchingCount; j++) {
                        if (mLaunchingProviders.get(j) == dst) {
                            mLaunchingProviders.remove(j);
                            wasInLaunchingProviders = true;
                            j--;
                            launchingCount--;
                        }
                    }
                    if (wasInLaunchingProviders) {
                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                    }
                    ...
                }
            }

 

可以看到,AMS會遍歷所有的ContentProviderHolder,然后調用mProviderMap把信息保存起來,這塊接下來說。保存好之后,先去看看之前是不是已經有launch過的,如果已經有launch過的,不再重復launch。再說說這個mProviderMap,這個和ActivityThread中的mProviderMap不太一樣,這個是一個成員實例,非真正的map。看看putProviderByClass和putProviderByName。

ProviderMap@putProviderByClass

if (record.singleton) {
            mSingletonByClass.put(name, record);
        } else {
            final int userId = UserHandle.getUserId(record.appInfo.uid);
            getProvidersByClass(userId).put(name, record);
        }

 

ProviderMap@putProviderByName

if (record.singleton) {
            mSingletonByName.put(name, record);
        } else {
            final int userId = UserHandle.getUserId(record.appInfo.uid);
            getProvidersByName(userId).put(name, record);
        }

 

可以看到,發布的Provider實際會根據class或authority存在不同的map中。如果是單例,則分別存到相應的mSingleton map中,否則就根據userId存到相應的map中。這樣發布的過程就完成了,其他進程需要使用的時候將會在AMS按需讀取。

ContentReslover跨進程數據操作

當我們跨進程調用數據時候,會先調用獲取用戶進程的ContentResolver

context.getContentResolver().query(uri, ...);
 public ContentResolver getContentResolver() {
        return mContentResolver;
    }

 

而這個ContentResolver在每個進程中都存在有且唯一的實例,其在ContextImpl構造函數中就已經初始化了,其初始化的實際對象是ApplicationContentResolver。

mContentResolver = new ApplicationContentResolver(this, mainThread, user);

這個ContentResolver是活在調用者進程中的,它是作為一個類似橋梁的作用。以插入為例:

ContentResolver@insert

IContentProvider provider = acquireProvider(url);
        if (provider == null) {
            throw new IllegalArgumentException("Unknown URL " + url);
        }
        try {
            long startTime = SystemClock.uptimeMillis();
            Uri createdRow = provider.insert(mPackageName, url, values);
            ...
            return createdRow;
        } catch (RemoteException e) {
            return null;
        } finally {
            releaseProvider(provider);
        }

問題就轉化成了,拿到其他進程的ContentProvider的Binder對象,有了binder對象就可以跨進程調用其方法了。

ContentResolver@acquireProvider

if (!SCHEME_CONTENT.equals(uri.getScheme())) {
            return null;
        }
        final String auth = uri.getAuthority();
        if (auth != null) {
            return acquireProvider(mContext, auth);
        }

校驗其URI,其scheme必須為content。

ApplicationContentResolver@acquireProvider

protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), true);
        }

 

這里面有個特別的函數會傳遞一個true的參數給ActivityThread,這意味本次連接是stable的。那stable和非stable的區別是什么呢?這么說吧:

Stable provider:若使用過程中,provider要是掛了,你的進程也必掛。

Unstable provider:若使用過程中,provider要是掛了,你的進程不會掛。但你會收到一個DeadObjectException的異常,可進行容錯處理。

繼續往下。

ActivityThread@acquireProvider

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);
        } catch (RemoteException ex) {
        }
        if (holder == null) {
            return null;
        }

        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;

 

這里面分了三步,1、尋找自身進程的緩存,有直接返回。 2、緩存沒有的話,尋找AMS中的Provider。3、InstallProvider,又到了這個方法。怎么個install法?還是一會兒再說。

@acquireExistingProvider (尋找自身緩存)

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;

 

這一步就是讀取我們發布時提到的mProviderMap中的緩存。當provider記錄存在,且進程存活的情況下,則在provider引用計數不為空時則繼續增加引用計數。

緩存不存在,則去AMS中找

AMS@getContentProviderImpl

ContentProviderRecord cpr;
cpr = mProviderMap.getProviderByName(name, userId);
if (providerRunning){
   if (r != null && cpr.canRunHere(r)) {
                    ContentProviderHolder holder = cpr.newHolder(null);
                    holder.provider = null;
                    return holder;
                }
}
 public boolean canRunHere(ProcessRecord app) {
        return (info.multiprocess || info.processName.equals(app.processName))
                && uid == app.info.uid;
    }

 

Provider是提供保護數據的接入訪問的。一般情況下,不同進程的訪問只能通過IPC來進行,但那是有些情況是可以允許訪問者在自己的進程中創建本地Provider來進行訪問的。

這種情況是在UID必須相同的前提下,要么同一進程,要么provider設定了multiprocess為true。

if (!providerRunning) {
            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);
            if (r != null && cpr.canRunHere(r)) {
                return cpr.newHolder(null);
            }
            ProcessRecord proc = getProcessRecordLocked(
                            cpi.processName, cpr.appInfo.uid, false);

                    if (proc != null && proc.thread != null) {
                        if (!proc.pubProviders.containsKey(cpi.name)) {
                            proc.pubProviders.put(cpi.name, cpr);
                            proc.thread.scheduleInstallProvider(cpi);
                        }
                    } else {
                        proc = startProcessLocked(cpi.processName,
                                cpr.appInfo, false, 0, "content provider",
                                new ComponentName(cpi.applicationInfo.packageName,
                                        cpi.name), false, false, false);
                    }
                } 
            }
            mProviderMap.putProviderByName(name, cpr);
        }

 

這塊步驟比較多,挑重點就是,先從AMS的ProviderMap對象中獲取AMS緩存。獲得后如果Provider沒有launch,則AMS通知其進程install其provider。如果進程不存在,則新孵化一個進程。

@InstallProvider

回到第三步中的installProvider

private IActivityManager.ContentProviderHolder installProvider(Context context,
            IActivityManager.ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable)

 

可以看到,這個方法里面有6個參數,其中包含ContentProviderHolder、ProviderInfo、noReleaseNeeded,這幾個很重要的參數。

ContentProviderHolder:當參數為空的時候,說明緩存為空,也就意味着是進程啟動的時候調用發布provider。當緩存不為空的時候,還得做一些處理。

ProviderInfo:包含Provider的一些信息,不能為空。

noReleaseNeeded:為true的時候Provider對於自身進程來說或系統的Provider,是永久install的,也就是不會被destory的。

ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    return null;
                }
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
            }
        } else {
            provider = holder.provider;
        }

 

這部分在發布的時候已經說了,緩存holder為null的時候,new一個實例。

IActivityManager.ContentProviderHolder retHolder;
        synchronized (mProviderMap) {
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    provider = pr.mProvider;
                } else {
                    holder = new IActivityManager.ContentProviderHolder(info);
                    holder.provider = provider;
                    holder.noReleaseNeeded = true;
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    mLocalProviders.put(jBinder, pr);
                    mLocalProvidersByName.put(cname, pr);
                }
                retHolder = pr.mHolder;
            } else {
                ...
            }

 

如果localProvider不等於null,則意味着是new一個實例的情況,這時候還是先去獲取緩存,沒有的話再真正地new一個ContentProviderHolder實例,並把通過installProviderAuthoritiesLocked方法把相關信息存入mProviderMap中,這個就是對應發布Provider提的那個方法。

IActivityManager.ContentProviderHolder retHolder;
        synchronized (mProviderMap) {
            ...
            } else {
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if (prc != null) {
                    if (!noReleaseNeeded) {
                        incProviderRefLocked(prc, stable);
                        try {
                            ActivityManagerNative.getDefault().removeContentProvider(
                                    holder.connection, stable);
                        } 
                    }
                } else {
                    ProviderClientRecord client = installProviderAuthoritiesLocked(
                            provider, localProvider, holder);
                    if (noReleaseNeeded) {
                        prc = new ProviderRefCount(holder, client, 1000, 1000);
                    } else {
                        prc = stable
                                ? new ProviderRefCount(holder, client, 1, 0)
                                : new ProviderRefCount(holder, client, 0, 1);
                    }
                    mProviderRefCountMap.put(jBinder, prc);
                }
                retHolder = prc.holder;
            }

 

如果localProvider等於空,也就意味着有holder緩存或者new時候出現的異常。那先從計數map中取緩存,如果緩存不為空(之前有過計數了),這時候如果設置了noReleaseNeeded,那就說明不需要計數。如果noReleaseNeeded為false,則把計數器數據轉移到一個新引用上,同時銷毀舊的。

如果緩存為空,說明之前沒有計數過。那還是先通過installProviderAuthoritiesLocked把信息保存到mProviderMap中。這時候如果noReleaseNeeded為true,把stable和非stable的數據都瞎設置了一個1000,反正用不到。。。否則就相應的+1,並把計數器放入相應的緩存中。最后再把holder返回。

再回到ContentResolver方法中,我們拿到了Provider的binder引用,就可以執行相應的方法了。

 

相關閱讀

Android 7.0 中 Launcher 啟動 Activity 過程

Android 7.0 中 Service bind 流程詳解

Android View和 Window 的關系

 

此文已由作者授權騰訊雲技術社區發布,轉載請注明 原文出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM