本文 大部分内容摘抄自:https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/context.html
源码版本为 Android API 25
1、概述
Context 这一名词中文意思为 “上下文,背景”,在 Android 中,其作为一个包含运行时环境信息的上下文接口,它允许访问特定的应用程序的资源和类,以及整合了系统级的操作服务。
截图自源码
2、子类层级
Android 中的 Context 类是一个抽象类,定义一些抽象方法,以及常量。
其子类 ContextWrapper ,是一个包装类,其内部本身实现的抽象方法,没有实际的逻辑,而是通过内部持有 Context mBase
类间接调用 mBase 的对应方法来实现的(如下面源码中的 startActivity()
方法)。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
...
}
在 Android 中,具体有三类 Context:Activity、Service、Application。
ContextThemeWrapper 类,如其名所言,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在 AndroidManifest.xml 中通过 android:theme 为 Application 元素或者 Activity 元素指定的主题。
当然,只有 Activity 才需要主题,Service 是不需要主题的,因为 Service 是没有界面的后台场景,所以Service 直接继承于 ContextWrapper,Application同理。
而 ContextImpl 类则真正实现了 Context 中的所有,应用程序中所调用的各种Context类的方法,其实现均来自于该类。
一句话总结:Context 的两个子类分工明确,其中 ContextImpl 是 Context 的具体实现类,ContextWrapper 是 Context 的包装类。Activity,Application,Service 虽都继承自 ContextWrapper(Activity 继承自ContextWrapper 的子类 ContextThemeWrapper ),但它们初始化的过程中都会创建 ContextImpl 对象,由 ContextImpl 实现 Context 中的方法。
比如,Application,其在实例化的时候,就会通过 Application#attach()
来绑定一个对应的 ContextImpl 实例。
// LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
Application app = null;
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
// 创建 ContextImpl 实例
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 创建 Application 实例
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
...
}
...
return app;
}
通过 mActivityThread.mInstrumentation.newApplication()
方法,最终会来到 Instrumentation.newApplication()
方法:
// Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
// Application.java
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
可以看到,除了会新建一个 Application 实例,还会通过其 attach()
方法将之前新建且传递过来的 ContextImpl 实例给绑定。
2、不同 Context 的作用域
由于 Context 的具体实例是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是可以通用的。不过有几种场景比较特殊,比如启动 Activity,还有弹出 Dialog。出于安全原因的考虑,Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须要建立在另一个 Activity 的基础之上,也就是以此形成的返回栈。而 Dialog 则必须在一个 Activity 上面弹出(除非是 System Alert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。
Application 和 Service 所不推荐的两种使用情况:
-
如果我们用 Application Context 去启动一个 LaunchMode 为 standard 的 Activity 的时候会报错
android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
这是因为非 Activity 类型的 Context 并没有所谓的任务栈,所以待启动的 Activity 就找不到栈了。解决这个问题的方法就是为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就为它创建一个新的任务栈,而此时 Activity 是以 singleTask 模式启动的。所有这种用 Application 启动 Activity 的方式不推荐使用,Service 同 Application。
但是这里我在 Android API 25 的源码版本下,使用 Application、Service 类型的 Context,都能够正常启动一个 standard 类型的 Activity,且没有设置
FLAG_ACTIVITY_NEW_TASK
,但是并没有报错,估计是高级版本的源码进行了修改,对这种情况进行相应的处理。 -
在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。
一句话总结:凡是跟UI相关的,都应该使用 Activity 做为 Context 来处理;其他的一些操作,Service, Activity, Application 等实例都可以,当然了,注意 Context 引用的持有,防止内存泄漏。
另外,在 Service 中启动一个 Dialog 是可以,但是是系统级的,这种dialog不响应home键和返回键,即强制用户必须对dialog作出操作。且涉及到 android.permission.SYSTEM_ALERT_WINOW
权限(即显示在其他应用之上),当 API Level >= 23 时需要动态申请。Application Context 同理。具体实现自己搜索。
3、补充
(1)一个应用程序有几个 Context ?
Context数量 = Activity 数量+ Service 数量 + 1
只有 Activity,Service 持有 Context,而 Broadcast Receiver,Content Provider 并不是 Context 的子类,他们所持有的 Context 都是其他地方传过去的,所以并不计入 Context 总数。
(2)Context 能干什么?
这个就实在是太多了,弹出 Toast、启动 Activity、启动 Service、发送广播、操作数据库等等都需要用到Context。
(3)Application Context 的生命周期伴随着应用,而 Activity Context、Service Context 则与对应的组件的生命周期有关。