您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Android7.0中ContentProvider組件的作用是什么,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
作為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引用,就可以執行相應的方法了。
關于Android7.0中ContentProvider組件的作用是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。