从面试官角度分析:介绍一下Android中的Context?
- Context是什么
- Context的结构
- Context的注意事项
问题正解:
一、Context是什么
Context 是 Android 中用的十分常见的一种概念,常被翻译成上下文,这个概念在其他的技术中也有运用。Android 官方对它的解释,可以理解为应用程序环境中全局信息的接口,它整合了相当多系统级的服务,可以用来得到应用中的类、资源,以及可以进行应用程序级的调起操作,比如启动 Activity、Service等等,而且 Context 这个类是 抽象abstract 的,不含有具体的函数实现。
二、Context结构
Context 是维持 Android 程序中各组件能够正常工作的一个核心功能类。
Context 本身是一个抽象类,其主要实现类为 ContextImpl,另有直系子类两个:
- ContextWrapper
- ContextThemeWrapper
这两个子类是 Context 的代理类,它们继承关系如下:
ContextImpl类介绍
ContextImpl 是 Context API 的十分常见实现,它为 Activity 和其他应用程序组件提供基本上下文对象,说白了就是 ContextImpl 实现了抽象类的方法,我们在使用 Context 的时候的方法就是它实现的。
ContextWrapper类介绍
ContextWrapper 类代理 Context 的实现,将其所有调用简单地代理给另一个 Context 对象(ContextImpl),可以被分类为修饰行为而不更改原始 Context 的类,其实就 Context 类的修饰类。真正的实现类是 ContextImpl,ContextWrapper 里面的方法执行也是执行 ContextImpl 里面的方法。
ContextThemeWrapper
就是一个带有主题的封装类,比 ContextWrapper 多了主题,它的一个直接子类就是 Activity。
通过Context的继承关系图结合我们几个开发中比较常见的类,Activity、Service、Application,所以Context 一共有三种类型,分别是 Application、Activity 和Service,他们分别承担不同的责任,都属于 Context,而他们具有 Context 的功能则是由ContextImpl 类实现的。
三、Context的数量
其实根据上面的 Context 类型我们就已经可以得出答案了。Context 一共有 Application、Activity 和 Service 三种类型对象,因此一个应用程序中Context 数量的计算公式就可以这样写:
Context数量 = Activity数量 + Service数量 + 1
上面的1代表着 Application 的数量,因为一个应用程序中可以有多个 Activity 和多个 Service,但是只能有一个 Application。
四、Context注意事项
Context 如果使用不恰当很容易引起内存泄露问题。
最简单的例子比如说使用了 Context 的错误的单例模式:
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Synchronized Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
上述代码中,我们使得了一个静态对象持有 Context 对象,而静态数据的生命一般是长于普通对象的,因此当 Context 被销毁(例如假设这里持有的是 Activity 的上下文对象,当 Activity 被销毁的时候),因为 instance 仍然持有 Context 的引用,导致 Context 虽然被销毁了但是却无法被GC机制回收,因为造成内存泄露问题。
而一般因为Context所造成的内存泄漏,基本上都是 Context 已经被销毁后,却因为被引用导致GC回收失败。但是 Application 的 Context 对象却会随着当前进程而一直存在,所以使用 Context 是应当注意:
- 当 Application 的 Context 能完成需要的情况下,并且生命周期长的对象,优先使用 Application 的 Context。
- 不要让生命周期长于 Activity 的对象持有到 Activity 的引用。
- 尽量不在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
五、如何正确回复以上面试题
- 面试官:Android 中有多少类型的 Context,它们有什么区别?
回答总共有 Activity 、Service、Application 这些 Context 。
共同点:它们都是 ContextWrapper 的派生类,而 ContextWrapper 的成员变量 mBase 可以用来存放系统实现的 ContextImpl,这样我们在执行如 Activity 的 Context 方法时,都是通过静态代理的方式最终执行到 ContextImpl 的方法。我们调用 ContextWrapper 的 getBaseContext 方法就能得到 ContextImpl 的实例。
不同点:它们有各自不同的生命周期;在功能上,只有 Activity 显示界面,正因为如此,Activity 继承的是 ContextThemeWrapper 提供一些关于主题、界面显示的能力,间接继承了 ContextWrapper ;而 Applicaiton 、Service 都是直接继承 ContextWrapper ,所以我们注意一点,但凡跟 UI 有关的,都应该用 Activity 作为 Context 来处理,不然要么会报错,要么 UI 会使用系统默认的主题。
- 面试官:一个APP应用里有几个 Context 呢?
Context 一共有 Application 、Activity 和 Service 三种类型,因此一个应用程序中 Context 数量的计算公式就可以这样写:
Context 数量 = Activity 数量 + Service 数量 + 1
上面的1代表着 Application 的数量,因为一个App中可以有多个Activity和多个 Service,但是只能有一个 Application。
- 面试官:Android 开发过程中,Context 有什么用?
Context 就等于 Application 的大管家,主要负责:
- 四大组件的信息交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等。
- 获取系统/应用资源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等。
- 文件,包括获取缓存文件夹、删除文件、SharedPreference 相关等。
- 数据库(SQLite)相关,包括打开数据库、删除数据库、获取数据库路径等。
其它辅助功能,比如配置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生
- 面试官:ContextImpl 实例是什么时候生成的,在 Activity 的 onCreate 里能拿到这个实例吗?
可以。我们开发的时候,经常会在 onCreate 里拿到 Application,如果用 getApplicationContext 取,最终调用的就是 ContextImpl 的 getApplicationContext 方法,如果调用的是 getApplication 方法,虽然没调用到 ContextImpl ,但是返回 Activity 的成员变量 mApplication 和 ContextImpl 的初始化时机是一样的。 再说下它的原理,Activity 真正开始启动是从 ActivityThread.performLaunchActivity 开始的,这个方法做了这些事:
- 通过 ClassLoader 去加载目标 Activity 的类,从而创建 对象。
- 从 packageInfo 里获取 Application 对象。
- 调用 createBaseContextForActivity 方法去创建 ContextImpl。
- 调用 activity.attach ( contextImpl , application) 这个方法就把 Activity 和 Application 以及 ContextImpl 关联起来了,就是上面结论里说的时机一样。
- 最后调用 activity.onCreate 生命周期回调。
通过以上的分析,我们知道了 Activity 是先创建类,再初始化 Context ,最后调用 onCreate , 从而得出问题的答案。不仅 Activity 是这样, Application 、Service 里的 Context 初始化也都是这样的。
- 面试官:ContextImpl 、ContextWrapper、ContextThemeWrapper 有什么区别?
- ContextWrapper、ContextThemeWrapper 都是 Context 的代理类,二者的区别在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以传入自己的配置初始化。
- ContextImpl 是 Context 的主要实现类,Activity、Service 和 Application 的 Base Context 都是由它建立的,即 ContextWrapper 代理的就是 ContextImpl 对象本身。
- ContextImpl 和 ContextThemeWrapper 的主要区别是, ContextThemeWrapper 有 Configuration 对象,Resource 可以根据这个对象来初始化。
- Service 和 Application 使用同一个 Recource,和 Activity 使用的 Resource 不同。
- 面试官:Activity Context、Service Context、Application Context、Base Context 有什么区别?
- Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 创建的,且创建的都是 ContextImpl 对象,即它们都是 ContextImpl 的代理类 。
- Service 和 Application 使用相同的Recource,和 Activity 使用的 Resource 不同。
- getApplicationContext 返回的就是 Application 对象本身,一般情况下它对应的是应用本身的 Application 对象,但也可能是系统的某个 Application。
- 面试官:为什么不推荐使用 BaseContext?
- 对于 Service 和 Application 来说,不推荐使用 Base Context,是担心用户修改了 Base Context 而导致出现错误。
- 对于 Activity 而言,除了担心用户的修改之外,Base Context 和 Activity 本身对于 Reource 以及 Theme 的相关行为是不同的(如果应用了 Configuration 的话),使用 Base Context 可能出现无法预期的现象。
- 面试官:ContentProvider 里的 Context 是什么时候初始化的呢?
ContentProvider 不是 Context ,但是它有一个成员属性 mContext ,是通过构造函数传入的。那么这个问题就变成了,ContentProvider 什么时候创建。应用创建 Application 是通过执行 ActivityThread.handleBindApplication 方法,这个方法的相关流程有:
- 创建 Application
- 初始化 Application 的 Context
- 执行 installContentProviders 并传入刚创建好的 Application 来创建 ContentProvider
- 执行 Application.onCreate
得出结论,ContentProvider 的 Context 是在 Applicaiton 创建之后,但是 onCreate 方法调用之前初始化的。
- 面试官:BroadcastReceiver 里的 Context是哪来的?
广播接收器,分动态注册和静态注册。
- 动态注册很简单,在调用 Context.registerReceiver 动态注册 BroadcastReceiver 时,会生成一个 ReceiverDispatcher 会持有这个 Context ,这样当有广播分发到它时,执行 onReceiver 方法就可以把 Context 传递过去了。当然,这也是为什么不用的时候要 unregisterReceiver 取消注册,不然这个 Context 就泄漏了哦。
- 静态注册时,在分发的时候最终执行的是 ActivityThread.handleReceiver ,这个方法直接通过 ClassLoader 去创建一个 BroadcastReceiver 的对象,而传递给 onReceiver 方法的 Context 则是通过 context.getReceiverRestrictedContext() 生成的一个以 Application 为 mBase 的 ContextWrapper。注意这边的 Context 不是 Application 。
链接:https://juejin.cn/post/7254812112563585080
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。