1 背景
今天突然想起之前在上家公司(做TV与BOX盒子)时有好几个人问过我关于Android的Context到底是啥的问题,所以就马上要诞生这篇文章。我们平时在开发App应用程序时一直都在使用Context(别说你没用过,访问当前应用的资源、启动一个activity等都用到了Context),但是很少有人关注过这玩意到底是啥,也很少有人知道getApplication与getApplicationContext方法有啥区别,以及一个App到底有多少个Context等等的细节。
更为致命的是Context使用不当还会造成内存泄漏。所以说完全有必要拿出来单独分析分析(基于Android 5.1.1 (API 22)源码分析)。
【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】
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都是怎么来的。
【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】
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的使用将不再迷惑。
【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】