阅读 262

Android 开发者,你真的懂 Context 吗?

Context 相信所有的 Android 开发人员基本上每天都在接触,因为它太常见了。但是这并不代表每位 Android 开发者真正搞懂 Context!

下列面试题,你真的都懂吗?

(后文有回答)

  1. 面试官:Android 中有哪些类型的 Context,它们有什么区别?
  2. 面试官:一个APP应用里有几个 Context 呢?
  3. 面试官:Android 开发过程中,Context 有什么用?
  4. 面试官:ContextImpl 实例是什么时候生成的,另外在 Activity 的 onCreate 里能拿到这个实例吗?
  5. 面试官:ContextImpl 、ContextWrapper、ContextThemeWrapper 有什么区别?
  6. 面试官:Activity Context、Service Context、Application Context、Base Context 有什么区别?
  7. 面试官:为什么不推荐使用 BaseContext?
  8. 面试官:ContentProvider 里的 Context 是什么时候初始化的呢?
  9. 面试官:BroadcastReceiver 里的 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 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

五、如何正确回复以上面试题

  1. 面试官: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 会使用系统默认的主题。

  1. 面试官:一个APP应用里有几个 Context 呢?

Context 一共有 Application 、Activity 和 Service 三种类型,因此一个应用程序中 Context 数量的计算公式就可以这样写:

Context 数量 = Activity 数量 + Service 数量 + 1

上面的1代表着 Application 的数量,因为一个应用程序中可以有多个Activity和多个 Service,但是只能有一个 Application。

  1. 面试官:Android 开发过程中,Context 有什么用?

Context 就相当于 Application 的大管家,主要负责:

  • 四大组件的交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等。

  • 获取系统/应用资源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等。

  • 文件,包括获取缓存文件夹、删除文件、SharedPreference 相关等。

  • 数据库(SQLite)相关,包括打开数据库、删除数据库、获取数据库路径等。

其它辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生

  1. 面试官: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 初始化也都是这样的。

  1. 面试官: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 不同。

  1. 面试官: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。

  1. 面试官:为什么不推荐使用 BaseContext?
  • 对于 Service 和 Application 而言,不推荐使用 Base Context,是担心用户修改了 Base Context 而导致错误的发生。

  • 对于 Activity 而言,除了担心用户的修改之外,Base Context 和 Activity 本身对于 Reource 以及 Theme 的相关行为是不同的(如果应用了 Configuration 的话),使用 Base Context 可能出现无法预期的现象。

  1. 面试官:ContentProvider 里的 Context 是什么时候初始化的呢?

ContentProvider 本身不是 Context ,但是它有一个成员变量 mContext ,是通过构造函数传入的。那么这个问题就变成了,ContentProvider 什么时候创建。应用创建 Application 是通过调用 ActivityThread.handleBindApplication 方法,这个方法的相关流程有:

  • 创建 Application

  • 初始化 Application 的 Context

  • 调用 installContentProviders 并传入刚创建好的 Application 来创建 ContentProvider

  • 调用 Application.onCreate

得出结论,ContentProvider 的 Context 是在 Applicaiton 创建之后,但是 onCreate 方法调用之前初始化的。

  1. 面试官:BroadcastReceiver 里的 Context是哪来的?

广播接收器,分动态注册和静态注册。

  • 动态注册很简单,在调用 Context.registerReceiver 动态注册 BroadcastReceiver 时,会生成一个 ReceiverDispatcher 会持有这个 Context ,这样当有广播分发到它时,调用 onReceiver 方法就可以把 Context 传递过去了。当然,这也是为什么不用的时候要 unregisterReceiver 取消注册,不然这个 Context 就泄漏了哦。
  • 静态注册时,在分发的时候最终调用的是 ActivityThread.handleReceiver ,这个方法直接通过 ClassLoader 去创建一个 BroadcastReceiver 的对象,而传递给 onReceiver 方法的 Context 则是通过 context.getReceiverRestrictedContext() 生成的一个以 Application 为 mBase 的 ContextWrapper。注意这边的 Context 不是 Application 。

本文使用 mdnice 排版