Android7.0中關(guān)于ContentProvider組件詳解
作為Android的四大組件之一,ContentProvider作為進(jìn)程之間靜態(tài)數(shù)據(jù)傳遞的重要手段,其在系統(tǒng)級(jí)別的應(yīng)用中起了重大的作用。毫無(wú)疑問(wèn),ContentProvider核心機(jī)制之一也是Binder,但是和其它3大組件又有區(qū)別。因?yàn)镃ontentProvider涉及數(shù)據(jù)的增刪查改,當(dāng)數(shù)據(jù)量比較大的時(shí)候,繼續(xù)用Parcel做容器效率會(huì)比較低,因此它還使用了匿名共享內(nèi)存的方式。
但是有一個(gè)問(wèn)題是,ContentProvider的提供者進(jìn)程不再存活時(shí),其他進(jìn)程通過(guò)Provider讀一個(gè)非常簡(jiǎn)單的數(shù)據(jù)時(shí),都需要先把提供者進(jìn)程啟動(dòng)起來(lái)(除非指定multiprocess=true),這對(duì)用戶是相當(dāng)不友好的。又因?yàn)槠涫情g接通過(guò)db進(jìn)行數(shù)據(jù)操作,所以效率也遠(yuǎn)不如直接操作db。因此在用戶app中,不是很建議經(jīng)常使用ContentProvider。不過(guò)對(duì)于系統(tǒng)級(jí)的app,它統(tǒng)一了數(shù)據(jù)操作的規(guī)范,利是遠(yuǎn)大于弊的。
ContentProvider發(fā)布
當(dāng)進(jìn)程第一次啟動(dòng)時(shí)候會(huì)調(diào)用handleBindApplication
if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { installContentProviders(app, data.providers); } }
當(dāng)xml中有provider時(shí),進(jìn)行provider的發(fā)布
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(這個(gè)方法先簡(jiǎn)單過(guò)一下,后面會(huì)繼續(xù)說(shuō))
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通過(guò)installProvider轉(zhuǎn)換成ContentProvider的Binder對(duì)象IContentProvider,并放于ContentProviderHolder中。并根據(jù)auth的不同,把發(fā)布進(jìn)程的ProviderClientRecord保存在一個(gè)叫mProviderMap的成員變量中,方便第二次調(diào)用同一個(gè)ContentProvider時(shí),無(wú)需重新到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會(huì)遍歷所有的ContentProviderHolder,然后調(diào)用mProviderMap把信息保存起來(lái),這塊接下來(lái)說(shuō)。保存好之后,先去看看之前是不是已經(jīng)有l(wèi)aunch過(guò)的,如果已經(jīng)有l(wèi)aunch過(guò)的,不再重復(fù)launch。再說(shuō)說(shuō)這個(gè)mProviderMap,這個(gè)和ActivityThread中的mProviderMap不太一樣,這個(gè)是一個(gè)成員實(shí)例,非真正的map??纯磒utProviderByClass和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); }
可以看到,發(fā)布的Provider實(shí)際會(huì)根據(jù)class或authority存在不同的map中。如果是單例,則分別存到相應(yīng)的mSingleton map中,否則就根據(jù)userId存到相應(yīng)的map中。這樣發(fā)布的過(guò)程就完成了,其他進(jìn)程需要使用的時(shí)候?qū)?huì)在AMS按需讀取。
ContentReslover跨進(jìn)程數(shù)據(jù)操作
當(dāng)我們跨進(jìn)程調(diào)用數(shù)據(jù)時(shí)候,會(huì)先調(diào)用獲取用戶進(jìn)程的ContentResolver
context.getContentResolver().query(uri, ...); public ContentResolver getContentResolver() { return mContentResolver; }
而這個(gè)ContentResolver在每個(gè)進(jìn)程中都存在有且唯一的實(shí)例,其在ContextImpl構(gòu)造函數(shù)中就已經(jīng)初始化了,其初始化的實(shí)際對(duì)象是ApplicationContentResolver。
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
這個(gè)ContentResolver是活在調(diào)用者進(jìn)程中的,它是作為一個(gè)類似橋梁的作用。以插入為例:
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); }
問(wèn)題就轉(zhuǎn)化成了,拿到其他進(jìn)程的ContentProvider的Binder對(duì)象,有了binder對(duì)象就可以跨進(jìn)程調(diào)用其方法了。
ContentResolver@acquireProvider
if (!SCHEME_CONTENT.equals(uri.getScheme())) { return null; } final String auth = uri.getAuthority(); if (auth != null) { return acquireProvider(mContext, auth); }
校驗(yàn)其URI,其scheme必須為content。
ApplicationContentResolver@acquireProvider
protected IContentProvider acquireProvider(Context context, String auth) { return mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), true); }
這里面有個(gè)特別的函數(shù)會(huì)傳遞一個(gè)true的參數(shù)給ActivityThread,這意味本次連接是stable的。那stable和非stable的區(qū)別是什么呢?這么說(shuō)吧:
Stable provider:若使用過(guò)程中,provider要是掛了,你的進(jìn)程也必掛。
Unstable provider:若使用過(guò)程中,provider要是掛了,你的進(jìn)程不會(huì)掛。但你會(huì)收到一個(gè)DeadObjectException的異常,可進(jìn)行容錯(cuò)處理。
繼續(xù)往下。
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、尋找自身進(jìn)程的緩存,有直接返回。 2、緩存沒(méi)有的話,尋找AMS中的Provider。3、InstallProvider,又到了這個(gè)方法。怎么個(gè)install法?還是一會(huì)兒再說(shuō)。
@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;
這一步就是讀取我們發(fā)布時(shí)提到的mProviderMap中的緩存。當(dāng)provider記錄存在,且進(jìn)程存活的情況下,則在provider引用計(jì)數(shù)不為空時(shí)則繼續(xù)增加引用計(jì)數(shù)。
緩存不存在,則去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是提供保護(hù)數(shù)據(jù)的接入訪問(wèn)的。一般情況下,不同進(jìn)程的訪問(wèn)只能通過(guò)IPC來(lái)進(jìn)行,但那是有些情況是可以允許訪問(wèn)者在自己的進(jìn)程中創(chuàng)建本地Provider來(lái)進(jìn)行訪問(wèn)的。
這種情況是在UID必須相同的前提下,要么同一進(jìn)程,要么provider設(shè)定了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); }
這塊步驟比較多,挑重點(diǎn)就是,先從AMS的ProviderMap對(duì)象中獲取AMS緩存。獲得后如果Provider沒(méi)有l(wèi)aunch,則AMS通知其進(jìn)程install其provider。如果進(jìn)程不存在,則新孵化一個(gè)進(jìn)程。
@InstallProvider
回到第三步中的installProvider
private IActivityManager.ContentProviderHolder installProvider(Context context, IActivityManager.ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable)
可以看到,這個(gè)方法里面有6個(gè)參數(shù),其中包含ContentProviderHolder、ProviderInfo、noReleaseNeeded,這幾個(gè)很重要的參數(shù)。
ContentProviderHolder:當(dāng)參數(shù)為空的時(shí)候,說(shuō)明緩存為空,也就意味著是進(jìn)程啟動(dòng)的時(shí)候調(diào)用發(fā)布provider。當(dāng)緩存不為空的時(shí)候,還得做一些處理。
ProviderInfo:包含Provider的一些信息,不能為空。
noReleaseNeeded:為true的時(shí)候Provider對(duì)于自身進(jìn)程來(lái)說(shuō)或系統(tǒng)的Provider,是永久install的,也就是不會(huì)被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; }
這部分在發(fā)布的時(shí)候已經(jīng)說(shuō)了,緩存holder為null的時(shí)候,new一個(gè)實(shí)例。
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一個(gè)實(shí)例的情況,這時(shí)候還是先去獲取緩存,沒(méi)有的話再真正地new一個(gè)ContentProviderHolder實(shí)例,并把通過(guò)installProviderAuthoritiesLocked方法把相關(guān)信息存入mProviderMap中,這個(gè)就是對(duì)應(yīng)發(fā)布Provider提的那個(gè)方法。
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時(shí)候出現(xiàn)的異常。那先從計(jì)數(shù)map中取緩存,如果緩存不為空(之前有過(guò)計(jì)數(shù)了),這時(shí)候如果設(shè)置了noReleaseNeeded,那就說(shuō)明不需要計(jì)數(shù)。如果noReleaseNeeded為false,則把計(jì)數(shù)器數(shù)據(jù)轉(zhuǎn)移到一個(gè)新引用上,同時(shí)銷毀舊的。
如果緩存為空,說(shuō)明之前沒(méi)有計(jì)數(shù)過(guò)。那還是先通過(guò)installProviderAuthoritiesLocked把信息保存到mProviderMap中。這時(shí)候如果noReleaseNeeded為true,把stable和非stable的數(shù)據(jù)都瞎設(shè)置了一個(gè)1000,反正用不到。。。否則就相應(yīng)的+1,并把計(jì)數(shù)器放入相應(yīng)的緩存中。最后再把holder返回。
再回到ContentResolver方法中,我們拿到了Provider的binder引用,就可以執(zhí)行相應(yīng)的方法了。
- Android使用ContentProvider初始化SDK庫(kù)方案小結(jié)
- 基于Android FileProvider 屬性配置詳解及FileProvider多節(jié)點(diǎn)問(wèn)題
- Android ContentProvider實(shí)現(xiàn)手機(jī)聯(lián)系人讀取和插入
- Android利用ContentProvider獲取本地?cái)?shù)據(jù)的方法
- Android7.0行為變更之適配File Provider的方法
- Android 中自定義ContentProvider與ContentObserver的使用簡(jiǎn)單實(shí)例
- Android 中ContentProvider的實(shí)例詳解
- Android控件AppWidgetProvider使用方法詳解
- Android編程之桌面小部件AppWidgetProvider用法示例
- Android實(shí)現(xiàn)花瓣飄落效果的步驟
相關(guān)文章
Android TabLayout設(shè)置指示器寬度的方法
本篇文章主要介紹了Android TabLayout設(shè)置指示器寬度的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04Android仿京東淘寶自動(dòng)無(wú)限循環(huán)輪播控件思路詳解
在App的開發(fā)中,很多的時(shí)候都需要實(shí)現(xiàn)類似京東淘寶一樣的自動(dòng)無(wú)限輪播的廣告欄,這里小編寫了一個(gè),分享到腳本之家平臺(tái)供大家參考2017-04-04Android開發(fā)TextvView實(shí)現(xiàn)鏤空字體效果示例代碼
這篇文章主要介紹了Android開發(fā)TextvView實(shí)現(xiàn)鏤空字體效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Android中實(shí)現(xiàn)多行、水平滾動(dòng)的分頁(yè)的Gridview實(shí)例源碼
如果單行水平滾動(dòng),可以用Horizontalscrollview實(shí)現(xiàn)。如果是多行水平滾動(dòng),則結(jié)合Gridview(一般是垂直滾動(dòng)的)和Horizontalscrollview實(shí)現(xiàn)2013-06-06詳解Android中Activity的四大啟動(dòng)模式實(shí)驗(yàn)簡(jiǎn)述
本篇文章主要介紹了Android中Activity的四大啟動(dòng)模式實(shí)驗(yàn)簡(jiǎn)述,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12Android中Textview超鏈接實(shí)現(xiàn)方式
TextView中的超鏈接可以通過(guò)幾種方式實(shí)現(xiàn):1.Html.fromHtml,2.Spannable,3.Linkify.addLinks。下面分別進(jìn)行測(cè)試,包括修改字體樣式,下劃線樣式,點(diǎn)擊事件等,需要的朋友可以參考下2016-02-02Flutter實(shí)現(xiàn)自定義篩選框的示例代碼
本文主要介紹了Flutter實(shí)現(xiàn)自定義篩選框的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07Android實(shí)現(xiàn)底部對(duì)話框BottomDialog彈出實(shí)例代碼
本篇文章主要介紹了Android實(shí)現(xiàn)底部對(duì)話框BottomDialog代碼。這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。2017-03-03