前言

Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像难以说清楚。从字面意思,Context的意思是“上下文”,或者也可以叫做环境、场景等,尽管如此,还是有点抽象。从类的继承来说,Context作为一个抽象的基类,它的实现子类有三种:Application、Activity和Service(估计这么说,暂时不管ContextWrapper等类),那么这三种有没有区别呢?为什么通过任意的Context访问资源都得到的是同一套资源呢?getApplication和getApplicationContext有什么区别呢?应用中到底有多少个Context呢?本文将围绕这些问题一一展开,所用源码版本为Android4.4。

什么是Context

Context是一个抽象基类,我们通过它访问当前包的资源(getResources、getAssets)和启动其他组件(Activity、Service、Broadcast)以及得到各种服务(getSystemService),当然,通过Context能得到的不仅仅只有上述这些内容。对Context的理解可以来说:Context提供了一个应用的运行环境,在Context的大环境里,应用才可以访问资源,才能完成和其他组件、服务的交互,Context定义了一套基本的功能接口,我们可以理解为一套规范,而Activity和Service是实现这套规范的子类,这么说也许并不准确,因为这套规范实际是被ContextImpl类统一实现的,Activity和Service只是继承并有选择性地重写了某些规范的实现。

Application、Activity和Service作为Context的区别

首先,它们都间接继承了Context,这是它们的相同点。

不同点,可以从几个方面来说:首先看它们的继承关系

Activity的继承关系

Service和Application的继承关系

通过对比可以清晰地发现,Service和Application的类继承关系比较像,而Activity还多了一层继承ContextThemeWrapper,这是因为Activity有主题的概念,而Service是没有界面的服务,Application更是一个抽象的东西,它也是通过Activity类呈现的。

下面来看一下三者在Context方面的区别

上文已经指出,Context的真正实现都在ContextImpl中,也就是说Context的大部分方法调用都会转到ContextImpl中,而三者的创建均在ActivityThread中完成,我之前写过一篇文章Android源码分析-Activity的启动过程,在文中我指出Activity启动的核心过程是在ActivityThread中完成的,这里要说明的是,Application和Service的创建也是在ActivityThread中完成的。下面我们看下三者在创建时是怎么和ContextImpl相关联的。

Activity对象中ContextImpl的创建

代码为ActivityThread中的performLaunchActivity方法

  1. if (activity != null) {
  2. Context appContext = createBaseContextForActivity(r, activity);
  3. /**
  4. *  createBaseContextForActivity中创建ContextImpl的代码
  5. *  ContextImpl appContext = new ContextImpl();
  6. *  appContext.init(r.packageInfo, r.token, this);
  7. *  appContext.setOuterContext(activity);
  8. */
  9. CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  10. Configuration config = new Configuration(mCompatConfiguration);
  11. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
  12. + r.activityInfo.name + " with config " + config);
  13. activity.attach(appContext, this, getInstrumentation(), r.token,
  14. r.ident, app, r.intent, r.activityInfo, title, r.parent,
  15. r.embeddedID, r.lastNonConfigurationInstances, config);
  16. if (customIntent != null) {
  17. activity.mIntent = customIntent;
  18. }
  19. ...
  20. }

可以看出,Activity在创建的时候会new一个ContextImpl对象并在attach方法中关联它,需要注意的是,创建Activity使用的数据结构是ActivityClientRecord。

Application对象中ContextImpl的创建

代码在ActivityThread中的handleBindApplication方法中,此方法内部调用了makeApplication方法

  1. public Application makeApplication(boolean forceDefaultAppClass,
  2. Instrumentation instrumentation) {
  3. if (mApplication != null) {
  4. return mApplication;
  5. }
  6. Application app = null;
  7. String appClass = mApplicationInfo.className;
  8. if (forceDefaultAppClass || (appClass == null)) {
  9. appClass = "android.app.Application";
  10. }
  11. try {
  12. java.lang.ClassLoader cl = getClassLoader();
  13. ContextImpl appContext = new ContextImpl();
  14. appContext.init(this, null, mActivityThread);
  15. app = mActivityThread.mInstrumentation.newApplication(
  16. cl, appClass, appContext);
  17. appContext.setOuterContext(app);
  18. } catch (Exception e) {
  19. if (!mActivityThread.mInstrumentation.onException(app, e)) {
  20. throw new RuntimeException(
  21. "Unable to instantiate application " + appClass
  22. + ": " + e.toString(), e);
  23. }
  24. }
  25. ...
  26. }

看代码发现和Activity中ContextImpl的创建是相同的。

Service对象中ContextImpl的创建

通过查看代码发现和Activity、Application是一致的。分析到这里,那么三者的Context有什么区别呢?没有区别吗?尽管如此,有一些细节是确定的:Dialog的使用需要Activity,在桌面上我们采用Application的Context无法弹出对话框,同时在桌面上想启动新的activity,我们需要为intent设置FLAG_ACTIVITY_NEW_TASK标志,否则无法启动activity,这一切都说明,起码Application的Context和Activity的Context还是有区别的,当然这也可能不是Context的区别,因为在桌面上,我们的应用没有界面,这意味着我们能干的事情可能受到了限制,事情的细节目前我还没有搞的很清楚。

Context对资源的访问

很明确,不同的Context得到的都是同一份资源。这是很好理解的,请看下面的分析

得到资源的方式为context.getResources,而真正的实现位于ContextImpl中的getResources方法,在ContextImpl中有一个成员 private Resources mResources,它就是getResources方法返回的结果,mResources的赋值代码为:

mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);

下面看一下ResourcesManager的getTopLevelResources方法,这个方法的思想是这样的:在ResourcesManager中,所有的资源对象都被存储在ArrayMap中,首先根据当前的请求参数去查找资源,如果找到了就返回,否则就创建一个资源对象放到ArrayMap中。有一点需要说明的是为什么会有多个资源对象,原因很简单,因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照android系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。

  1. public Resources getTopLevelResources(String resDir, int displayId,
  2. Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
  3. final float scale = compatInfo.applicationScale;
  4. ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
  5. token);
  6. Resources r;
  7. synchronized (this) {
  8. // Resources is app scale dependent.
  9. if (false) {
  10. Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
  11. }
  12. WeakReference<Resources> wr = mActiveResources.get(key);
  13. r = wr != null ? wr.get() : null;
  14. //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
  15. if (r != null && r.getAssets().isUpToDate()) {
  16. if (false) {
  17. Slog.w(TAG, "Returning cached resources " + r + " " + resDir
  18. + ": appScale=" + r.getCompatibilityInfo().applicationScale);
  19. }
  20. return r;
  21. }
  22. }
  23. //if (r != null) {
  24. //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
  25. //            + r + " " + resDir);
  26. //}
  27. AssetManager assets = new AssetManager();
  28. if (assets.addAssetPath(resDir) == 0) {
  29. return null;
  30. }
  31. //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
  32. DisplayMetrics dm = getDisplayMetricsLocked(displayId);
  33. Configuration config;
  34. boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
  35. final boolean hasOverrideConfig = key.hasOverrideConfiguration();
  36. if (!isDefaultDisplay || hasOverrideConfig) {
  37. config = new Configuration(getConfiguration());
  38. if (!isDefaultDisplay) {
  39. applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
  40. }
  41. if (hasOverrideConfig) {
  42. config.updateFrom(key.mOverrideConfiguration);
  43. }
  44. } else {
  45. config = getConfiguration();
  46. }
  47. r = new Resources(assets, dm, config, compatInfo, token);
  48. if (false) {
  49. Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
  50. + r.getConfiguration() + " appScale="
  51. + r.getCompatibilityInfo().applicationScale);
  52. }
  53. synchronized (this) {
  54. WeakReference<Resources> wr = mActiveResources.get(key);
  55. Resources existing = wr != null ? wr.get() : null;
  56. if (existing != null && existing.getAssets().isUpToDate()) {
  57. // Someone else already created the resources while we were
  58. // unlocked; go ahead and use theirs.
  59. r.getAssets().close();
  60. return existing;
  61. }
  62. // XXX need to remove entries when weak references go away
  63. mActiveResources.put(key, new WeakReference<Resources>(r));
  64. return r;
  65. }
  66. }

根据上述代码中资源的请求机制,再加上ResourcesManager采用单例模式,这样就保证了不同的ContextImpl访问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,因为资源可能位于不同的目录,但它一定是我们的应用的资源,或许这样来描述更准确,在设备参数和显示参数不变的情况下,不同的ContextImpl访问到的是同一份资源。设备参数不变是指手机的屏幕和android版本不变,显示参数不变是指手机的分辨率和横竖屏状态。也就是说,尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources其实都指向的是同一块内存(C语言的概念),因此,它们的mResources都是同一个对象(在设备参数和显示参数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的ContextImpl和处在竖屏状态下的ContextImpl访问的资源不是同一个资源对象。

代码:单例模式的ResourcesManager类

  1. public static ResourcesManager getInstance() {
  2. synchronized (ResourcesManager.class) {
  3. if (sResourcesManager == null) {
  4. sResourcesManager = new ResourcesManager();
  5. }
  6. return sResourcesManager;
  7. }
  8. }

getApplication和getApplicationContext的区别

getApplication返回结果为Application,且不同的Activity和Service返回的Application均为同一个全局对象,在ActivityThread内部有一个列表专门用于维护所有应用的application

final ArrayList<Application> mAllApplications  = new ArrayList<Application>();

getApplicationContext返回的也是Application对象,只不过返回类型为Context,看看它的实现

  1. @Override
  2. public Context getApplicationContext() {
  3. return (mPackageInfo != null) ?
  4. mPackageInfo.getApplication() : mMainThread.getApplication();
  5. }

上面代码中mPackageInfo是包含当前应用的包信息、比如包名、应用的安装目录等,原则上来说,作为第三方应用,包信息mPackageInfo不可能为空,在这种情况下,getApplicationContext返回的对象和getApplication是同一个。但是对于系统应用,包信息有可能为空,具体就不深入研究了。从这种角度来说,对于第三方应用,一个应用只存在一个Application对象,且通过getApplication和getApplicationContext得到的是同一个对象,两者的区别仅仅是返回类型不同。

应用中Context的数量

到此已经很明了了,一个应用中Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application。

Android源码分析-全面理解Context的更多相关文章

  1. Android源码分析(十五)----GPS冷启动实现原理分析

    一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...

  2. Android源码分析(十四)----如何使用SharedPreferencce保存数据

    一:SharedPreference如何使用 此文章只是提供一种数据保存的方式, 具体使用场景请根据需求情况自行调整. EditText添加saveData点击事件, 保存数据. diff --git ...

  3. Android源码分析(十三)----SystemUI下拉状态栏如何添加快捷开关

    一:如何添加快捷开关 源码路径:frameworks/base/packages/SystemUI/res/values/config.xml 添加headset快捷开关,参考如下修改. Index: ...

  4. Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果

    一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...

  5. Android源码分析(六)-----蓝牙Bluetooth源码目录分析

    一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...

  6. Android源码分析(十七)----init.rc文件添加脚本代码

    一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh ...

  7. Android源码分析(十六)----adb shell 命令进行OTA升级

    一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...

  8. Android源码分析(十一)-----Android源码中如何引用aar文件

    一:aar文件如何引用 系统Settings中引用bidehelper-1.1.12.aar 文件为例 源码地址:packages/apps/Settings/Android.mk LOCAL_PAT ...

  9. Android源码分析(十)-----关机菜单中如何添加飞行模式选项

    一:关机菜单添加飞行模式选项 源码路径:frameworks/base/core/res/res/values/config.xml 增加<item>airplane</item&g ...

随机推荐

  1. CentOS6下Haproxy的安装配置

    Haproxy 是一个开源的负载均衡和反向代理软件,其提供了高可用的网络服务.其一般是应用于web服务,但同时也能为SMTP和终端服务等提供可靠的支持. 1.下载安装haproxy wget ftp: ...

  2. 关于android开发添加菜单XML文件之后无法在R.java中生成ID的问题

    因为和同学分开做的android软件,现在想整合他做的界面部分,于是拷贝了res和src文件夹的文件,其中包括一个res.menu文件夹中的XML.但是每次将该文件导入到工程总无法自动在R.java中 ...

  3. 转: fscanf()函数详解

    以前解析有规律的文件的时候要么用正则表达式,要么就是傻傻的自己写程序来解析有规律的文件.今天突然发现c的库函数中有一个现成的可以解析有规律的文件的函数,就是fscanf()函数.哎 以前自己做了这么多 ...

  4. 函数返回char* 的解决方案

    在C语言中,自动变量在堆栈中分配内存.当包含自动变量的函数或代码块退出时,它们所占用的内存便被回收,它们的内容肯定会被下一个所调用的函数覆盖.这一切取决于堆栈中先前的自动变量位于何处,活动函数声明了什 ...

  5. 【TOMCAT启动异常】The BASEDIR environment variable is not defined correctly

    <span style="font-size:18px;">The BASEDIR environment variable is not defined correc ...

  6. Leetcode_205_Isomorphic Strings

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/46530865 Given two strings s an ...

  7. BashOnWindow安装mysql

    1.下载mysql 服务器和客户端 sudo apt-get install mysql-server mysql-client 其中过程会让输入用户名和密码 2.启动mysql服务 sudo ser ...

  8. Spring MVC 自动为对象注入枚举类型

    原文地址:http://1358440610-qq-com.iteye.com/blog/2079048 如果一个对象里面有枚举类型的话,则Spring MVC是不能够直接进行注入的,因为它只实现了一 ...

  9. iOS 多线程之GCD的简单使用

    在iOS开发中,遇到耗时操作,我们经常用到多线程技术.Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,只需定义想要执行的任务,然后添加到适当的调度队列 ...

  10. mac+php+nginx+laravel配置启动

    首先保证mac安装php,nginx,composer 根据laravel中文文档进行安装 http://laravelacademy.org/post/6665.html 直接指向 composer ...