Android | 一個進程有多少個 Context 對象(答對的不多)

前言

  • “一個進程有多少 Context 對象基茵?” 這是一個比較初級的問題奋构。但是,從這個問題卻可以看出面試者對Android源碼是否具備最基本的認識拱层;
  • 如果你試圖直接從網(wǎng)上尋找答案弥臼,而不是自己閱讀源碼,很可能你會給出這樣的答案:Context對象個數(shù) = Service對象個數(shù) + Activity對象個數(shù) + 1舱呻, 因為網(wǎng)上 99% 的文章 / 面經(jīng)就是這么講的醋火。但是,你覺得他們說得對嗎箱吕?

目錄

1. Context 繼承關(guān)系

Context是一個抽象類芥驳,具體的實現(xiàn)類有ApplicationActivity茬高、ServiceContextImpl兆旬。為方便區(qū)分,通常也稱為ApplicationConext怎栽、ActivityContextServiceContext丽猬,具體 UML 類圖如下:

可以看到宿饱,除了我們熟悉的ApplicationActivity脚祟、Service谬以,繼承關(guān)系上還有ContextWrapperContextThemeWrapper,它們的作用 & 職責如下:

  • ContextWrapper
    • 定義:Context包裝類

    • 作用:持有基礎(chǔ)對象的引用(mBase)由桌,并且實現(xiàn)了Context接口为黎,將所有方法調(diào)用請求轉(zhuǎn)發(fā)給基礎(chǔ)對象

// ContextWrapper.java

Context mBase;

public ContextWrapper(Context base) {
    mBase = base;
}
    
// 【分析點1:綁定基礎(chǔ)對象(見todo)】
protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

@Override
public void startActivity(Intent intent) {
    // 轉(zhuǎn)發(fā)給 mBase
    mBase.startActivity(intent);
}
  • ContextThemeWrapper
    • 定義:Context包裝類
      【todo】

2. Application 對象

我們都知道,在啟動四大組件(Activity行您、Service铭乾、ContentProvider, BroadcastReceiver)時,如果對應(yīng)的進程未啟動娃循,就需要先創(chuàng)建進程炕檩,相應(yīng)地也會創(chuàng)建一個Application對象。若還不了解捌斧,請務(wù)必閱讀:《Android | 帶你理解 Application 的創(chuàng)建過程》。簡單來說:

  • system_server進程骤星,通過AMS#getProcessRecordLocked(...)獲取進程信息(ProcessRecord)经瓷;
  • 若不存在,則調(diào)用AMS#startProcessLocked(...)創(chuàng)建進程
  • Zygote孵化目標進程之后洞难,在目標進程反射執(zhí)行ActivityThread#main()舆吮,并最終在ActivityThread#handleBindApplication(...)中創(chuàng)建Application對象
// ActivityThread.java

Application mInitialApplication;
final ArrayList<Application> mAllApplications = new ArrayList<Application>();

private void handleBindApplication(AppBindData data) {
    // ...
    Application app;
    // data.info 為 LoadedApk.java
    app = data.info.makeApplication(data.restrictedBackupMode, null);
    // ...
    mInitialApplication = app;
    // ...
}

// LoadedApk.java

private Application mApplication;

public Application makeApplication(...) {
    // 創(chuàng)建基礎(chǔ)對象 ContextImpl
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
    // 反射調(diào)用創(chuàng)建 Application 對象
    app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
    // ContextImpl 也持有包裝類 Application
    appContext.setOuterContext(app);
    // 保存創(chuàng)建的 Application 對象
    mActivityThread.mAllApplications.add(app);
    mApplication = app;
}

// Instrumentation.java

public Application newApplication(ClassLoader cl, String className, Context context) {
    // 反射調(diào)用創(chuàng)建 Application 對象
    Application app = getFactory(context.getPackageName()).instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

// Application.java
final void attach(Context context) {
    // 設(shè)置包裝類 Application 的基礎(chǔ)對象
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

總結(jié)要點如下:

  • 一個Application對象相當于存在兩個Context對象(代理對象與基礎(chǔ)對象)
  • Application對象與ContextImpl對象相互引用

3. Activity 對象

這一節(jié)我們來看Activity對象的創(chuàng)建過程,若還不了解队贱,請務(wù)必閱讀:《Android | 帶你理解 startActivity() 的執(zhí)行過程》色冀,簡單來說:

  • 創(chuàng)建Application對象之后,最后在ActivityThread#handleLaunchActivity(...)中創(chuàng)建Activity對象
// ActivityThread.java

public Activity handleLaunchActivity(...) {
    // ...
    final Activity a = performLaunchActivity(r, customIntent);
    // ...
}

private Activity performLaunchActivity(...) {
    // ...
    // 創(chuàng)建基礎(chǔ)對象 ContextImpl
    ContextImpl appContext = ContextImpl.createActivityContext(...);
    // 反射調(diào)用創(chuàng)建 Activity 對象
    Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    // 相互引用
    appContext.setOuterContext(activity);
    activity.attach(appContext,...)
    // ...
}

總結(jié)要點如下:

  • 一個Activity對象相當于存在兩個Context對象(代理對象與基礎(chǔ)對象)
  • Activity對象與ContextImpl對象相互引用

4. Service 對象

這一節(jié)我們來看Service對象的創(chuàng)建過程柱嫌,若還不了解锋恬,請務(wù)必閱讀:《Android | 帶你理解 startService() 的執(zhí)行過程》《Android | 帶你理解 bindService() 的執(zhí)行過程》,簡單來說:

  • 創(chuàng)建Application對象之后编丘,startService(...)最后在ActivityThread#handleCreateService(...)中創(chuàng)建Service對象

  • 創(chuàng)建Application對象之后与学,bindService(...)最后在ActivityThread#handleCreateService(...)中創(chuàng)建Service對象,在ActivityThread#handleBindService(...)中綁定Service(注意:兩個方法都在遠程進程執(zhí)行)

// ActivityThread.java

private void handleCreateService(...) {
    // ...
    // 反射調(diào)用創(chuàng)建 Serivce 對象
    Serivce service = packageInfo.getAppFactory().instantiateService(cl, data.info.name, data.intent);
    // 創(chuàng)建基礎(chǔ)對象 ContextImpl
    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
    // 相互引用
    context.setOuterContext(service);
    service.attach(...);
}

總結(jié)要點如下:

  • 一個Serivce對象相當于存在兩個Context對象(代理對象與基礎(chǔ)對象)
  • Serivce對象與ContextImpl對象相互引用

5. 問題回歸

到這里嘉抓,我們回歸開頭提出的問題索守,結(jié)論是:Context個數(shù) = Service個數(shù) + Activity個數(shù) + Application個數(shù) + ContextImpl個數(shù)

考慮到Application等與ContextImpl間的代理關(guān)系抑片,也可以寫為:Context 個數(shù) = 2 x(Service 個數(shù) + Activity 個數(shù) + Application 個數(shù)) + 其他 ContextImpl 個數(shù)

可能有的小伙伴會問卵佛,“這個問題沒有實際價值啊,不知道答案照樣可以正常開發(fā)”。是的截汪,如果僅僅滿足于對Context的字典式認知疾牲,那么這個問題確實不會發(fā)揮太大的價值。
更重要的是以這個問題為線索衙解,去理解四大組件的啟動流程 & 原理阳柔,甚至去發(fā)掘更多問題,例如:

5.1 既然 Android 的另外兩大組件 ContentProvider & BroadcastReceiver 不是 Context 的實現(xiàn)類蚓峦,那么它們是怎么拿到 Context 對象的引用呢盔沫?

請務(wù)必閱讀:《Android | 帶你理解 Broadcast 廣播機制》 & 《Android | 帶你理解 ContentProvider 機制》

5.2 ApplicationContext、ActivityContext 與 ServiceContext有什么區(qū)別枫匾?

請務(wù)必閱讀:《Android | 徹底拆解 Context 的功能模塊》

5.3 View & Fragment & Window 的getContext()是ActivityContext嗎?

這里有的小伙伴可能就會說“當然是啦”拟淮,真的是這樣嗎干茉?如果這個View是一個懸浮窗呢?請務(wù)必閱讀文章:
《Android | View & Fragment & Window 的getContext() 真的是Activity嗎很泊?》

5.4 第三方庫如何獲得Context對象角虫?

請務(wù)必閱讀:《Android | 使用 ContentProvider 無侵入獲取 Context》

請繼續(xù)關(guān)注彭旭銳的簡書!

參考資料

《理解Android Context》 —— Gityuan 著


推薦閱讀

感謝喜歡!你的點贊是對我最大的鼓勵昏兆!歡迎關(guān)注彭旭銳的簡書枫虏!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市爬虱,隨后出現(xiàn)的幾起案子隶债,更是在濱河造成了極大的恐慌,老刑警劉巖跑筝,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件死讹,死亡現(xiàn)場離奇詭異,居然都是意外死亡曲梗,警方通過查閱死者的電腦和手機赞警,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虏两,“玉大人愧旦,你說我怎么就攤上這事〉饩伲” “怎么了忘瓦?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我耕皮,道長境蜕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任凌停,我火速辦了婚禮粱年,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罚拟。我一直安慰自己台诗,他們只是感情好,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布赐俗。 她就那樣靜靜地躺著拉队,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阻逮。 梳的紋絲不亂的頭發(fā)上粱快,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音叔扼,去河邊找鬼事哭。 笑死,一個胖子當著我的面吹牛瓜富,可吹牛的內(nèi)容都是我干的鳍咱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼与柑,長吁一口氣:“原來是場噩夢啊……” “哼谤辜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仅胞,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤每辟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后干旧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渠欺,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年椎眯,在試婚紗的時候發(fā)現(xiàn)自己被綠了挠将。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡编整,死狀恐怖舔稀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掌测,我是刑警寧澤内贮,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響夜郁,放射性物質(zhì)發(fā)生泄漏什燕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一竞端、第九天 我趴在偏房一處隱蔽的房頂上張望屎即。 院中可真熱鬧,春花似錦事富、人聲如沸技俐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雕擂。三九已至,卻和暖如春贱勃,著一層夾襖步出監(jiān)牢的瞬間捂刺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工募寨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人森缠。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓遥金,卻偏偏與公主長得像捌锭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344