深入理解Context
- 深入理解Context【收费】
概述
Context作用
- 四大组件相关
- 绑定/开启
Service
- 启动
Activity
- 获取
ContentResolver
- 注册/发送广播
- 绑定/开启
- 检查权限
- 文件/
SharedPreferences
相关 - 获取
Resource
相关 - 文件缓存目录/数据库相关
- 获取各种
SystemService
Context
,字面意思:语境、环境、上下文,在 Android 系统中,可以理解为当前对象在应用程序中所处的工作环境。其内部定义很多访问应用程序环境中全局信息的接口,通过它可以访问到应用程序的资源有关的类,如:Resources
、AssetManager
、Package
及权限相关信息等。还可以通过它调用应用程序级的操作,如:启动 Activity
和 Service
、发送广播等。
1 | /** |
翻译:Context
提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被 Android 系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。
Context结构
Context
是维持 Android 程序中各组件能够正常工作的一个核心功能类。Context
本身是一个抽象类,其主要实现类为 ContextImpl
,另外有直系子类两个:
ContextWrapper
ContextThemeWrapper
这两个子类都是Context
的代理类,它们继承关系如下:Context
是一个抽象类,定义一系列与系统交互的接口ContextImpl
继承自Context
抽象类,实现了Context
类中的抽象方法,是Context
类的具体实现类。它为Activity
及其它应用组件提供上下文环境,应用中使用到的Context
的方法就是其实现的ContextWrapper
继承自Context
抽象类,是Context
类的包装类(装饰器模式),内部维护一个Context
类型的成员变量mBase
指向一个ContextImpl
对象,ContextWrapper
里面的方法调用是通过mBase
来调用ContextImpl
里面的方法,这里用到了代理模式ContextThemeWrapper
继承自ContextWrapper
类,在ContextWrapper
的基础上增加与主题Theme
相关的逻辑,即可以指定Theme
的Context
包装类,用于在View
构造时为其提供Theme
属性集
通过 Context
的继承关系图并结合我们开发中比较熟悉的类:Activity
、Service
、Application
,所以我们可以认为 Context
一共有三种类型,分别是 Application
、Activity
和 Service
,他们分别承担不同的作用,但是都属于 Context
,而他们具有 Context
的功能则是由 ContextImpl
类实现的。
简单流程是:Application
,Activity
或 Service
通过 attach()
调用父类 ContextWrapper
的 attachBaseContext()
,从而设置父类成员变量 mBase
为 ContextImpl
对象,从而核心的工作都交给 ContextImpl
来处理。
ContextImpl
实现类中涉及的主要核心类是:ActivityThread
、LoadedApk
、PackageManager
和 ResourcesManager
,这几个类都是单例的,一个应用程序进程中是共用同一个对象的。Contextlmpl
是一种轻量级类,而 LoadedApk
是一个重量级类,Contextlmpl
中的大多数进行包操作的重量级函数实际上都是转向了 LoadedApk
对象相应的方法。
Activity
继承自 ContextThemeWrapper
,Application
、Service
继承自 ContextWrapper
,它们直接或间接的继承自 ContextWrapper
类,因此也拥有了一个 Context
类型的成员变量 mBase
指向一个 ContextImpl
对象,ContextImpl
是 Context
类的具体实现类,所以也都拥有了 Context
提供的所有功能。
代理模式:属于结构型模式,是指为其他对象提供一种代理以控制对这个对象的访问,代理模式又分为静态代理和动态代理。
装饰器模式:又叫包装模式,也是结构型模式,是指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
Context 数量
根据上面的 Context
类型我们可以知道。Context
一共有 Application
、Activity
和 Service
三种类型,因此在一个应用程序中 Context
数量的计算公式可以这样写:
1 | Context 数量 = Activity 数量 + Service 数量 + 1 |
上面的 1 代表着 Application
的数量,因为一个应用程序中可以有多个 Activity
和多个 Service
,但是只能有一个 Application
。
Context 详解
前面讲到 Context
的体系结构时,了解到其最终实现类有:Application
、Service
和 Activity
,它们都持有 ContextImpl
这个 Context
抽象类的真正实现,接下来对这三个实现类分别进行讨论分析。
Application Context
Application
是 Android 系统框架中的一个系统组件,当 Android 应用程序启动时系统会创建一个 Application
类的对象且只创建一个,用来存储系统的一些信息,即 Application
是单例的。
通常在开发过程中是不需要指定一个 Application
的,系统自动帮开发者创建,如果要创建应用自定义的 Application
,只需创建一个类继承 Application
并在 AndroidManifest.xml
文件中的 application
标签中进行注册(只需给 application
标签增加 name
属性,并添加自定义的 Application
的名字即可)。
通常自定义 Application
的目的是在应用程序启动时做一些全局的初始化工作,当应用程序启动时,Application
同步创建并启动,系统会创建⼀个 PID,即进程 ID,所有的 Activity
都会在此进程上运⾏,因此都可以取到这些初始化的全局变量的值,且由于 Application
对象在整个应用程序运行期间会一直存在,有开发者就会在 Application
中编写一些工具方法,全局获取使用,但是切记不要这样把 Application
当工具类使用。注意:这严重违背 Google 设计 Application 的原则,也违背设计模式中的单一职责原则。
自定义 Application 实例
1 | open class TestApplication : Application() { |
继承 Application
并重写 onCreate()
方法,在 Application
创建的时候调用,一般用于全局初始化,如第三方 SDK 的初始化、环境的配置等等,同时可以通过 TestApplication # context
来获取 Application
类型的全局 Context
对象。
获取 Application 实例
1 | class TestActivity : Activity() { |
获取 Application
的方法一般有两个:
Activity # getApplication()
或Service # getApplication()
Context # getApplicationContext()
通过getApplication()
和getApplicationContext()
都可以获取到Application
,那它们区别是什么呢?
通过日志输出可以看到,它们获取到的是同一个对象,但有同学要问了,那为什么还要提供两个功能一样的方法?因为 getApplication()
方法更加直观,但只能在 Activity
和 Service
场景中调用。getApplicationContext()
方法适用范围更广,任意场景中通过 Context
对象皆可以调用此方法。
Application Context 创建过程
Application
的 Context
是在应用被创建的时候创建的,要追踪其创建需要从应用程序的启动流程出发来探索,即从点击桌面应用图标开始到应用第一个界面展示出来的过程中的某一步,具体哪一步创建的,可以之前应用进程的启动分析。
简述一下过程:
ActivityThread
类作为应用初始化类,在其入口方法main()
方法中调用ActivityThread # attach()
方法中,然后通过Binder
通信跨进程调用到system_server
进程中AMS
的attachApplication()
方法,并将ApplicationThread
作为参数传递过去。- 通过传进来的
ApplicationThread
,跨进程通信调用应用进程中ApplicationThread # bindApplication()
方法绑定Application
。 ApplicationThread # bindApplication()
方法中,构建AppBindData
对象,然后通过内部类 H 发送BIND_APPLICATION
类型的Handler
消息,进而调用到ActivityThread # handleBindApplication()
方法创建并绑定Application
。
时序图
源码解析
通过过程简述与时序图可知,Application
的 Context
的创建是在 ActivityThread # handleBindApplication()
方法中创建的,跟踪查看源码进行详细解析。
ActivityThread # handleBindApplication()
1 | ActivityThread.class (api 30) |
ActivityThread # handleBindApplication()
方法的参数 AppBindData
是 AMS
传给应用程序的启动信息,其中包含 LoadedApk
、ApplicationInfo
等,然后通过 LoadedApk
实例对象创建 ContextImpl
和 Application
实例对象。
LoadedApk # makeApplication()
1 | LoadedApk.java (api 30) |
执行流程如下:
- 首先判断
LoadedApk
对象中的mApplication
是否存在,如果已经存在则直接返回。如果不存在,则先获取AndroidMenifest
中application
标签中name
属性指定的Application
类名。然后获取ClassLoader
,创建ContextImpl
实例。 - 通过类加载器
ClassLoader
创建Application
类实例,并将Application
实例赋值给ContextImpl
的成员变量mOuterContext
,以便ContextImpl
通过mOuterContext
访问Application
实例。同时将Application
实例赋值给mApplication
,调用Context # getApplicationContext()
方法获取的就是该实例对象。
ContextImpl
创建
1 | ContextImpl.java (api 30) |
创建一个 ContextImpl
实例对象,同时给 ContextImpl
赋值访问系统资源相关的 “权限” 对象 – ActivityThread
、LoadedApk
等。
Application 创建
回头继续看 LoadedApk # makeApplication()
的 Application
类实例的创建,代码如下:
1 | Instrumentation.java (api 30) |
获取 LoadedApk
的 AppComponentFactory
,然后通过 ClassLoader
加载 Application
类,并创建类的实例对象。接下来将 ContextImpl
实例赋值给创建的 Application
实例对象的 mBase
成员变量。
Application 绑定 ContextImpl
1 | Application.java (api 30) |
1 | ContextWrapper.java (api 30) |
在 Application
的 attach()
方法中调用 ContextWrapper
的 attachBaseContext()
方法,将 ContextImpl
实例对象赋值给其成员变量 mBase
。
Service Context
Service
是 Android 系统框架中四大组件的其中之一,它是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。可由其他应用组件启动(如:Activity
),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity
)已销毁也不受影响,接下来分析其 Context
实例的由来。
Service Context 创建过程
Service
的 Context
是在 Service
被其他应用组件启动的时候创建的(如:Activity
中启动),这里不详细分析 Service
的启动过程,也不是本篇文章的重点。具体可以参考之前针对 Service
的启动过程分析。
简述一下过程:
- 通常都是调用
Context # startService()
方法启动Service
,通过上面的分析可知,这里将调用其实现类ContextImpl # startService()
方法,然后通过Binder
通信跨进程调用到system_server
进程中AMS
的startService()
方法,在系统进程中经过一系列调用后,流程走到ApplicationThread
的scheduleCreateService()
方法,在方法中将AMS
进程中创建的ServiceInfo
等封装成CreateServiceData
对象。 ApplicationThread # scheduleCreateService()
方法中将CreateServiceData
实例对象通过内部类H
发送CREATE_SERVICE
类型的Handler
消息,进而调用到ActivityThread # handleCreateService()
方法创建Service
,同时创建Service
的Context
。
时序图
源码解析
通过过程简述与时序图可知,Service
的 Context
的创建是在 ActivityThread # handleCreateService()
方法中创建的,跟踪查看源码进行详细解析。
ActivityThread # handleCreateService()
1 | ActivityThread.class (api 30) |
执行流程如下:
- 获取
LoadedApk
实例对象,用于获取类加载器ClassLoader
等,然后创建一个ContextImpl
实例对象,即Service
的上下文环境,同时给ContextImpl
赋值访问系统资源相关的 “权限” 对象 –ActivityThread
、LoadedApk
等。 - 获取类加载器
ClassLoader
实例对象,并使用它加载Service
类并创建实例对象,将创建的Service
实例赋值给ContextImpl
的成员变量mOuterContext
,以便ContextImpl
通过mOuterContext
访问Service
实例。 - 调用
Service # attach()
方法将ContextImpl
实例绑定到Service
实例对象的mBase
成员变量,然后回调Service # onCreate()
方法,最后通过跨进程调用AMS # serviceDoneExecuting()
方法通知AMS
,Service
已启动完毕。
Service 绑定 ContextImpl
1 | Service.java (api 30) |
1 | ContextWrapper.java (api 30) |
在 Service
的 attach()
方法中继续调用 Service # attachBaseContext()
方法,然后继续调用父类 ContextWrapper # attachBaseContext()
方法将 ContextImpl
实例对象赋值给成员变量 mBase
。
Activity Context
Activity
是 Android 系统框架中四大组件中使用频率最多的,是用来给用户展示内容的界面,并与用户直接进行交互的组件。日常开发中 Activity
的 Context
会被经常用到,如通过 Context
启动新的 Activity
、启动 Service
及注册广播接收器等,下面一起来看一下 Activity
的 Context
实例的由来。
Activity Context 创建过程
Activity
的 Context
是在 Activity
组件启动的时候创建的,这里不详细分析 Activity
的启动过程,也不是本篇文章的重点,感兴趣的同学可以参考之前分析 Activity
的启动过程。
简述一下过程:
- 通常是调用
Activity # startActivity()
方法来启动Activity
,然后通过Binder
通信跨进程调用到system_server
进程中ATMS
的startActivity()
方法,在系统进程中经过一系列调用后,流程走到ApplicationThread
的scheduleTransaction()
方法。 ApplicationThread # scheduleTransaction()
方法中根据生命周期状态,来调度启动Activity
的事务LaunchActivityItem
,在LaunchActivityItem # execute()
方法中调用到ActivityThread # handleLaunchActivity()
方法来创建并启动Activity
,同时创建Activity
的Context
。
Android P(9.0) 开始 Activity
启动及生命周期有关的逻辑,被解耦成多个 Transaction 事务(如:LaunchActivityItem
、ResumeActivityItem
等),通过 ClientLifecycleManager
来调度事务的执行。
时序图
源码解析
通过过程简述与时序图可知,Activity
的 Context
的创建是在 ActivityThread # handleLaunchActivity()
方法中创建的,跟踪查看源码进行详细解析。
ActivityThread # handleLaunchActivity()
1 | ActivityThread.java (api 30) |
继续调用 ActivityThread # performLaunchActivity()
执行 Activity
的启动,继续跟踪启动流程。
ActivityThread # performLaunchActivity()
1 | ActivityThread.java (api 30) |
执行流程如下:
- 获取
LoadedApk
实例对象,用于获取类加载器ClassLoader
等,继续调用ActivityThread # createBaseContextForActivity()
方法,该方法中调用ContextImpl # createActivityContext()
方法创建ContextImpl
实例对象,即Activity
的上下文环境Context
。 - 调用
Instrumentation # newActivity()
方法加载并新建Activity
实例对象,该方法中调用AppComponentFactory # instantiateActivity()
方法,然后通过在ActivityThread # performLaunchActivity()
方法中获取的类加载器ClassLoader
加载并新建Activity
实例对象。 - 通过
LoadApk # makeApplication()
方法创建一个Application
对象,过程跟加载并新建Activity
类似,用到类加载器ClassLoader
。 - 执行
Activity # attach()
方法,通过该方法将ContextImpl
实例设置给Activity
,除此之外,方法中还完成了 Window 实例的创建并建立自己和 Window 的关联,这样当 Window 接收到外部输入事件后就可以将事件传递给Activity
。 - 执行
Instrumentation # callActivityOnCreate()
方法,该方法中调用Activity # performCreate()
方法,Activity # performCreate()
方法中调用Activity # onCreate()
方法。
ActivityThread # createBaseContextForActivity()
1 | ActivityThread.java (api 30) |
1 | ContextImpl.java (api 30) |
只看主流程有关源代码,调用 ContextImpl # createActivityContext()
创建 Activity
的 Context
。
至于主流程中 Activity
和 Application
的加载并新建过程感兴趣的可以跟进源码查看,主要是由类加载器 ClassLoader
加载后新建实例对象,下面主要来查看 Activity
绑定 Context
的流程。
Activity # attach()
1 | ContextImpl.java (api 30) |
1 | ContextThemeWrapper.java (api 30) |
1 | ContextWrapper.java (api 30) |
在 Activity
的 attach()
方法中继续调用 Activity # attachBaseContext()
方法,然后继续调用父类 ContextThemeWrapper # attachBaseContext()
方法,由于 ContextThemeWrapper
继承自 ContextWrapper
,因此继续调用 ContextWrapper # attachBaseContext()
方法将 ContextImpl
实例对象赋值给成员变量 mBase
。
总结
通过源码的深入分析可知,Application
、Service
和 Activity
直接或间接的继承自 ContextWrapper
类,因此也都拥有了一个 Context
类型的成员变量 mBase
指向一个 ContextImpl
对象,ContextImpl
是 Context
类的具体实现类,所以它们也就都拥有了 Context
提供的获取应用环境全局信息的接口功能。
Context 补充知识
前面的章节分析了 Application
、Service
和 Activity
等组件的创建以及绑定 ContextImpl
实例对象的流程,有同学会问了,四大组件中的 BroadcastReceiver
和 ContentProvider
创建的过程中没有绑定 ContextImpl
实例对象吗?
其实它们也有绑定的,只不过不是自身继承自 Context
,其 Context
实例是需要通过前面所述三者来提供,大致来看一下源码。
BroadcastReceiver 获取 Context 实例
开发中,通常是通过调用 Context # registerReceiver()
方法来注册广播接收器,这里的 Context
根据注册广播接收器时的场景可以是前面所述三者的任意一个来提供,这里以 Activity
场景中注册为例,调用 Context # registerReceiver()
方法注册,由上面的分析可知,此时会调用到 Context
的实现类 ContextImpl # registerReceiver()
方法中,代码如下:
1 | ContextImpl.java (api 30) |
注册广播接收器时,继续调用 ContextImpl # registerReceiverInternal()
方法并传入当前所处的上下文环境 - 即 Context
,这里通过 ContextImpl # getOuterContext()
获取该 Context
实例,这个方法是不是看着很熟悉,在前面 2.1.5.2 、2.2.3.1 及 2.3.3.2 小节中,通过 ContextImpl # setOuterContext()
方法为其赋值的,这也验证了上面的解析,Context
实例的获取是根据注册广播接收器时所处的场景来决定到底获取的是前面所述三者中的哪一个。
ContentProvider 获取 Context 实例
ContentProvider
是四大组件中被使用频率最低的一个,通常用来做跨进程共享数据,它是伴随着应用程序的启动由系统创建的,但它本身不属于 Context
体系结构,因此创建 ContentProvider
实例时所用的 Context
实例需要由别处获得。既然这样那就先看看在应用程序启动过程中的哪里创建的 ContentProvider
实例?
时序图
应用程序启动的流程这里不做详细解读,可以参考这篇文章的详细分析 – 深度详解 Android R(11.0)Activity 启动过程。应用程序在创建并绑定 Application
后,通过 ActivityThread # installContentProviders()
方法来创建并绑定 Context
的实例,一起探索源码来验证一下。
源码分析
ActivityThread # handleBindApplication()
1 | ActivityThread.class (api 30) |
在 ActivityThread # handleBindApplication()
方法中,调用 ActivityThread # installContentProviders()
方法并传入创建好的 Application
实例对象,继续调用 ActivityThread # installProvider()
方法来创建 ContentProvider
实例对象,创建过程跟上面分析的差不多,通过类加载器 ClassLoader
加载并新建 ContentProvider
实例对象,最后调用 ContentProvider # attachInfo()
方法为 ContentProvider
设置合适的上下文环境 - Context
。
ContentProvider # attachInfo()
1 | ContentProvider.class (api 30) |
ContentProvider # attachInfo()
方法中将 Context
实例对象赋值给 ContentProvider
的成员变量 mContext
,这样 ContentProvider
就可以使用 Context
提供的获取应用环境全局信息的接口功能,而这个 Context
也正是一开始 ActivityThread # handleBindApplication()
方法中传进来的 Application
实例对象(注意:成员变量 mContext
只允许设置一次)。
注意:在 ContentProvider # installProvider()
方法中会根据不同使用场景,获取对应场景下的 Context
实例对象,我们这里是分析的是同一个应用程序内,所以 ContentProvider
的成员变量 mContext
被赋值为传进来的 Application
实例对象。如果跨进程或者通过 Intent # setPackage()
指定了其它应用的包名等,则需要获取对应场景下的 Context
实例对象。
总结
本节内容补充了四大组件中 BroadcastReceiver
和 ContentProvider
是如何获取到 Context
实例对象的,它们虽是系统组件,但不是 Context
体系结构中的一员,但身为系统组件,它们同样需要用到 Context
所提供的获取应用环境全局信息的接口功能,因此抱着深入学习的态度,还是细细的把源码流程研读了一遍,梳理其创建及获取 Context
实例对象的流程。
总结
结合本文的讲解和源码解析,这里来看一下那些面试中问到过的问题,加深一下理解。
问题一:Context
会导致内存泄露吗?
一般 Context
导致的内存泄漏,几乎都是当 Context
销毁的时候,却因为被引用导致 GC
销毁失败,而 Application
的 Context
对象可以理解为随着应用进程存在的,所以这里总结给出使用 Context
时的一些建议:
- 当
Application
的Context
能搞定的情况下,且生命周期较长的对象,优先使用Application
的Context
。 - 不要让生命周期长于
Activity
的对象持有Activity
的引用。 - 尽量不要在
Activity
中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,推荐使用静态内部类,将外部实例引用作为弱引用持有。
问题二:getContext()
,getBaseContxet()
,getApplication()
及 getApplicationContext()
的区别?
在文章中已经解析过,getApplication()
和 getApplicationContext()
这俩个方法获取到的是同一个实例对象,只是使用场景范围的不同。getApplication()
方法更加直观,但只能在 Activity
和 Service
场景中调用。getApplicationContext()
方法适用范围更广,任意场景中通过 Context
对象皆可以调用此方法。那为何是同一个对象呢?简单看一下源码:
1 | Activity.class (api 30) |
1 | Service.class (api 30) |
首先看到 getApplication()
方法返回的是 Activity
和 Service
调用 attach()
方法时传入的 Application
实例对象。还记得文章前面的分析不,Activity # attach()
方法的调用,参见 2.3.3.2 ActivityThread # performLaunchActivity()
,而 Service # attach()
方法的调用,参见 2.2.3.1 ActivityThread # handleCreateService()
,在这两个方法中传给 attach()
方法的 Application
实例对象都是通过 LoadedApk # makeApplication()
方法来创建获取的。
再来看一下 getApplicationContext()
方法的返回值,虽然调用到 ContextWrapper
,但最终还是委托给实现类 ContextImpl
中实现的,源码如下:
1 | ContextImpl.java (api 30) |
这里根据 mPackageInfo
是否为空,分别调用了 mPackageInfo # getApplication()
方法和 mMainThread # getApplication()
方法,那就来看看这两个方法,首先在 LoadedApk
中看一下 getApplication()
方法的返回值,代码如下:
1 | LoadedApk.java (api 30) |
首先判断 LoadedApk
中的 mApplication
是否为空 (保证对象的单例),不为空直接返回,如果为空的话新建了一个 Application
实例对象然后赋值给 mApplication
。接着在 ActivityThread
中看一下 getApplication()
方法的返回值,代码如下:
1 | ActivityThread.java (api 30) |
ActivityThread # getApplication()
方法返回的 mInitialApplication
也是 LoadedApk # makeApplication()
方法返回的,所以可以得出 getApplicationContext()
方法在上述两种情况下返回的是同一个 Application
实例对象。
由于 getApplication()
方法返回的 Application
实例对象也是通过 LoadedApk # makeApplication()
方法来创建获取的,所以说 getApplication()
和 getApplicationContext()
这俩个方法返回的是同一个 Application
实例对象。
getContext()、getBaseContxet() 和 getApplicationContext() 方法的区别?
首先 getBaseContxet()
方法获取的是前面分析的赋值给 mBase
的 Context
的实现类的实例对象。getApplicationContext()
方法返回的 LoadedApk # makeApplication()
方法创建的 Application
实例对象。并且 Application
、Activity
和 Service
都有 getBaseContxet()
和 getApplicationContext()
这两个方法。而 getContext()
方法是在 Fragment
或 View
中用来获取其宿主对象的。