四大組件以及Application和Context的全面理解

本文首發(fā)于微信公眾號——世界上有意思的事,搬運轉(zhuǎn)載請注明出處,否則將追究版權(quán)責(zé)任妈橄。微信號:a1018998632庶近,交流qq群:859640274

一、概述

Context抽象結(jié)構(gòu)

閱讀須知

  • 1.文章中的縮寫指代的意思:CI——>ContextImpl眷蚓、AT——>ActivityThread鼻种、LA——>LoadedApk、CR——>ContentResolver沙热、PM——>PackageManager叉钥、SP——>SharedPreferences、APT——>ApplicationThread篙贸、AMS——>ActivityManagerService投队、PR——>ProcessRecord、AR——>ActivityRecord爵川、AS——>ActiveServices敷鸦、SR——>ServicecRecord。
  • 2.文章中的變量 mXXX(yyy)寝贡,括號中 yyy 表示該變量的類型扒披。
  • 3.文章中的方法 xxx(yyy),括號中 yyy 表示方法需要傳入的變量類型圃泡。

二碟案、用處

  • 1.Context的實現(xiàn)類有很多,但是 CI 是唯一做具體工作的颇蜡,其他實現(xiàn)都是對 CI 做代理价说。
  • 2.CI 中有一些成員對象,先來看看這些對象的用處:
    • 1.mSharedPrefsPaths风秤、sSharedPrefsCache:這兩個對象是用于獲取 SharedPreferences 的鳖目,在我前一篇博客里面有講到。全面剖析SharedPreferences唁情。
    • 2.mMainThread(AT):這個對象是一個 App 進程的主線程疑苔,一個 App 的 Framework 層就是從這里啟動的甫匹。
    • 3.mPackageInfo(LA):在 AT 初始化 App 的主線程的時候甸鸟,會將 Apk 加載到內(nèi)存中,Apk 在內(nèi)存中就是以這個對象的形式存在的兵迅,該對象可以加載 Apk 的資源和 Dex 文件抢韭。
    • 4.mUser(UserHandle):多用戶相關(guān)。
    • 5.mContentResolver( ApplicationContentResolver):繼承于 CR恍箭,主要功能是通過 Uri 來獲取文件挪钓、數(shù)據(jù)庫边锁、Asset蓬坡、Res 等數(shù)據(jù)苛败,還有就是通過 ContentProvider 來獲取其他應(yīng)用和本機數(shù)據(jù)。
    • 6.mResourcesManager(ResourcesManager):單例绊诲,因為一個 Apk 不同機型的適配資源,所以用來加載Resource對象,以保證一個 App 中所有的 CI 使用的都是同一份資源橡淑。
    • 7.mResources(Resources):獲取 Apk 中 Res 資源的對象。
    • 8.mOuterContext(Context):用于指向代理本對象的 Context咆爽,例如 Activity梁棠、Service 等。
    • 9.mTheme(Resources.Theme):主題
    • 10.mPackageManager(PM):包管理類斗埂,不僅可以獲取我們apk包的信息符糊,還能獲取本機apk包的信息。
  • 3.CI 中有很多 Api呛凶,我將這些 Api 歸了一下類
    • 1.獲取成員對象:即獲取上面我列出來的那些對象男娄,這些對象獲取到了之后又有更多 Api 暴露出來,在這里 CI 相當(dāng)于做了一個聚合把兔。最常用的就是 getResource() 了沪伙。
    • 2.獲取成員對象的成員對象:即為了方便,CI 封裝了一些常用的獲取成員對象中的信息的方法县好。例如getPackageName()围橡,是通過 PM 來獲取的。
    • 3.關(guān)于 SP 的操作:我們知道 SP 其實就是 Xml 文件缕贡,所以這里的操作有:獲取翁授、移動、刪除晾咪。
    • 4.文件操作:增刪移文件收擦、打開文件流、獲取 App 私有文件夾地址等等谍倦。
    • 5.數(shù)據(jù)庫操作:我們知道 Sqlite 其實是一種文件型數(shù)據(jù)庫塞赂,所以有:打開、創(chuàng)建昼蛀、移動宴猾、刪除、獲取數(shù)據(jù)庫文件路徑叼旋,等操作仇哆。
    • 6.壁紙相關(guān)操作:這個不是成員變量提供的,WallpaperManager 是系統(tǒng) Service 一種夫植,所以是SystemService 提供的讹剔。
    • 7.啟動Activity:包括一般啟動 Acitivyt、多用戶啟動 Activity、啟動多個 Activity延欠。
    • 8.廣播操作:發(fā)送普通廣播陌兑、發(fā)送需要權(quán)限的廣播、發(fā)送有序廣播由捎、發(fā)送粘連廣播诀紊、發(fā)送有序粘連廣播、多用戶廣播隅俘、移除各種廣播邻奠、注冊各種廣播、取消注冊各種廣播为居。
    • 9.Service 操作:啟動碌宴、停止、重啟蒙畴、綁定贰镣、解綁、獲取系統(tǒng)服務(wù)以及多用戶操作膳凝。
    • 10.權(quán)限操作:檢查本 App 是否有某種權(quán)限碑隆、檢查某 App 是否有某種權(quán)限、檢查Uri權(quán)限蹬音、授予權(quán)限等等上煤。
    • 11.各種情況下創(chuàng)建 CI,這個比較重要
      • 1.createSystemContext(AT):在 SystemService 創(chuàng)建的時候為其創(chuàng)建一個 CI
      • 2.create App Context(AT著淆,LA):在 App lication/Service創(chuàng)建的時候為其創(chuàng)建一個 CI
      • 3.createActivityContext(ActivityThread mainThread劫狠,LA, IBinder永部, int displayId独泞,Configuration):在 Activity 創(chuàng)建的時候為其創(chuàng)建一個 CI。

三苔埋、四大組件以及 App lication初始化與Context的關(guān)系

在了解 Binder 的時候有如下要注意的點

圖片

1.Activity初始化

  • 1.CI.startActivity():將調(diào)用交給 Instrumentation (負(fù)責(zé)監(jiān)控 Activity 和 AMS 的交互懦砂,所有 Activity 的本地進程到遠(yuǎn)端進程的調(diào)用轉(zhuǎn)換都是其來執(zhí)行),

  • 2.Instrumentation.execStartActivity():傳入一個 APT 然后通過 Binder 機制將調(diào)用過程轉(zhuǎn)移到(后稱AMS)所AMS 在的系統(tǒng)服務(wù)進程组橄,本地主線程則繼續(xù)運行荞膘,不過本地主線程后續(xù)也沒別的操作了。接下來就是本地的MessageQueue 等待 AMS 服務(wù)運行完畢晨炕,發(fā)送消息將 Activity 的啟動重新交給本地主線程衫画。

  • 3.AMS.startActivity():從這里開始會調(diào)用會按順序在 ActivityStarter毫炉、ActivityStackSupervisor瓮栗、ActivityStack 這三個類之間進行調(diào)用,主要會進行下面這些操作,不按順序:

    • 1.對 Intent 的內(nèi)容進行解析费奸,獲取目標(biāo) Activity 的信息弥激。
    • 2.根據(jù)傳入的 APT 獲取被調(diào)用 App 的信息封裝成 PR。
    • 3.將1愿阐、2和其他信息結(jié)合微服,將源 Activity 和目標(biāo) Activity 封裝成兩個 AR
    • 4.解析 Activity 的啟動模式 和當(dāng)前的 Activity 棧狀態(tài),判斷是否需要創(chuàng)建棧和 Activity 缨历。(注意這里的AR 有著 App 中的 Activity 的全部信息以蕴,可以將其看成系統(tǒng)服務(wù)里面的 Activity 的化身)
    • 5.獲取到 Activity 和 Activity 棧之后,接下來要判斷是否要將當(dāng)前 Activity 執(zhí)行 onPause() 以及讓使用Binder 執(zhí)行目標(biāo) Activity 的 onCreate() 和 onResume(注意這里 onStart() 會在 Binder 遠(yuǎn)程調(diào)用onCreate() 的時候直接執(zhí)行)辛孵,這里 AMS 進程會使用 APT 調(diào)用 App 進程的 Activity 執(zhí)行相應(yīng)的生命周期丛肮。
    • 6.在 AMS 中前置準(zhǔn)備一切就緒之后,會通過 APT 使用 Handler 的形式調(diào)用到 App 進程的 AT 中魄缚。
    • 7.最終到了ActivityStackSupervisor.realStartActivityLocked()中會使用 APT 將調(diào)用交給 App 進程——>AT.scheduleLaunchActivity()——>AT.handleLaunchActivity()
  • 4.AT.handleLaunchActivity():將有以下操作

    • 1.AT.performLaunchActivity:這個方法有以下操作
      • 1.創(chuàng)建對象LA(一個 App 只加載一次)
      • 2.創(chuàng)建對象 Activity
      • 3.創(chuàng)建對象 App lication(一個 App 宝与,只創(chuàng)建一次)
      • 4.**創(chuàng)建對象 CI **:CI.createActivityContext()
      • 5.Application、CI都 attach 到 Activity 對象:Activity.attach()
      • 6.執(zhí)行 onCreate():Instrumentation.callActivityOnCreate()——>Activity.performCreate()——>Activity.onCreate()
      • 7.執(zhí)行onStart():AT.performLaunchActivity——>Activity.performStart()——>Instrumentation.callActivityOnStart()——>Activity.onStart()
    • 2.AT.handleResumeActivity()
      • 1.AT.performResumeActivity()——>Activity.performResume()——>Instrumentation.callActivityOnResume()——>Activity.onResume()
      • 2.Activity.makeVisible()——>WindowManager.addView():開始進行View的繪制流程冶匹。
    • 3.從上面我們可以總結(jié)一下:在 AMS 將調(diào)用交給 App 進程之后习劫,三個生命周期都是在 App 進程被回調(diào)的,并且在 onResume() 之后View才進行繪制

2.Service初始化

  • 1.CI.startService()——>CI.startServiceCommon():在這里傳入一個 APT嚼隘,類似 Activity 啟動時的第二步诽里,將調(diào)用過程轉(zhuǎn)移到 AMS 中,本地主線程繼續(xù)運行飞蛹,等待 APT 從 AMS 進程將調(diào)用轉(zhuǎn)移到本地主線程中须肆。
  • 2.AMS.startService():到了 AMS 進程之后,Service 的啟動就會全權(quán)交給 AS(這是 AMS 用來管理 Service 的成員變量)
  • 3.AS.startServiceLocked():這里做了以下操作
    • 1.根據(jù)傳入的 APT 獲取被調(diào)用 App 的信息封裝成 PR
    • 2.解析 Intent 等參數(shù)獲取到 Service 的信息桩皿,封裝成 SR(這個類可以看做是 Service 在系統(tǒng)服務(wù)的化身豌汇,記錄了 Service 的一切信息)
    • 3.再進過一系列調(diào)用:AS.startServiceInnerLocked()——>AS.bringUpServiceLocked()——>AS.realStartServiceLocked() 到這里才是真正在 App 進程啟動 Service 的流程。
  • 4.AS.realStartServiceLocked():這里會有以下操作:
    • 1.SR.APT.scheduleCreateService():這里會將調(diào)用轉(zhuǎn)到 App 進程泄隔,但是當(dāng)前的進程還會繼續(xù)執(zhí)行拒贱,這里就到了 App 線程的APT,這個方法里有以下操作
      • 1.通過 Handler 轉(zhuǎn)到 AT.handleCreateService()
      • 2.創(chuàng)建對象 LA(一個 App 只加載一次)
      • 3.創(chuàng)建對象 Service
      • 4.創(chuàng)建對象 CI
      • 5.創(chuàng)建對象 Application(一個 App 只創(chuàng)建一次)
      • 6.Application佛嬉、CI分別 attach 到 Service 對象
      • 7.執(zhí)行 Service.onCreate() 回調(diào)
      • 8.此時 Service 已經(jīng)啟動了
    • 2.AS.sendServiceArgsLocked()——>SR. App.APT.scheduleServiceArgs():這里就轉(zhuǎn)到了 App 進程的 APT 中逻澳,這里會有以下操作:
      • 1.APT.scheduleServiceArgs()
      • 2.AT.handleServiceArgs()
      • 3.Service.onStartCommand()
      • 4.此時我們需要在 Service 中進行的操作將會執(zhí)行。

3.ContentProvider初始化

  • 1.AT.main()——>AT.attach()——>AMS.attach App lication():傳入一個 APT暖呕,調(diào)用轉(zhuǎn)到了 AMS 進程
  • 2.AMS.attachApplicationLocked():獲取到 ApplicationInfo 和 ProviderInfo 列表之后通過 APT 將調(diào)用轉(zhuǎn)回 App 進程斜做。
  • 3.APT.bindApplication()——>AT.handleBindApplication()——>AT.installContentProviders():到這里之后將會循環(huán)初始化 ContentProvider。
  • 4.AT.installProvider():這個方法里面有以下操作
    • 1.創(chuàng)建對象LA:CI.createPackageContext()中
    • 2.創(chuàng)建對象CI:CI.createPackageContext()中
    • 3.創(chuàng)建對象ContentProvider:ClassLoader創(chuàng)建
    • 4.CI attach到ContentProvider對象:ContentProvider.attachInfo()中
    • 5.執(zhí)行onCreate回調(diào):ContentProvider.attachInfo()中

4.BroadCastReceiver靜態(tài)初始化

因為動態(tài)廣播的注冊時進程已創(chuàng)建湾揽, 基本對象已創(chuàng)建完成瓤逼,只需要回調(diào)BroadcastReceiver 的 onReceive() 方法即可笼吟,所以這里不分析

  • 1.當(dāng)收到廣播時會調(diào)用AT.handleReceiver()

  • 2.創(chuàng)建對象LA(一個 App 只加載一次)

  • 3.創(chuàng)建對象BroadcastReceiver

  • 4.創(chuàng)建對象 Application

  • 5.從創(chuàng)建的 Application中獲取 CI

  • 6.執(zhí)行 onReceive() 回調(diào)

  • 5.Application初始化:由上面四個組件的初始化我們可以知道,當(dāng) App 還沒啟動的時候喚醒任意組件都會創(chuàng)建一個 Application霸旗,而這里分析的是正常情況啟動一個 App 的時候創(chuàng)建 Application的流程贷帮。

    • 1.這里的流程其實就是包含了ContentProvider 初始化的流程,所以前面都差不多
    • 2.最后到了AT.handleBindApplication()中诱告,這里有以下操作:
      • 1.創(chuàng)建對象 LA
      • 2.創(chuàng)建對象 CI
      • 3.創(chuàng)建對象 Instrumentation
      • 4.創(chuàng)建對象 Application;
      • 5.安裝 providers
      • 6.執(zhí)行 Create 回調(diào)

四撵枢、四大組件以及 App lication綁定Context的方法

由上一節(jié)我們可以知道,四大組件以及 App lication在初始化的時候都會進行Context的綁定或者創(chuàng)建精居,這節(jié)就來講講各個組件是如何對context進程賦值的锄禽。

  • 1.Activity:
    • 1.AT.performLaunchActivity()
    • 2.AT.createBaseContextForActivity(ActivityClientRecord , Activity)
    • 3.CI.createActivityContext(ActivityThread 靴姿, LoadedApk 沟绪, IBinder , int displayId 空猜, Configuration)
    • 4.CI():被賦值了 ActivityThread绽慈、LoadedApk、IBinder activityToken辈毯、Configuration
  • 2.Service/ App lication:
    • 1.AT.handleCreateService()
    • 2.CI.create App Context(ActivityThread 坝疼, LoadedApk)
    • 3.new CI():被賦值了 ActivityThread、LoadedApk
  • 3.BroadCastReceiver:在AT.handleReceiver()中直接獲取 App lication的Context谆沃,其自身并不創(chuàng)建Context
  • 4.ContentProvider:
    • 1.AT.installProvider()
    • 2.Context.createPackageContext()——>CI.createPackageContext()——>CI.createPackageContextAsUser():這里是通過一個 App lication的Context創(chuàng)建的Context钝凶,所以可以看做是 App lication的Context的一個復(fù)制。

五唁影、總結(jié)

1.組件初始化會創(chuàng)建的對象

image.png
  • 1.LoadedApk:所有組件在初始化的時候耕陷,如果LA沒被初始化都會初始化一遍
  • 2.Context:
    • 1.只有Activity的CI有上一個Activity的Token
    • 2.Receiver的Context是繼承于ContextWr App er 的 ReceiverRestrictedContext,不可綁定Service据沈。
    1. App lication:
    • 1.Receiver使用的Context是ReceiverRestrictedContext包裝的 App lication的Context哟沫,所以其可以通過Context獲取到 App lication
    • 2.ContentProvider一般是在 App 初始化的時候在初始化 App lication的過程中加載的,此時 App lication會被加載锌介。但是如果是多個 App 共享進程嗜诀,第二個 App 由ContentProvider調(diào)起,那么 App lication不會被初始化孔祸。

2.Context使用場景

image.png

說明: (圖中第一列代表不同的 Context隆敢, √代表允許在該 Context 執(zhí)行相應(yīng)的操作; ×代表不允許; -代表分情況討論)

  • 1.當(dāng) Context 為 Receiver的情況下:
    • 1.不允許執(zhí)行 bindService() 操作, 由于限制性上下文(ReceiverRestrictedContext)所決定的崔慧,會直接拋出異常.
    • 2.registerReceiver 是否允許取決于 receiver;
    • 3.當(dāng) receiver == null 用于獲取 sticky 廣播拂蝎, 允許使用。否則不允許使用registerReceiver惶室。
  • 2.縱向來看 startActivity 操作
    • 1.當(dāng)為 ActivityContext 則可直接使用;
    • 2.當(dāng)為其他 Context温自, 要啟動的 Activity 不屬于任何 Activity 棧玄货,所以必須帶上 FLAG_ACTIVITY_NEW_TASK flags 才能使用

3.getApplication() 和 getApplicationContext()

絕大多數(shù)情況下, getApplication() 和 getApplicationContext() 這兩個方法完全一致捣作, 返回值也相同; 那么兩者到底有什么區(qū)別呢? 真正理解這個問題的人非常少. 接下來徹底地回答下這個問題:

  • 1.getApplicationContext() 這個的存在是 Android 歷史原因. 我們都知道 getApplication() 只存在于 Activity 和Service 對象,那么對于 BroadcastReceiver 和 ContentProvider 卻無法獲取 Application鹅士, 這時就需要一個能在 Context 上下文直接使用的方法券躁, 那便是 getApplicationContext().
  • 2.對于 Activity/Service 來說, getApplication() 和 getApplicationContext() 的返回值完全相同掉盅,除非廠商修改過接口也拜。
  • 3.BroadcastReceiver 在 onReceive 的過程,能使用 getBaseContext().getApplicationContext 獲取所在 App lication趾痘,而無法使用 getApplication;
  • 4.ContentProvider 能使用 getContext().getApplicationContext() 獲取所在 Application. 絕大多數(shù)情況下沒有問題慢哈,但是有可能會出現(xiàn)空指針的問題。情況如下:當(dāng)同一個進程有多個 Apk 的情況下永票, 對于第二個 Apk 是由Provider 方式拉起的卵贱, 前面介紹過 Provider 創(chuàng)建過程并不會初始化所在 Application, 此時執(zhí) getContext().get ApplicationContext() 返回的結(jié)果便是 NULL侣集,所以對于這種情況要做好判空键俱。

參考文章

  • 1.Context全面理解
    不販賣焦慮,也不標(biāo)題黨世分。分享一些這個世界上有意思的事情编振。題材包括且不限于:科幻、科學(xué)臭埋、科技踪央、互聯(lián)網(wǎng)、程序員瓢阴、計算機編程畅蹂。下面是我的微信公眾號:世界上有意思的事,干貨多多等你來看荣恐。
    世界上有意思的事
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魁莉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子募胃,更是在濱河造成了極大的恐慌旗唁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痹束,死亡現(xiàn)場離奇詭異检疫,居然都是意外死亡,警方通過查閱死者的電腦和手機屎媳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門夺溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烛谊,你說我怎么就攤上這事。” “怎么了焙矛?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長村斟。 經(jīng)常有香客問我蟆盹,道長脱盲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任日缨,我火速辦了婚禮钱反,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匣距。我一直安慰自己面哥,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布毅待。 她就那樣靜靜地躺著尚卫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尸红。 梳的紋絲不亂的頭發(fā)上吱涉,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音外里,去河邊找鬼怎爵。 笑死,一個胖子當(dāng)著我的面吹牛盅蝗,可吹牛的內(nèi)容都是我干的鳖链。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼墩莫,長吁一口氣:“原來是場噩夢啊……” “哼芙委!你這毒婦竟也來了逞敷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤灌侣,失蹤者是張志新(化名)和其女友劉穎推捐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侧啼,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡牛柒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慨菱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰络。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡戴甩,死狀恐怖符喝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甜孤,我是刑警寧澤协饲,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站缴川,受9級特大地震影響茉稠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜把夸,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一而线、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恋日,春花似錦膀篮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谈截,卻和暖如春筷屡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背簸喂。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工毙死, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喻鳄。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓规哲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诽表。 傳聞我的和親對象是個殘疾皇子唉锌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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