Android 7.0中ContentProvider實(shí)現(xiàn)原理

| 導(dǎo)語 本文描述了ContentProvider發(fā)布者和調(diào)用者這兩在Framework層是如何實(shí)現(xiàn)的。

作為Android的四大組件之一,ContentProvider作為進(jìn)程之間靜態(tài)數(shù)據(jù)傳遞的重要手段添坊,其在系統(tǒng)級別的應(yīng)用中起了重大的作用铅协。毫無疑問ContentProvider核心機(jī)制之一也是Binder剧浸,但和其它3大組件又有區(qū)別。因?yàn)镃ontentProvider涉及數(shù)據(jù)的增刪查改冶伞,當(dāng)數(shù)據(jù)量比較大的時(shí)候,繼續(xù)用Parcel做容器效率會比較低步氏,因此它還使用了匿名共享內(nèi)存的方式响禽。

但是有一個(gè)問題是,ContentProvider的提供者進(jìn)程不再存活時(shí)荚醒,其他進(jìn)程通過Provider讀一個(gè)非常簡單的數(shù)據(jù)時(shí)芋类,都需要先把提供者進(jìn)程啟動起來(除非指定multiprocess=true),這對用戶是相當(dāng)不友好的界阁。又因?yàn)槠涫情g接通過db進(jìn)行數(shù)據(jù)操作侯繁,所以效率也遠(yuǎn)不如直接操作db。因此在用戶app中泡躯,不是很建議經(jīng)常使用ContentProvider贮竟。不過對于系統(tǒng)級的app丽焊,它統(tǒng)一了數(shù)據(jù)操作的規(guī)范,利是遠(yuǎn)大于弊的咕别。

ContentProvider發(fā)布

當(dāng)進(jìn)程第一次啟動時(shí)候會調(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è)方法先簡單過一下,后面會繼續(xù)說)

   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轉(zhuǎn)換成ContentProvider的Binder對象IContentProvider惰拱,并放于ContentProviderHolder中雌贱。并根據(jù)auth的不同,把發(fā)布進(jìn)程的ProviderClientRecord保存在一個(gè)叫mProviderMap的成員變量中弓颈,方便第二次調(diào)用同一個(gè)ContentProvider時(shí)帽芽,無需重新到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然后調(diào)用mProviderMap把信息保存起來导街,這塊接下來說。保存好之后纤子,先去看看之前是不是已經(jīng)有l(wèi)aunch過的搬瑰,如果已經(jīng)有l(wèi)aunch過的,不再重復(fù)launch控硼。再說說這個(gè)mProviderMap泽论,這個(gè)和ActivityThread中的mProviderMap不太一樣,這個(gè)是一個(gè)成員實(shí)例卡乾,非真正的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);
        }

可以看到幔妨,發(fā)布的Provider實(shí)際會根據(jù)class或authority存在不同的map中鹦赎。如果是單例,則分別存到相應(yīng)的mSingleton map中误堡,否則就根據(jù)userId存到相應(yīng)的map中古话。這樣發(fā)布的過程就完成了,其他進(jìn)程需要使用的時(shí)候?qū)贏MS按需讀取锁施。

ContentReslover跨進(jìn)程數(shù)據(jù)操作

當(dāng)我們跨進(jìn)程調(diào)用數(shù)據(jù)時(shí)候陪踩,會先調(diào)用獲取用戶進(jìn)程的
ContentResolver

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

ContentResolver在每個(gè)進(jìn)程中都存在有且唯一的實(shí)例,其在ContextImpl構(gòu)造函數(shù)中就已經(jīng)初始化了悉抵,其初始化的實(shí)際對象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);
        }

問題就轉(zhuǎn)化成了婚温,拿到其他進(jìn)程的ContentProvider的Binder對象,有了binder對象就可以跨進(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ù)會傳遞一個(gè)true的參數(shù)給ActivityThread,這意味本次連接是stable的力图。那stable和非stable的區(qū)別是什么呢步绸?這么說吧:

Stable provider:若使用過程中,provider要是掛了吃媒,你的進(jìn)程也必掛瓤介。Unstable provider:若使用過程中,provider要是掛了赘那,你的進(jìn)程不會掛刑桑。但你會收到一個(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呢灶、緩存沒有的話吴超,尋找AMS中的Provider。3鸯乃、InstallProvider鲸阻,又到了這個(gè)方法。怎么個(gè)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;

這一步就是讀取我們發(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ù)的接入訪問的性置。一般情況下拾并,不同進(jìn)程的訪問只能通過IPC來進(jìn)行,但那是有些情況是可以允許訪問者在自己的進(jìn)程中創(chuàng)建本地Provider來進(jì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對象中獲取AMS緩存。獲得后如果Provider沒有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í)候祭往,說明緩存為空伦意,也就意味著是進(jìn)程啟動的時(shí)候調(diào)用發(fā)布provider。當(dāng)緩存不為空的時(shí)候硼补,還得做一些處理驮肉。ProviderInfo:包含Provider的一些信息,不能為空括勺。noReleaseNeeded:為true的時(shí)候Provider對于自身進(jìn)程來說或系統(tǒng)的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;
        }

這部分在發(fā)布的時(shí)候已經(jīng)說了奈辰,緩存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í)候還是先去獲取緩存宛裕,沒有的話再真正地new一個(gè)ContentProviderHolder實(shí)例瑟啃,并把通過installProviderAuthoritiesLocked方法把相關(guān)信息存入mProviderMap中,這個(gè)就是對應(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中取緩存岩榆,如果緩存不為空(之前有過計(jì)數(shù)了)错负,這時(shí)候如果設(shè)置了noReleaseNeeded,那就說明不需要計(jì)數(shù)勇边。如果noReleaseNeeded為false犹撒,則把計(jì)數(shù)器數(shù)據(jù)轉(zhuǎn)移到一個(gè)新引用上,同時(shí)銷毀舊的粒褒。

如果緩存為空识颊,說明之前沒有計(jì)數(shù)過。那還是先通過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)的方法了

聲明:本文轉(zhuǎn)載自 2017-10-12 yixiongwang 騰訊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谨敛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滤否,更是在濱河造成了極大的恐慌脸狸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藐俺,死亡現(xiàn)場離奇詭異炊甲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)欲芹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門卿啡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人菱父,你說我怎么就攤上這事颈娜。” “怎么了浙宜?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵官辽,是天一觀的道長。 經(jīng)常有香客問我粟瞬,道長同仆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任裙品,我火速辦了婚禮俗批,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘清酥。我一直安慰自己,他們只是感情好蕴侣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布焰轻。 她就那樣靜靜地躺著,像睡著了一般昆雀。 火紅的嫁衣襯著肌膚如雪辱志。 梳的紋絲不亂的頭發(fā)上蝠筑,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音揩懒,去河邊找鬼什乙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛已球,可吹牛的內(nèi)容都是我干的臣镣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼智亮,長吁一口氣:“原來是場噩夢啊……” “哼忆某!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起阔蛉,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤弃舒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后状原,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聋呢,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年颠区,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了削锰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瓦呼,死狀恐怖喂窟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情央串,我是刑警寧澤磨澡,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站质和,受9級特大地震影響稳摄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饲宿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一厦酬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘫想,春花似錦仗阅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春筹裕,著一層夾襖步出監(jiān)牢的瞬間醋闭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工朝卒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留证逻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓抗斤,卻偏偏與公主長得像囚企,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子豪治,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容