注册

从面试官角度分析:介绍一下Android中的Context?

  1. Context是什么
  2. Context的结构
  3. Context的注意事项

问题正解:

一、Context是什么

Context 是 Android 中用的十分常见的一种概念,常被翻译成上下文,这个概念在其他的技术中也有运用。Android 官方对它的解释,可以理解为应用程序环境中全局信息的接口,它整合了相当多系统级的服务,可以用来得到应用中的类、资源,以及可以进行应用程序级的调起操作,比如启动 Activity、Service等等,而且 Context 这个类是 抽象abstract 的,不含有具体的函数实现。

二、Context结构

Context 是维持 Android 程序中各组件能够正常工作的一个核心功能类。

Context 本身是一个抽象类,其主要实现类为 ContextImpl,另有直系子类两个:

  • ContextWrapper
  • ContextThemeWrapper

这两个子类是 Context 的代理类,它们继承关系如下:

image.png

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 的数量,因为一个App中可以有多个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 。


作者:派大星不吃蟹
链接:https://juejin.cn/post/7254812112563585080
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册