Context在开发Android应用的过程中扮演着非常重要的角色,比如启动一个 Activity需要使用context.startActivity方法,将一个xml文件转换为一个View对象也需要使用Context对象,可以 这么说,离开了这个类,Android开发寸步难行,对于这样一个类,我们又对他了解多少呢。我就说说我的感受吧,在刚开始学习Android开发时,感 觉使用Context的地方一直就是传入一个Activity对象,久而久之感觉只要是Context的地方就传入一个Activity就行了,那么我们 现在就来详细的分析一下Context和Activity的关系吧!
在开始本文之前我们先放置一个问题在这里:
我们平时在获取项目资源时使用context.getResources()的时候为什么放回的是同一个值,明明是使用不同的Activity调用getResources返回结果却是一样的。
Context本身是一个纯的abstract类,ContextWrapper是对Context的一个包装而已,它的内部包含了一个 Context对象,其实对ContextWrapper的方法调用最终都是调用其中的Context对象完成的,至于 ContextThremeWrapper,很明显和Theme有关,所以Activity从ContextThemmWrapper继承,而 Service从ContextWrapper继承,ContextImpl是唯一一个真正实现了Context中方法的类。
从上面的继承关系来看,每一个Activity就是一个Context,每一个Service就是一个Context,这也就是为什么使用Context的地方可以被Activity或者Service替换了。
根据前面所说,由于实现了Context的只有ContextImpl类,Activity和Service本没有真正的实现,他们只是内部包含了 一个真实的Context对象而已,也就是在在创建Activity或者Service的时候肯定要创建爱你一个ContextImpl对象,并赋值到 Activity中的Context类型变量中。那我们就来看看Andorid源码中有哪些地方创建了ContextImpl.
据统计Android中创建ContextImpl的地方一共有7处:
在PackageInfo.makeApplication()中
在performLaunchActivity()中
在handleCreateBackupAgent()中
在handleCreateService()中
2次在hanldBinderAppplication()中
在attach()方法中
由于创建ContextImpl的基本原理类似,所以这里只会分析几个比较有代表性的地方:
1、 Application对应的Context
在应用程序启动时,都会创建一个Application对象,所以辗转调用到handleBindApplication()方法。
- private final void handleBindApplication(AppBindData data) {
- mBoundApplication = data;
- mConfiguration = new Configuration(data.config);
- ....
- data.info = getPackageInfoNoCheck(data.appInfo);
- ...
- Application app = data.info.makeApplication(data.restrictedBackupMode, null);
- mInitialApplication = app;
- ....
- }
其中data.info是LoadedApk类型的,到getPackageInfoNoCheck中看看源码
- public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
- return getPackageInfo(ai, null, false, true);
- }
里面其实调用的是getPackageInfo,继续跟进:
- if (includeCode) {
- ref = mPackages.get(aInfo.packageName);
- } else {
- ref = mResourcePackages.get(aInfo.packageName);
- }
- LoadedApk packageInfo = ref != null ? ref.get() : null;
- if (packageInfo == null || (packageInfo.mResources != null
- && !packageInfo.mResources.getAssets().isUpToDate())) {
- if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
- : "Loading resource-only package ") + aInfo.packageName
- + " (in " + (mBoundApplication != null
- ? mBoundApplication.processName : null)
- + ")");
- packageInfo =
- new LoadedApk(this, aInfo, this, baseLoader,
- securityViolation, includeCode &&
- (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
- if (includeCode) {
- mPackages.put(aInfo.packageName,
- new WeakReference<LoadedApk>(packageInfo));
- } else {
- mResourcePackages.put(aInfo.packageName,
- new WeakReference<LoadedApk>(packageInfo));
- }
由于includeCode传入的是true,所以首先从mPackages中获取,如果没有,则new一个出来,并放入mPackages里面去,注意,这里的mPackages是ActivityThread中的属性。
下面继续分析一下LoadedApk这个类中的makeApplication函数
- try {
- java.lang.ClassLoader cl = getClassLoader();
- //创建一个ContextImpl对象
- ContextImpl appContext = new ContextImpl();
- appContext.init(this, null, mActivityThread);
- app = mActivityThread.mInstrumentation.newApplication(
- cl, appClass, appContext);
- appContext.setOuterContext(app);
- } catch (Exception e) {
- if (!mActivityThread.mInstrumentation.onException(app, e)) {
- throw new RuntimeException(
- "Unable to instantiate application " + appClass
- + ": " + e.toString(), e);
- }
- }
- 这里创建了一个ContextImpl对象,并调用了它的init方法,现在进入init方法。
- mPackageInfo = packageInfo;
- mResources = mPackageInfo.getResources(mainThread);
对mPackageInof和mResources两个变量初始化
回到makeApplication中,创建了一个Application对象,并将appContext传进去,其实就是将appContext 传递给ContextWrapper中的Context类型变量(Application也是继承ContextWrapper)
2、Activity中的Context
在创建一个Activity时,经过辗转调用,会执行handleLaunchActivity(),然后调用performLaunchActivity(),该方法创建ContextImpl代码如下:
- r.packageInfo= getPackageInfo(aInfo.applicationInfo,
- Context.CONTEXT_INCLUDE_CODE);
- ContextImplappContext = new ContextImpl();
- appContext.init(r.packageInfo,r.token, this);
- appContext.setOuterContext(activity);
- activity.attach(appContext,this, getInstrumentation(), r.token,
- r.ident, app, r.intent,r.activityInfo, title, r.parent,
- r.embeddedID,r.lastNonConfigurationInstance,
- r.lastNonConfigurationChildInstances, config);
由于getPackageInfo函数之前已经分析过了,稍微有点区别,但是大致流程是差不多的,所以此处的appContext执行init之 后,其中的mPackages变量和mResources变量时一样的,activity通过attach函数将该appContext赋值到 ContextWrapper中的Context类型变量
3、Service中的Context
同样 在创建一个Service时,经过辗转调用会调用到scheduleCreateService方法,之后会巧用handleCreateService
- LoadedApkpackageInfo = getPackageInfoNoCheck(
- data.info.applicationInfo);
- ContextImplcontext = new ContextImpl();
- context.init(packageInfo, null,this);
- Application app =packageInfo.makeApplication(false, mInstrumentation);
- context.setOuterContext(service);
- service.attach(context, this,data.info.name, data.token, app,
- ActivityManagerNative.getDefault());
其思路和上面两个基本一样,在此就不再详述。
在此总结一下:
(1)Context是一个抽象类,ContextWrapper是对Context的封装,它包含一个Context类型的变 量,ContextWrapper的功能函数内部其实都是调用里面的Context类型变量完成的。 Application,Service,Activity等都是直接或者间接继承自ContextWrapper,但是并没有真正的实现其中的功 能,Application,Service,Activity中关于Context的功能都是通过其内部的Context类型变量完成的,而这个变量的 真实对象必定是ContextImpl,所以没创建一个Application,Activity,Servcice便会创建一个 ContextImpl,并且这些ContextImpl中的mPackages和mResources变量都是一样的,所以不管使用Acitivty还 是Service调用getResources得到相同的结果
(2)在一个apk中,Context的数量等于Activity个数+Service个数+1.
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/lmj623565791/article/details/40481055
1、Context概念
Context,相信不管是第一天开发Android,还是开发Android的各种老鸟,对于Context的使用一定不陌生~~你在加载资源、启动一个新的Activity、获取系统服务、获取内部文件(夹)路径、创建View操作时等都需要Context的参与,可见Context的常见性。大家可能会问到底什么是Context,Context字面意思上下文,或者叫做场景,也就是用户与操作系统操作的一个过程,比如你打电话,场景包括电话程序对应的界面,以及隐藏在背后的数据;
但是在程序的角度Context又是什么呢?在程序的角度,我们可以有比较权威的答案,Context是个抽象类,我们可以直接通过看其类结构来说明答案:
可以看到Activity、Service、Application都是Context的子类;
也就是说,Android系统的角度来理解:Context是一个场景,代表与操作系统的交互的一种过程。
从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。
在仔细看一下上图:Activity、Service、Application都是继承自ContextWrapper,而ContextWrapper内部会包含一个base context,由这个base context去实现了绝大多数的方法。
2、Context与ApplicationContext
看了标题,千万不要被误解,ApplicationContext并没有这个类,其实更应该叫做:Activity与Application在作为Context时的区别。嗯,的确是这样的,大家在需要Context的时候,如果是在Activity中,大多直接传个this,当在匿名内部类的时候,因为this不能用,需要写XXXActivity.this,很多哥们会偷懒,直接就来个getApplicationContext。那么大家有没有想过,XXXActivity.this和getApplicationContext的区别呢?
XXXActivity和getApplicationContext返回的肯定不是一个对象,一个是当前Activity的实例,一个是项目的Application的实例。既然区别这么明显,那么各自的使用场景肯定不同,乱使用可能会带来一些问题。
下面开始介绍在使用Context时,需要注意的问题。
3、引用的保持
大家在编写一些类时,例如工具类,可能会编写成单例的方式,这些工具类大多需要去访问资源,也就说需要Context的参与。
在这样的情况下,就需要注意Context的引用问题。
例如以下的写法:
1 package com.mooc.shader.roundimageview; 2 3 import android.content.Context; 4 5 public class CustomManager 6 { 7 private static CustomManager sInstance; 8 private Context mContext; 9 10 private CustomManager(Context context) 11 { 12 this.mContext = context; 13 } 14 15 public static synchronized CustomManager getInstance(Context context) 16 { 17 if (sInstance == null) 18 { 19 sInstance = new CustomManager(context); 20 } 21 return sInstance; 22 } 23 24 //some methods 25 private void someOtherMethodNeedContext() 26 { 27 28 } 29 }
对于上述的单例,大家应该都不陌生(请别计较getInstance的效率问题),内部保持了一个Context的引用;
这么写是没有问题的,问题在于,这个Context哪来的我们不能确定,很大的可能性,你在某个Activity里面为了方便,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏。
那么,我们如何才能避免这样的问题呢?
有人会说,我们可以软引用,嗯,软引用,假如被回收了,你不怕NullPointException么。
把上述代码做下修改:
1 public static synchronized CustomManager getInstance(Context context) 2 { 3 if (sInstance == null) 4 { 5 sInstance = new CustomManager(context.getApplicationContext()); 6 } 7 return sInstance; 8 }
这样,我们就解决了内存泄漏的问题,因为我们引用的是一个ApplicationContext,它的生命周期和我们的单例对象一致。
这样的话,可能有人会说,早说嘛,那我们以后都这么用不就行了,很遗憾的说,不行。上面我们已经说过,Context和Application Context的区别是很大的,也就是说,他们的应用场景(你也可以认为是能力)是不同的,并非所有Activity为Context的场景,Application Context都能搞定。
下面就开始介绍各种Context的应用场景。
4、Context的应用场景
大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。
好了,这里我们看下表格,重点看Activity和Application,可以看到,和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
5、总结
好了,到此,Context的分析基本完成了,希望大家在以后的使用过程中,能够稍微考虑下,这里使用Activity合适吗?会不会造成内存泄漏?这里传入Application work吗?