ContentProvider啟動流程分析(一)
## 0x01 扯東扯西的前言&概述
作為安卓設計的四大元件之一,是跨程序共享資料的一把利器,所謂跨程序共享資料,通俗理解就是,應用程式A可以訪問操作應用程式B共享出來的資料,這些共享出來的資料一般都有其對應的URI(統一資源識別符號),那麼就涉及到兩個過程:
- 提供資料內容的過程:
- A應用(比如系統聯絡人,日曆)如果希望共享自己的資料內容(比如聯絡人列表,日曆資訊),就需要通過子類重寫ContentProvider六個方法來實現,ContentProvider的六個方法的共性特徵是,都接受一個URI引數,通過解析URI引數匹配相應的資料內容並將其返回;
- 當一個跨程序訪問資料的請求(包含RUI引數)發出後,系統首先會校驗URI許可權(authority),許可權校驗通過後,把這個訪問請求傳遞到對應程序(具體來說是一個應用程式)的ContentProvider元件,ContetnProvider接收到URI引數後,會解析URI路徑(path),然後根據路徑解析結果,提供相應的資料內容並將其返回;
- 此過程在A應用程式內部實現,當A應用程式封裝好了ContentProvider實現類,需要在Mainfest清單檔案中進行註冊,至此,A應用程式所在的程序已經提供了訪問自己所在程序資料的介面,B應用程式只需要根據資料內容的URI,發出訪問請求即可;
- 發出訪問資料請求的過程:
- 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元件是經過哪些呼叫,一步一步被啟動的呢?請看時序圖如下:
## 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
此函式的邏輯是,從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)!
相關推薦
ContentProvider啟動流程分析(一)
## 0x01 扯東扯西的前言&概述 作為安卓設計的四大元件之一,是跨程序共享資料的一把利器,所謂跨程序共享資料,通俗理解就是,應用程式A可以訪問操作應用程式B共享出來的資料,這些共享出來的資料一般都有其對應的URI(統一資源識別符號),那麼就涉及到兩個過程: 提供資料內容的過程: A應用(
Android9.0 Activity啟動流程分析(一)
1、ActivityRecord、TaskRecord、ActivityStack和ActivityDisplay介紹 本篇文章是基於Android refs/tags/android-9.0.0_r8分支的程式碼進行分析的 在分析Activity啟動的原始碼之前先介紹一下Act
ContentProvider啟動流程分析(三)
## 0x01 扯東扯西的前言&概述 ## 0x02 ContentProvider啟動流程分析 step15: ApplicationThread#bindApplication() 上一步,在ApplicationThreadProxy類的bindApplication()函式中,通過B
ContentProvider啟動流程分析(二)
## 0x01 扯東扯西的前言&概述 ## 0x02 ContentProvider啟動流程分析 step6: ActivityManagerProxy#getContentProvider() 代理類ActivityManagerProxy位於ActivityManagerNative.j
springboot啟動流程分析(一)
以springboot 2.0.2.RELEASE版本為例 1.pom.xml引入依賴 <parent> <groupId>org.springframework.boot</groupId>
SpringBoot啟動流程分析(一):SpringApplication類初始化過程
SpringBoot系列文章簡介 SpringBoot原始碼閱讀輔助篇: Spring IoC容器與應用上下文的設計與實現 SpringBoot啟動流程原始碼分析: SpringBoot啟動流程分析(一):SpringApplication類初始化過程 SpringBoot啟動流程分析(二)
SpringMVC的流程分析(一)—— 整體流程概括
classes amp 不同 方法 restfu equals 類圖 strong .get SpringMVC的整體概括 之前也寫過springmvc的流程分析,只是當時理解的還不透徹所以那篇文章就放棄了,現在比之前好了些,想著寫下來分享下,也能增強記憶,也希望可以幫助到
Android9.0 Activity啟動流程分析(二)
文章目錄 1、ActivityThread的main函式 2. AMS的attachApplication函式 2.1 Part-I 2.2 Part-II 2.2.1 ApplicationThread的bindApp
Web前端(一)-Vue專案執行流程分析(一)
去年年底離職了創業去了哈,把寫部落格的事情給落下了! 一直盯著專案幾乎很少休息,一個月能休息一天算好的啦,煎熬將近一年啊。 都說 “離職窮半年,創業窮三年”,終於嚐到其中滋味!哎。。。現在終於涼了!哈哈! 最近靜下心來,好好反思總結,其中寫部落格這件事,還是不能停止..
Vue框架專案實戰整理:3、Vue專案執行流程分析(一)
宣告:本教程不收取任何費用,歡迎轉載,尊重作者勞動成果,不得用於商業用途,侵權必究!!!! 上兩篇講了Vue框架專案實戰整理:1、快速啟動Vue、開發工具介紹 和 Vue框架專案實戰整理:2、Vue環境搭建,今天應該到Vue專案執行流程分析的時候了,為什麼會有這方便的研究
dart 非同步事件執行流程分析(一)
深入研究了dart 的非同步事件執行流程。 main() { /// testNoAwait() and testWithCallerAwait() exe order is same. // testNoAwait(); // testWithCal
[Android6.0][RK3399] 雙屏異顯程式碼實現流程分析(一)
Platform: RK3399 OS: Android 6.0 Version: v2016.08 本文分為兩部分。 《[RK3399] 雙屏異顯程式碼實現流程分析(一)》為分析 RK video 部分標準的程式碼(base on 2017.
Android OTA升級原理和流程分析(一)
這篇及以後的篇幅將通過分析update.zip包在具體Android系統升級的過程,來理解Android系統中Recovery模式服務的工作原理。我們先從update.zip包的製作開始,然後是Android系統的啟動模式分析,Recovery工作原理,如何從
springboot啟動流程分析(二)
現在繼續看啟動過程的詳情,詳細描述下SpringApplication建構函式: 1.載入過程中的SpringApplication初始化如下: public SpringApplication(ResourceLoader resourceLoader
Activity啟動流程筆記(一)
從startActivity開始說起: public void startActivity(Intent intent, Bundle options) { if (options != null) { startA
STM32 eCos 啟動程式碼分析(一)系統復位
概述 最近接觸了STM32,開始瞭解CortexM3系列ARM處理器上RTOS的移植和啟動。 開始總是艱難的,CortexM3是arm7tdmi的升級產品,但實際上和之前的ARM7有著很大的區別。 首先,我們必須有支援CortexM3的編譯器,因為CortexM3採用的是T
Vsync垂直同步訊號分發和SurfaceFlinger響應執行渲染流程分析(一)
void SurfaceFlinger::init() { ALOGI( "SurfaceFlinger's main thread ready to run. " "Initializing graphics H/W..."); status_t err;
electron-vue架構解析3-開發環境啟動流程分析(原)
上一節我們介紹了生產環境的打包流程,這一節我們來看開發環境的啟動流程。 該框架主要修改是對開發環境的優化,包括了於開發環境的配置檔案隔離,主程序和渲染程序配置檔案隔離,編譯過程提示等功能,因此這一節內容才是整個框架的核心。 我們從開發人員用到的啟動命令
SpringBoot啟動流程分析(二):SpringApplication的run方法
SpringBoot系列文章簡介 SpringBoot原始碼閱讀輔助篇: Spring IoC容器與應用上下文的設計與實現 SpringBoot啟動流程原始碼分析: SpringBoot啟動流程分析(一):SpringApplication類初始化過程 SpringBoot啟動流程分析(二)
SpringBoot啟動流程分析(四):IoC容器的初始化過程
SpringBoot系列文章簡介 SpringBoot原始碼閱讀輔助篇: Spring IoC容器與應用上下文的設計與實現 SpringBoot啟動流程原始碼分析: SpringBoot啟動流程分析(一):SpringApplication類初始化過程 SpringBoot啟動流程分析(二)