一、Context 类继承关系
上图描述了 Context 类的主要继承关系,Context 是抽象类,提供了应用环境全局信息的接口;Context 有两个直接子类 ContextWrapper 和 ContextImpl,顾名思义,ContextWrapper 是上下文功能的封装类,ContextImpl 是上下文功能的实现类;Applicaiton 和 Service 直接继承自 ContextWrapper 类,Activity 继承自 ContextThemeWrapper 类,因为 Activity 提供 UI 显示,需要有主题。
- Context 类提供了一组通用的 API;
- ContextImpl 实现了 Context 所有的功能,为 Activity 等应用组件提供 Context 对象;
- ContextWrapper 包含一个真正的 ContextImpl 的引用 mBase,然后就是 ContextImpl 的装饰者模式;
- ContextThemeWrapper 内部包含了 Theme 相关的接口,即 android:theme 指定的属性;
二、Context 的数量及作用域
Context 数量 = Activity 数量 + Service 数量 + 1(Application 数量)
由于 Context 的具体实例是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是可以通用的。
不过有几种场景比较特殊:
- Dialog 则必须在一个 Activity 上面弹出(除非是 System Alert类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。
- 非 Activity 类型的 Context 并没有所谓的任务栈,所以待启动的 Activity 就找不到栈了。解决这个问题的方法就是为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就为它创建一个新的任务栈,而此时 Activity 是以 singleTask 模式启动的。所有这种用 Application 启动 Activity 的方式不推荐使用,Service 同 Application。
- 在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。
三、获取 Context 对象
- View 的 getContext 方法,返回 View 的 Context,通常是当前正在展示的 Activity 的对象,通常在自定义 View 时会用到;
- Activity.this 返回当前 Activity 实例,如果是 UI 控件需要使用 Activity 作为 Context 对象,但是默认的 Toast 实际上使用 ApplicationContext 也可以;
- Activity 和 Service 的 getApplication 方法,返回 Application 的实例;
- getApplicationContext 方法,获取当前应用的 Context 对象,也就是 Application 的实例,与上一个方法不同的是依附的对象不同;
四、Context 引起的内存泄漏
一般 Context 造成的内存泄漏都是 Activity 被销毁后还被其它对象强引用也就是长生命周期对象持有短生命周期对象的引用而造成 Activity 不能被 GC 回收而导致的。
造成内存泄漏的原因主要有两个:
- 静态的 Activity Context 或任何包含 Activity Context 的对象(如 View)没有在该 Activity 销毁的时候置空引用;
- 非静态内部类或匿名内部类持有外部类的引用,在 Activity 销毁后依然执行任务;
常见的场景如下:
static 直接或间接修饰 Activity Context
解决方法:
- 在任何时候都不建议用 static 修饰 Activity,因为 static 变量和应用的存活时间是一样的,这样 Activity 生命周期结束时,引用仍然被持有;
- 在适当的地方比如 onDestroy 方法中置空 Activity Context 的引用;
- 使用软引用解决,确保在 Activity 销毁时,垃圾回收机制可以将其回收。
单例引用 Activity Context
解决方法:
- 使用 Applicaion Context 代替 Activity Context ;
- 在调用的地方使用弱引用
非静态或匿名内部类执行耗时任务
非静态内部类或匿名内部类都会拥有所在外部类的引用
解决方法:
用 static 修饰内部类或者匿名内部类所在的方法;
静态内部类持有外部类静态成员变量
虽然静态内部类的生命周期和外部类无关,但是如果在内部类中想要引入外部成员变量的话,这个成员变量必须是静态的了,这也可能导致内存泄露。
解决方法:
使用弱引用
Handler 引起的内存泄漏
这种情况和非静态或匿名内部类执行耗时任务的原因一样。
总结一下:
- 当 Application 的 Context 能搞定的情况下,并且生命周期长的对象,优先使用 Application 的 Context。
2:不要让生命周期长于 Activity 的对象持有到 Activity 的引用。引用了要及时置空。
3:尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
五、各种 Context 在 ActivityThread 中实例化过程源码分析
Context 的实现是 ContextImpl,Activity、Application 和 Service 的创建都是在 ActivityThread 中完成的。
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 的核心代码,在 performLaunchActivity 方法里面创建了 Activity 的实例,然后然后调用 createBaseContextForActivity 方法创建一个 Context 对象,这个对象是 ContextImpl,将前面创建的 Context 对象传入到 Activity 的 attach 方法中去。
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;
}
再看 createBaseContextForActivity 方法,实质就是通过 ContextImpl 的有参构造初始化一些参数创建一个 ContextImpl 对象,创建一个 Context 对象将创建的 ContextImpl 赋值给 Context 变量并返回。
ContextImpl 有一个成员变量 mOuterContext,通过 ContextImpl 的 setOuterContext 方法将传进来的 Activity 对象赋值给 mOuterContext,这样 ContextImpl 就持有了 Activity 的引用。
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);
......
}
attach 里面关键的代码是调用 ContextThemeWrapper 类的 attachBaseContext 方法将 createBaseContextForActivity 创建的 ContextImpl 实例赋值给 ContextWrapper 的 mBase 变量,这样 ContextWrapper 的成员 mBase 就被实例化为 Context 的实现类 ContextImpl
Service中ContextImpl实例化源码分析
startService 或者 bindService 方法创建一个新 Service 时就会回调 ActivityThread 类的 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) {
......
}
}
与实例化 Activity 的过程相似,创建一个 ContextImpl 对象赋值给 mBase 变量,同时 ContextImpl 的 mOuterContext 变量也持有 Service 的引用,这里创建 ContextImpl 对象是用的 ContextImpl.createAppContext 方法
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);
......
}
Application中ContextImpl实例化源码分析
创建 Application 的过程也在 ActivityThread 类的 handleBindApplication() 方法完成相关数据操作,而 ContextImpl 的创建是在该方法中调运 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;
}