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

标签: android移动开发  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,另外有直系子类两个:ContextWrapperContextThemeWrapper。这两个子类都是 Context 的代理类,它们继承关系如下:

在这里插入图片描述

1、ContextImpl类介绍

ContextImpl 是 Context API 的常见实现,它为 Activity 和其他应用程序组件提供基本上下文对象,说的通俗一点就是 ContextImpl 实现了抽象类的方法,我们在使用 Context 的时候的方法就是它实现的。

2、ContextWrapper类介绍

ContextWrapper 类代理 Context 的实现,将其所有调用简单地委托给另一个 Context 对象(ContextImpl),可以被分类为修饰行为而不更改原始 Context 的类,其实就 Context 类的修饰类。真正的实现类是 ContextImpl,ContextWrapper 里面的方法调用也是调用 ContextImpl 里面的方法。

3、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 会使用系统默认的主题。

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

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

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

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

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

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

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

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

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

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

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

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

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

7.面试官:为什么不推荐使用 BaseContext?

  • 对于 Service 和 Application 而言,不推荐使用 Base Context,是担心用户修改了 Base Context 而导致错误的发生。

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

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

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

  1. 创建 Application

  2. 初始化 Application 的 Context

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

  4. 调用 Application.onCreate

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

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

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

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

在这里插入图片描述

版权声明:本文为jaynm原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jaynm/article/details/106212380

智能推荐

单链表+单链表代码(链表最基础)

链表 链表是有顺序的表,在内存中存储: 链表是以节点的方式存储的 每个节点包括data域,next域:指向下一个节点 如图:发现链表的各个节点不一定是连续存放的,有跳跃的,不是连续存储 链表分为带头节点的链表和没有头结点的链表 添加: 1.先创建一个head头结点,作用就是单链表的头 2.后面每添加一个节点,就直接加入到链表最后 遍历: 代码 添加节点到链表里: 这里借助于temp节点,通过循环找...

Rtthread学习笔记(十三)RT-Thread Studio开启硬件看门狗Watchdog

一、开启硬件看门狗Watchdog 1、配置RT-Thread Settings 2、开启stm32f1xx_hal_conf.h中的宏定义 3.使用RT接口函数初始化硬件看门狗...

TYVJ 4864 天天去哪吃 || 清北学堂金秋杯大奖赛

题目描述: 记录一下i这个值上次出现的位置在哪里,就是pre...

java反编译

jvm 把Boolean类型的值flag当做int类型处理。​​​ Foo.java: 由 class 文件生成 jasm 文件:java -jar asmtools.jar jdis Foo.class > Foo.jasm  修改jasm文件: 执行反编译: java -jar jd-gui-1.6.6.jar File 打开Foo.class文件:b修改为2 重新执行java...

【学习笔记】03-v-html的学习和示例

v-html的认识和使用 示例: 显示结果: 注意:v-html是有复制的...

猜你喜欢

Java实现在线考试系统(系统介绍)

1.和现在有的考试系统有以下几种优势: a.和现在有的系统比较起来,本系统有科目、章节、老师、学生、班级等信息的管理,还有批阅试卷查看已批阅试卷等。传统的考试系统划分并不细,业务功能简单。 b.和学校的考试系统还有外面的考试系统比较起来,本系统是B/S结构,学校的考试系统一般为C/S结构,性能方面不如B/S结构,并且C/S接口需要安装客户端,客户端压力很大,我的系统只需要电脑具有浏览器,在同一局域...

计算机视觉--多视几何初步尝试

基础矩阵的原理 K和K’分别是两个相机的参数矩阵。p和p’是X在平面π的坐标表示。所以可以得出 具体计算过程 代码: #!/usr/bin/env python coding: utf-8 from PIL import Image from numpy import * from pylab import * import numpy as np from imp ...

java初学者怎么学习才可以快速入门

java初学者怎么学习才可以快速入门 一、了解JAVA 我们要知道:Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言。 Java之父:詹姆斯·高斯林 1.1 java的三个体系 Java SE(Java Platform Standard Edition)。Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境...

字段属性之主键&增删改查&自增长&唯一键约束

字段属性之主键&自增长&唯一键约束 主键 主键:primary key 主要的键 一张表中只有一个字段可以使用对应的键,用来唯一的约束该字段里面的数据,不能重复,这种称之为主键 一张表只能最多一个主键 增加主键 SQL操作中有多种方式增加主键大体分为三种 1.在创建表的时候直接在字段之后跟primary key关键字(主键本身不允许为空) 优点:非常直接:缺点:只能使用一个字段作为...

linux下 基于libmad的socket多用户mp3音频在线播放服务器

在众多大神的帮助下,这个在线播放流媒体服务器终于完成啦。。。。 这个mp3流媒体服务器设计的思路是,服务器程序server用多线程实现和多个客户端的通信(这是必然的),然后发送给客户端当前的音频列表公客户端选择,之后根据k客户端的选择给多个客户端传输相应mp3文件的数据,同时,客户端进行实时地音频解码并播放。 关于libmad开源mp3音频解码库的使用,见上一篇博客吧。。。。 在服务器程序这一端,...