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

本文首发于微信公众号——世界上有意思的事,搬运转载请注明出处,否则将追究版权责任。微信号:a1018998632,交流qq群:859640274

一、概述

Context抽象结构

阅读须知

  • 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的实现类有很多,但是 CI 是唯一做具体工作的,其他实现都是对 CI 做代理。
  • 2.CI 中有一些成员对象,先来看看这些对象的用处:
    • 1.mSharedPrefsPaths、sSharedPrefsCache:这两个对象是用于获取 SharedPreferences 的,在我前一篇博客里面有讲到。全面剖析SharedPreferences
    • 2.mMainThread(AT):这个对象是一个 App 进程的主线程,一个 App 的 Framework 层就是从这里启动的。
    • 3.mPackageInfo(LA):在 AT 初始化 App 的主线程的时候,会将 Apk 加载到内存中,Apk 在内存中就是以这个对象的形式存在的,该对象可以加载 Apk 的资源和 Dex 文件。
    • 4.mUser(UserHandle):多用户相关。
    • 5.mContentResolver( ApplicationContentResolver):继承于 CR,主要功能是通过 Uri 来获取文件、数据库、Asset、Res 等数据,还有就是通过 ContentProvider 来获取其他应用和本机数据。
    • 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 相当于做了一个聚合。最常用的就是 getResource() 了。
    • 2.获取成员对象的成员对象:即为了方便,CI 封装了一些常用的获取成员对象中的信息的方法。例如getPackageName(),是通过 PM 来获取的。
    • 3.关于 SP 的操作:我们知道 SP 其实就是 Xml 文件,所以这里的操作有:获取、移动、删除。
    • 4.文件操作:增删移文件、打开文件流、获取 App 私有文件夹地址等等。
    • 5.数据库操作:我们知道 Sqlite 其实是一种文件型数据库,所以有:打开、创建、移动、删除、获取数据库文件路径,等操作。
    • 6.壁纸相关操作:这个不是成员变量提供的,WallpaperManager 是系统 Service 一种,所以是SystemService 提供的。
    • 7.启动Activity:包括一般启动 Acitivyt、多用户启动 Activity、启动多个 Activity。
    • 8.广播操作:发送普通广播、发送需要权限的广播、发送有序广播、发送粘连广播、发送有序粘连广播、多用户广播、移除各种广播、注册各种广播、取消注册各种广播。
    • 9.Service 操作:启动、停止、重启、绑定、解绑、获取系统服务以及多用户操作。
    • 10.权限操作:检查本 App 是否有某种权限、检查某 App 是否有某种权限、检查Uri权限、授予权限等等。
    • 11.各种情况下创建 CI,这个比较重要
      • 1.createSystemContext(AT):在 SystemService 创建的时候为其创建一个 CI
      • 2.create App Context(AT,LA):在 App lication/Service创建的时候为其创建一个 CI
      • 3.createActivityContext(ActivityThread mainThread,LA, IBinder, int displayId,Configuration):在 Activity 创建的时候为其创建一个 CI。

三、四大组件以及 App lication初始化与Context的关系

在了解 Binder 的时候有如下要注意的点

图片

1.Activity初始化

  • 1.CI.startActivity():将调用交给 Instrumentation (负责监控 Activity 和 AMS 的交互,所有 Activity 的本地进程到远端进程的调用转换都是其来执行),

  • 2.Instrumentation.execStartActivity():传入一个 APT 然后通过 Binder 机制将调用过程转移到(后称AMS)所AMS 在的系统服务进程,本地主线程则继续运行,不过本地主线程后续也没别的操作了。接下来就是本地的MessageQueue 等待 AMS 服务运行完毕,发送消息将 Activity 的启动重新交给本地主线程。

  • 3.AMS.startActivity():从这里开始会调用会按顺序在 ActivityStarter、ActivityStackSupervisor、ActivityStack 这三个类之间进行调用,主要会进行下面这些操作,不按顺序:

    • 1.对 Intent 的内容进行解析,获取目标 Activity 的信息。
    • 2.根据传入的 APT 获取被调用 App 的信息封装成 PR。
    • 3.将1、2和其他信息结合,将源 Activity 和目标 Activity 封装成两个 AR
    • 4.解析 Activity 的启动模式 和当前的 Activity 栈状态,判断是否需要创建栈和 Activity 。(注意这里的AR 有着 App 中的 Activity 的全部信息,可以将其看成系统服务里面的 Activity 的化身)
    • 5.获取到 Activity 和 Activity 栈之后,接下来要判断是否要将当前 Activity 执行 onPause() 以及让使用Binder 执行目标 Activity 的 onCreate() 和 onResume(注意这里 onStart() 会在 Binder 远程调用onCreate() 的时候直接执行),这里 AMS 进程会使用 APT 调用 App 进程的 Activity 执行相应的生命周期。
    • 6.在 AMS 中前置准备一切就绪之后,会通过 APT 使用 Handler 的形式调用到 App 进程的 AT 中。
    • 7.最终到了ActivityStackSupervisor.realStartActivityLocked()中会使用 APT 将调用交给 App 进程——>AT.scheduleLaunchActivity()——>AT.handleLaunchActivity()
  • 4.AT.handleLaunchActivity():将有以下操作

    • 1.AT.performLaunchActivity:这个方法有以下操作
      • 1.创建对象LA(一个 App 只加载一次)
      • 2.创建对象 Activity
      • 3.创建对象 App lication(一个 App ,只创建一次)
      • 4.**创建对象 CI **:CI.createActivityContext()
      • 5.Application、CI都 attach 到 Activity 对象:Activity.attach()
      • 6.执行 onCreate():Instrumentation.callActivityOnCreate()——>Activity.performCreate()——>Activity.onCreate()
      • 7.执行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.从上面我们可以总结一下:在 AMS 将调用交给 App 进程之后,三个生命周期都是在 App 进程被回调的,并且在 onResume() 之后View才进行绘制

2.Service初始化

  • 1.CI.startService()——>CI.startServiceCommon():在这里传入一个 APT,类似 Activity 启动时的第二步,将调用过程转移到 AMS 中,本地主线程继续运行,等待 APT 从 AMS 进程将调用转移到本地主线程中。
  • 2.AMS.startService():到了 AMS 进程之后,Service 的启动就会全权交给 AS(这是 AMS 用来管理 Service 的成员变量)
  • 3.AS.startServiceLocked():这里做了以下操作
    • 1.根据传入的 APT 获取被调用 App 的信息封装成 PR
    • 2.解析 Intent 等参数获取到 Service 的信息,封装成 SR(这个类可以看做是 Service 在系统服务的化身,记录了 Service 的一切信息)
    • 3.再进过一系列调用:AS.startServiceInnerLocked()——>AS.bringUpServiceLocked()——>AS.realStartServiceLocked() 到这里才是真正在 App 进程启动 Service 的流程。
  • 4.AS.realStartServiceLocked():这里会有以下操作:
    • 1.SR.APT.scheduleCreateService():这里会将调用转到 App 进程,但是当前的进程还会继续执行,这里就到了 App 线程的APT,这个方法里有以下操作
      • 1.通过 Handler 转到 AT.handleCreateService()
      • 2.创建对象 LA(一个 App 只加载一次)
      • 3.创建对象 Service
      • 4.创建对象 CI
      • 5.创建对象 Application(一个 App 只创建一次)
      • 6.Application、CI分别 attach 到 Service 对象
      • 7.执行 Service.onCreate() 回调
      • 8.此时 Service 已经启动了
    • 2.AS.sendServiceArgsLocked()——>SR. App.APT.scheduleServiceArgs():这里就转到了 App 进程的 APT 中,这里会有以下操作:
      • 1.APT.scheduleServiceArgs()
      • 2.AT.handleServiceArgs()
      • 3.Service.onStartCommand()
      • 4.此时我们需要在 Service 中进行的操作将会执行。

3.ContentProvider初始化

  • 1.AT.main()——>AT.attach()——>AMS.attach App lication():传入一个 APT,调用转到了 AMS 进程
  • 2.AMS.attachApplicationLocked():获取到 ApplicationInfo 和 ProviderInfo 列表之后通过 APT 将调用转回 App 进程。
  • 3.APT.bindApplication()——>AT.handleBindApplication()——>AT.installContentProviders():到这里之后将会循环初始化 ContentProvider。
  • 4.AT.installProvider():这个方法里面有以下操作
    • 1.创建对象LA:CI.createPackageContext()中
    • 2.创建对象CI:CI.createPackageContext()中
    • 3.创建对象ContentProvider:ClassLoader创建
    • 4.CI attach到ContentProvider对象:ContentProvider.attachInfo()中
    • 5.执行onCreate回调:ContentProvider.attachInfo()中

4.BroadCastReceiver静态初始化

因为动态广播的注册时进程已创建, 基本对象已创建完成,只需要回调BroadcastReceiver 的 onReceive() 方法即可,所以这里不分析

  • 1.当收到广播时会调用AT.handleReceiver()

  • 2.创建对象LA(一个 App 只加载一次)

  • 3.创建对象BroadcastReceiver

  • 4.创建对象 Application

  • 5.从创建的 Application中获取 CI

  • 6.执行 onReceive() 回调

  • 5.Application初始化:由上面四个组件的初始化我们可以知道,当 App 还没启动的时候唤醒任意组件都会创建一个 Application,而这里分析的是正常情况启动一个 App 的时候创建 Application的流程。

    • 1.这里的流程其实就是包含了ContentProvider 初始化的流程,所以前面都差不多
    • 2.最后到了AT.handleBindApplication()中,这里有以下操作:
      • 1.创建对象 LA
      • 2.创建对象 CI
      • 3.创建对象 Instrumentation
      • 4.创建对象 Application;
      • 5.安装 providers
      • 6.执行 Create 回调

四、四大组件以及 App lication绑定Context的方法

由上一节我们可以知道,四大组件以及 App lication在初始化的时候都会进行Context的绑定或者创建,这节就来讲讲各个组件是如何对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,其自身并不创建Context
  • 4.ContentProvider:
    • 1.AT.installProvider()
    • 2.Context.createPackageContext()——>CI.createPackageContext()——>CI.createPackageContextAsUser():这里是通过一个 App lication的Context创建的Context,所以可以看做是 App lication的Context的一个复制。

五、总结

1.组件初始化会创建的对象

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调起,那么 App lication不会被初始化。

2.Context使用场景

image.png

说明: (图中第一列代表不同的 Context, √代表允许在该 Context 执行相应的操作; ×代表不允许; -代表分情况讨论)

  • 1.当 Context 为 Receiver的情况下:
    • 1.不允许执行 bindService() 操作, 由于限制性上下文(ReceiverRestrictedContext)所决定的,会直接抛出异常.
    • 2.registerReceiver 是否允许取决于 receiver;
    • 3.当 receiver == null 用于获取 sticky 广播, 允许使用。否则不允许使用registerReceiver。
  • 2.纵向来看 startActivity 操作
    • 1.当为 ActivityContext 则可直接使用;
    • 2.当为其他 Context, 要启动的 Activity 不属于任何 Activity 栈,所以必须带上 FLAG_ACTIVITY_NEW_TASK flags 才能使用

3.getApplication() 和 getApplicationContext()

绝大多数情况下, getApplication() 和 getApplicationContext() 这两个方法完全一致, 返回值也相同; 那么两者到底有什么区别呢? 真正理解这个问题的人非常少. 接下来彻底地回答下这个问题:

  • 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. 绝大多数情况下没有问题,但是有可能会出现空指针的问题。情况如下:当同一个进程有多个 Apk 的情况下, 对于第二个 Apk 是由Provider 方式拉起的, 前面介绍过 Provider 创建过程并不会初始化所在 Application, 此时执 getContext().get ApplicationContext() 返回的结果便是 NULL,所以对于这种情况要做好判空。

参考文章

  • 1.Context全面理解
    不贩卖焦虑,也不标题党。分享一些这个世界上有意思的事情。题材包括且不限于:科幻、科学、科技、互联网、程序员、计算机编程。下面是我的微信公众号:世界上有意思的事,干货多多等你来看。
    世界上有意思的事
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容