- Android 触摸屏事件派发机制详解与源码分析一 View 篇
- Android 触摸屏事件派发机制详解与源码分析二 ViewGroup 篇
- Android 触摸屏事件派发机制详解与源码分析三 Activity 篇
- Android 应用 setContentView 与 LayoutInflater 加载解析机制源码分析
- Android 应用 Context 详解及源码解析
- Android 异步消息处理机制详解及源码分析
- Android 应用 Activity、Dialog、PopWindow、Toast 窗口添加机制及源码分析
- Android ListView 工作原理完全解析,带你从源码的角度彻底理解
- Activity 启动过程全解析
- Android 应用 AsyncTask 处理机制详解及源码分析
- 说说 PendingIntent 的内部机制
- Android Activity.startActivity 流程简介
- Activity 界面显示全解析
- 框架层理解 Activity 生命周期(APP 启动过程)
- APK 安装过程及原理详解
- Android 构建过程简述
- Android 应用层 View 绘制流程与源码分析
Android 应用 Context 详解及源码解析
1 背景
今天突然想起之前在上家公司(做 TV 与 BOX 盒子)时有好几个人问过我关于 Android 的 Context 到底是啥的问题,所以就马上要诞生这篇文章。我们平时在开发 App 应用程序时一直都在使用 Context(别说你没用过,访问当前应用的资源、启动一个 activity 等都用到了 Context),但是很少有人关注过这玩意到底是啥,也很少有人知道 getApplication 与 getApplicationContext 方法有啥区别,以及一个 App 到底有多少个 Context 等等的细节。
更为致命的是 Context 使用不当还会造成内存泄漏。所以说完全有必要拿出来单独分析分析(基于 Android 5.1.1 (API 22)源码分析)。
2 Context 基本信息
2-1 Context 概念
先看下源码 Context 类基本情况,如下:
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
......
}
从源码注释可以看见,Context 提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被 Android 系统所提供。它允许获取以应用为特征的
资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。
看见上面的 Class OverView 了吗?翻译就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android 提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动 Activity,发广播,接受 Intent 等)。
既然上面 Context 是一个抽象类,那么肯定有他的实现类咯,我们在 Context 的源码中通过 IDE 可以查看到他的子类如下:
吓尿了,737 个子类,经过粗略浏览这些子类名字和查阅资料发现,这些子类无非就下面一些主要的继承关系。这 737 个类都是如下关系图的直接或者间接子类而已。如下是主要的继承关系:
从这里可以发现,Service 和 Application 的类继承类似,Activity 继承 ContextThemeWrapper。这是因为 Activity 有主题(Activity 提供 UI 显示,所以需要主题),而 Service 是没有界面的服务。
所以说,我们从这张主要关系图入手来分析 Context 相关源码。
2-2 Context 之间关系源码概述
有了上述通过 IDE 查看的大致关系和图谱之后我们在源码中来仔细看下这些继承关系。
先来看下 Context 类源码注释:
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
......
}
看见没有,抽象类 Context ,提供了一组通用的 API。
再来看看 Context 的实现类 ContextImpl 源码注释:
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
private Context mOuterContext;
......
}
该类实现了 Context 类的所有功能。
再来看看 Context 的包装类 ContextWrapper 源码注释:
/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context. Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
......
}
该类的构造函数包含了一个真正的 Context 引用(ContextImpl 对象),然后就变成了 ContextImpl 的装饰着模式。
再来看看 ContextWrapper 的子类 ContextThemeWrapper 源码注释:
/**
* A ContextWrapper that allows you to modify the theme from what is in the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
......
}
该类内部包含了主题 Theme 相关的接口,即 android:theme 属性指定的。
再来看看 Activity、Service、Application 类的继承关系源码:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
......
}
public class Application extends ContextWrapper implements ComponentCallbacks2 {
......
}
看见没有?他们完全符合上面我们绘制的结构图与概述。
2-3 解决应用 Context 个数疑惑
有了上面的 Context 继承关系验证与分析之后我们来看下一个应用程序到底有多个 Context?
Android 应用程序只有四大组件,而其中两大组件都继承自 Context,另外每个应用程序还有一个全局的 Application 对象。所以在我们了解了上面继承关系之后我们就可以计算出来 Context 总数,如下:
APP Context 总数 = Application 数(1) + Activity 数(Customer) + Service 数(Customer);
到此,我们也明确了 Context 是啥,继承关系是啥样,应用中 Context 个数是多少的问题。接下来就有必要继续深入分析这些 Context 都是怎么来的。
3 各种 Context 在 ActivityThread 中实例化过程源码分析
在开始分析之前还是和 《Android 异步消息处理机制详解及源码分析》 的 3-1-2 小节及 《Android 应用 setContentView 与 LayoutInflater 加载解析机制源码分析》 的 2-6 小节一样直接先给出关于 Activity 启动的一些概念,后面会写文章分析这一过程。
Context 的实现是 ContextImpl,Activity 与 Application 和 Service 的创建都是在 ActivityThread 中完成的,至于在 ActivityThread 何时、怎样调运的关系后面会写文章分析,这里先直接给出结论,因为我们分析的重点是 Context 过程。
3-1 Activity 中 ContextImpl 实例化源码分析
通过 startActivity 启动一个新的 Activity 时系统会回调 ActivityThread 的 handleLaunchActivity() 方法,该方法内部会调用 performLaunchActivity() 方法去创建一个 Activity 实例,然后回调 Activity 的 onCreate() 等方法。所以 Activity 的 ContextImpl 实例化是在 ActivityThread 类的 performLaunchActivity 方法中,如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
//已经创建好新的 activity 实例
if (activity != null) {
//创建一个 Context 对象
Context appContext = createBaseContextForActivity(r, activity);
......
//将上面创建的 appContext 传入到 activity 的 attach 方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
......
}
......
return activity;
}
看见上面 performLaunchActivity 的核心代码了吗?通过 createBaseContextForActivity(r, activity);
创建 appContext,然后通过 activity.attach 设置值。
具体我们先看下 createBaseContextForActivity 方法源码,如下:
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
//实质就是 new 一个 ContextImpl 对象,调运 ContextImpl 的有参构造初始化一些参数
ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
//特别特别留意这里!!!
//ContextImpl 中有一个 Context 的成员叫 mOuterContext,通过这条语句就可将当前新 Activity 对象赋值到创建的 ContextImpl 的成员 mOuterContext(也就是让 ContextImpl 内部持有 Activity)。
appContext.setOuterContext(activity);
//创建返回值并且赋值
Context baseContext = appContext;
......
//返回 ContextImpl 对象
return baseContext;
}
再来看看 activity.attach,也就是 Activity 中的 attach 方法,如下:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
//特别特别留意这里!!!
//与上面 createBaseContextForActivity 方法中 setOuterContext 语句类似,不同的在于:
//通过 ContextThemeWrapper 类的 attachBaseContext 方法,将 createBaseContextForActivity 中实例化的 ContextImpl 对象传入到 ContextWrapper 类的 mBase 变量,这样 ContextWrapper(Context 子类)类的成员 mBase 就被实例化为 Context 的实现类 ContextImpl
attachBaseContext(context);
......
}
通过上面 Activity 的 Context 实例化分析再结合上面 Context 继承关系可以看出:
Activity 通过 ContextWrapper 的成员 mBase 来引用了一个 ContextImpl 对象,这样,Activity 组件以后就可以通过这个 ContextImpl 对象来执行一些具体的操作(启动 Service 等);同时 ContextImpl 类又通过自己的成员 mOuterContext 引用了与它关联的 Activity,这样 ContextImpl 类也可以操作 Activity。
SO,由此说明一个 Activity 就有一个 Context,而且生命周期和 Activity 类相同(记住这句话,写应用就可以避免一些低级的内存泄漏问题)。
3-2 Service 中 ContextImpl 实例化源码分析
写 APP 时我们通过 startService 或者 bindService 方法创建一个新 Service 时就会回调 ActivityThread 类的 handleCreateService() 方法完成相关数据操作(具体关于 ActivityThread 调运 handleCreateService 时机等细节分析与上面 Activity 雷同,后边文章会做分析)。具体 handleCreateService 方法代码如下:
private void handleCreateService(CreateServiceData data) {
......
//类似上面 Activity 的创建,这里创建 service 对象实例
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
......
}
try {
......
//不做过多解释,创建一个 Context 对象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
//特别特别留意这里!!!
//ContextImpl 中有一个 Context 的成员叫 mOuterContext,通过这条语句就可将当前新 Service 对象赋值到创建的 ContextImpl 的成员 mOuterContext(也就是让 ContextImpl 内部持有 Service)。
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
//将上面创建的 context 传入到 service 的 attach 方法
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
......
} catch (Exception e) {
......
}
}
再来看看 service.attach,也就是 Service 中的 attach 方法,如下:
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
//特别特别留意这里!!!
//与上面 handleCreateService 方法中 setOuterContext 语句类似,不同的在于:
//通过 ContextWrapper 类的 attachBaseContext 方法,将 handleCreateService 中实例化的 ContextImpl 对象传入到 ContextWrapper 类的 mBase 变量,这样 ContextWrapper(Context 子类)类的成员 mBase 就被实例化为 Context 的实现类 ContextImpl
attachBaseContext(context);
......
}
可以看出步骤流程和 Activity 的类似,只是实现细节略有不同而已。
SO,由此说明一个 Service 就有一个 Context,而且生命周期和 Service 类相同(记住这句话,写应用就可以避免一些低级的内存泄漏问题)。
3-3 Application 中 ContextImpl 实例化源码分析
当我们写好一个 APP 以后每次重新启动时都会首先创建 Application 对象(每个 APP 都有一个唯一的全局 Application 对象,与整个 APP 的生命周期相同)。创建 Application 的过程也在 ActivityThread 类的 handleBindApplication() 方法完成相关数据操作(具体关于 ActivityThread 调运 handleBindApplication 时机等细节分析与上面 Activity 雷同,后边文章会做分析)。而 ContextImpl 的创建是在该方法中调运 LoadedApk 类的 makeApplication 方法中实现,LoadedApk 类的 makeApplication() 方法中源代码如下:
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
//只有新创建的 APP 才会走 if 代码块之后的剩余逻辑
if (mApplication != null) {
return mApplication;
}
//即将创建的 Application 对象
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
//不做过多解释,创建一个 Context 对象
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//将 Context 传入 Instrumentation 类的 newApplication 方法
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
//特别特别留意这里!!!
//ContextImpl 中有一个 Context 的成员叫 mOuterContext,通过这条语句就可将当前新 Application 对象赋值到创建的 ContextImpl 的成员 mOuterContext(也就是让 ContextImpl 内部持有 Application)。
appContext.setOuterContext(app);
} catch (Exception e) {
......
}
......
return app;
}
接着看看 Instrumentation.newApplication 方法。如下源码:
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
继续看重载两个参数的 newApplication 方法,如下:
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
......
//继续传递 context
app.attach(context);
return app;
}
继续看下 Application 类的 attach 方法,如下:
final void attach(Context context) {
//特别特别留意这里!!!
//与上面 makeApplication 方法中 setOuterContext 语句类似,不同的在于:
//通过 ContextWrapper 类的 attachBaseContext 方法,将 makeApplication 中实例化的 ContextImpl 对象传入到 ContextWrapper 类的 mBase 变量,这样 ContextWrapper(Context 子类)类的成员 mBase 就被实例化为 Application 的实现类 ContextImpl
attachBaseContext(context);
......
}
可以看出步骤流程和 Activity 的类似,只是实现细节略有不同而已。
SO,由此说明一个 Application 就有一个 Context,而且生命周期和 Application 类相同(然而一个 App 只有一个 Application,而且与应用生命周期相同)。
4 应用程序 APP 各种 Context 访问资源的唯一性分析
你可能会有疑问,这么多 Context 都是不同实例,那么我们平时写 App 时通过 context.getResources 得到资源是不是就不是同一份呢?下面我们从源码来分析下,如下:
class ContextImpl extends Context {
......
private final ResourcesManager mResourcesManager;
private final Resources mResources;
......
@Override
public Resources getResources() {
return mResources;
}
......
}
看见没,有了上面分析我们可以很确定平时写的 App 中 context.getResources 方法获得的 Resources 对象就是上面 ContextImpl 的成员变量 mResources。
那我们追踪可以发现 mResources 的赋值操作如下:
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration) {
......
//单例模式获取 ResourcesManager 对象
mResourcesManager = ResourcesManager.getInstance();
......
//packageInfo 对于一个 APP 来说只有一个,所以 resources 是同一份
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (activityToken != null
|| displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
//mResourcesManager 是单例,所以 resources 是同一份
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
overrideConfiguration, compatInfo, activityToken);
}
}
//把 resources 赋值给 mResources
mResources = resources;
......
}
由此可以看出在设备其他因素不变的情况下我们通过不同的 Context 实例得到的 Resources 是同一套资源。
PS 一句,同样的分析方法也可以发现 Context 类的 packageInfo 对于一个应用来说也只有一份。感兴趣可以自行分析。
5 应用程序 APP 各种 Context 使用区分源码分析
5-1 先来解决 getApplication 和 getApplicationContext 的区别
很多人一直区分不开这两个方法的区别,这里从源码来分析一下,如下:
首先来看 getApplication 方法,你会发现 Application 与 Context 都没有提供该方法,这个方法是哪提供的呢?我们看下 Activity 与 Service 中的代码,可以发下如下:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
public final Application getApplication() {
return mApplication;
}
......
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
......
public final Application getApplication() {
return mApplication;
}
......
}
Activity 和 Service 提供了 getApplication,而且返回类型都是 Application。这个 mApplication 都是在各自类的 attach 方法参数出入的,也就是说这个
mApplication 都是在 ActivityThread 中各自实例化时获取的 makeApplication 方法返回值。
所以不同的 Activity 和 Service 返回的 Application 均为同一个全局对象。
再来看看 getApplicationContext 方法,如下:
class ContextImpl extends Context {
......
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
......
}
可以看到 getApplicationContext 方法是 Context 的方法,而且返回值是 Context 类型,返回对象和上面通过 Service 或者 Activity 的 getApplication 返回
的是一个对象。所以说对于客户化的第三方应用来说两个方法返回值一样,只是返回值类型不同,还有就是依附的对象不同而已。
5-2 各种获取 Context 方法的差异及开发要点提示
可以看出来,Application 的 Context 生命周期与应用程序完全相同。
Activity 或者 Service 的 Context 与他们各自类生命周期相同。
所以说对于 Context 使用不当会引起内存泄漏。
譬如一个单例模式的自定义数据库管理工具类需要传入一个 Context,而这个数据库管理对象又需要在 Activity 中使用,如果我们传递 Activity 的 Context 就可能造成内存泄漏,所以需要传递 Application 的 Context。
6 Context 分析总结
到此整个 Android 应用的 Context 疑惑就完全解开了,同时也依据源码分析结果给出了平时开发 APP 中该注意的内存泄漏问题提示与解决方案。相信通过这一篇你在开发 APP 时对于 Context 的使用将不再迷惑。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论