【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

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 ContextThemeWrapperimplements 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的实现类ContextImplattachBaseContext(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的实现类ContextImplattachBaseContext(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 {......//继续传递contextapp.attach(context);return app;}

继续看下Application类的attach方法,如下:

final void attach(Context context) {//特别特别留意这里!!!//与上面makeApplication方法中setOuterContext语句类似,不同的在于://通过ContextWrapper类的attachBaseContext方法,将makeApplication中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Application的实现类ContextImplattachBaseContext(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;......@Overridepublic 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赋值给mResourcesmResources = resources;......}

由此可以看出在设备其他因素不变的情况下我们通过不同的Context实例得到的Resources是同一套资源。

PS一句,同样的分析方法也可以发现Context类的packageInfo对于一个应用来说也只有一份。感兴趣可以自行分析。

5 应用程序APP各种Context使用区分源码分析

5-1 先来解决getApplication和getApplicationContext的区别

很多人一直区分不开这两个方法的区别,这里从源码来分析一下,如下:

首先来看getApplication方法,你会发现Application与Context都没有提供该方法,这个方法是哪提供的呢?我们看下Activity与Service中的代码,可以发下如下:

public class Activity extends ContextThemeWrapperimplements 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 {......@Overridepublic 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 转载烦请注明出处,尊重分享成果】

Android应用Context详解及源码解析相关推荐

  1. okhttp的应用详解与源码解析--http的发展史

    乘5G之势,借物联网之风,Android未来亦可期,Android优势在于开放,手机.平板.车载设备.智能家居等都是Android的舞台,Google不倒,Android不灭,本专栏的同步视频教程已经 ...

  2. RN FlatList使用详解及源码解析

    FlatList使用详解及源码解析 前言 长列表或者无限下拉列表是最常见的应用场景之一.RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧.而在最新的 0.43 版本中,提供 ...

  3. FreeRTOS之Tracealyzer for FreeRTOS(FreeRTOS+Trace) 详解(源码解析+移植)

    源:FreeRTOS之Tracealyzer for FreeRTOS(FreeRTOS+Trace) 详解(源码解析+移植)

  4. Diffusion Model原理详解及源码解析

    作者:秃头小苏@CSDN 编辑:3D视觉开发者社区 文章目录 Diffusion Model原理详解及源码解析 写在前面 Diffusion Model原理详解✨✨✨ 整体思路 实施细节 正向过程 逆 ...

  5. cvHoughLines2霍夫直线检测函数详解及源码解析

    转载请注明出处. 文章链接:https://blog.csdn.net/duiwangxiaomi/article/details/126406184 博文目录 一. 前言 二. cvHoughLin ...

  6. 【OS xv6】1 万字详解shell源码解析命令(内含wsl+vscode调试xv6教程 文档第一章助读)

    现在前面的 嘻嘻几百年没写文了确实没时间,等搞完毕设可以一起重温重温.最近学os,读源码发现还挺多东西得整理的,尤其途中有必要找资料整理的时候,内容有点多有点乱,写在源码已经显得不现实了.用的vsco ...

  7. FreeRTOS 之二 Tracealyzer for FreeRTOS(FreeRTOS+Trace) 详解(源码解析+移植)

    2020/5/19 更新了在使用 4.3.8 时遇到的一些问题说明 2018/5/16 大约一个月之前,Tracealyzer for FreeRTOS目前更新到了4.x,新版本不在区分针对哪个系统, ...

  8. 单点登录 cas 设置回调地址_cas客户端流程详解(源码解析)单点登录

    博主之前一直使用了cas客户端进行用户的单点登录操作,决定进行源码分析来看cas的整个流程,以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的,来,贴上示例配置: ...

  9. okhttp的应用详解与源码解析--android网络请求框架发展史

    乘5G之势,借物联网之风,Android未来亦可期,Android优势在于开放,手机.平板.车载设备.智能家居等都是Android的舞台,Google不倒,Android不灭,本专栏的同步视频教程已经 ...

最新文章

  1. MySQL 分库分表及其平滑扩容方案
  2. 四元素的真面目..........简单粗暴
  3. Group by 第二选择 OVER
  4. 软件测试的学习之路-----计算机基础 (详情展示)
  5. 计算机与艺术就业怎样,就业报告:这些艺术类好就业,这些难就业!
  6. 渗透测试实践(工具使用总结)
  7. android viewpager 缩放,android – 使用ViewPager实现捏缩放
  8. WorkerMan源码分析 - 实现最简单的原型
  9. linux下的screen工具配置(针对 string escape)
  10. Android6.0之AMS启动App下篇
  11. Google的Java常用类库 Guava资料
  12. 游戏虚拟引擎自学_自学5个小时,如何做出一个游戏?
  13. 路由技术 -- 路由策略(Routing Policy)与策略路由(PBR,Policy-based Routing)技术
  14. 我在CSDN的2021--一次没有专栏的写在尾声
  15. MS-TCN: Multi-Stage Temporal Convolutional Network for Action Segmentation
  16. 关于2012(世界末日)
  17. 太阳直射点纬度计算公式_高中地理:正午太阳高度公式的应用
  18. html5全景图微信查看,在微信上怎么展示全景图片?
  19. 『類自然數(LZRS)游弋題教室』
  20. ServiceWorker 缓存与 HTTP 缓存

热门文章

  1. vi / vim 编辑器 使用手册
  2. 食品补充成分的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  3. 一套完整的外贸网站SEO优化流程
  4. 树莓派串口通信 USB串口通信 常用串口命令
  5. RS232串口电路设计
  6. openstack是什么?openstack的主要功能组件有哪些
  7. html怎么给图片命名,如何给照片命名的8种方法
  8. 使用第三方安装程序将Windows桌面应用打包
  9. cellspacing和cellpadding的区别
  10. 搭乘飞机办理乘机手续事项