深入解析Android App开发中Context的用法

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
根据前面所说,由于实现了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());

其思路和上面两个基本一样,在此就不再详述。

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就是典型的例子。

public Resources getTopLevelResources(String resDir, int displayId,
    Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
  final float scale = compatInfo.applicationScale;
  ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
      token);
  Resources r;
  synchronized (this) {
    // Resources is app scale dependent.
    if (false) {
      Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
    }
    WeakReference<Resources> wr = mActiveResources.get(key);
    r = wr != null ? wr.get() : null;
    //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
    if (r != null && r.getAssets().isUpToDate()) {
      if (false) {
        Slog.w(TAG, "Returning cached resources " + r + " " + resDir
            + ": appScale=" + r.getCompatibilityInfo().applicationScale);
      }
      return r;
    }
  } 

  //if (r != null) {
  //  Slog.w(TAG, "Throwing away out-of-date resources!!!! "
  //      + r + " " + resDir);
  //} 

  AssetManager assets = new AssetManager();
  if (assets.addAssetPath(resDir) == 0) {
    return null;
  } 

  //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
  DisplayMetrics dm = getDisplayMetricsLocked(displayId);
  Configuration config;
  boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
  final boolean hasOverrideConfig = key.hasOverrideConfiguration();
  if (!isDefaultDisplay || hasOverrideConfig) {
    config = new Configuration(getConfiguration());
    if (!isDefaultDisplay) {
      applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
    }
    if (hasOverrideConfig) {
      config.updateFrom(key.mOverrideConfiguration);
    }
  } else {
    config = getConfiguration();
  }
  r = new Resources(assets, dm, config, compatInfo, token);
  if (false) {
    Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
        + r.getConfiguration() + " appScale="
        + r.getCompatibilityInfo().applicationScale);
  } 

  synchronized (this) {
    WeakReference<Resources> wr = mActiveResources.get(key);
    Resources existing = wr != null ? wr.get() : null;
    if (existing != null && existing.getAssets().isUpToDate()) {
      // Someone else already created the resources while we were
      // unlocked; go ahead and use theirs.
      r.getAssets().close();
      return existing;
    } 

    // XXX need to remove entries when weak references go away
    mActiveResources.put(key, new WeakReference<Resources>(r));
    return r;
  }
}

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

代码:单例模式的ResourcesManager类

public static ResourcesManager getInstance() {
  synchronized (ResourcesManager.class) {
    if (sResourcesManager == null) {
      sResourcesManager = new ResourcesManager();
    }
    return sResourcesManager;
  }
}

getApplication和getApplicationContext的区别

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

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

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

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

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

在此总结一下:
(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.

(0)

相关推荐

  • Android 中Context的使用方法详解

    Android 中Context的使用方法详解 概要: Context字面意思是上下文,位于framework package的android.content.Context中,其实该类为LONG型,类似Win32中的Handle句柄.很多方法需要通过 Context才能识别调用者的实例:比如说Toast的第一个参数就是Context,一般在Activity中我们直接用this代替,代表调用者的实例为Activity,而到了一个button的onClick(View view)等方法时,我们用t

  • 谈谈Android里的Context的使用实例

    大家好,今天给大家分享一下Android里的Context的一些用法,以前经常有人在群里问我比如我在一个工具类里的某个方法,或者View里需要调用Context.但是工具类还有View里没有这个上下文怎么办?为了解决大家的疑问,为了解决大家的疑问,我今天写一个简单的Demo.让大家如何学好自如的用Context.想什么时候有Context,什么时候就有Context. 这里大致可以分为两种:一是传递Context参数,二是调用全局的Context. 其实我们应用启动的时候会启动Applicati

  • Android获取其他包的Context实例代码

    Android中有Context的概念,想必大家都知道.Context可以做很多事情,打开activity.发送广播.打开本包下文件夹和数据库.获取classLoader.获取资源等等.如果我们得到了一个包的Context对象,那我们基本上可以做这个包自己能做的大部分事情.那我们能得到吗?很高兴的告诉你,能!Context有个createPackageContext方法,可以创建另外一个包的上下文,这个实例不同于它本身的Context实例,但是功能是一样的. 这个方法有两个参数:1.packag

  • android基础教程之context使用详解

    在android中有两种context,一种是application context,一种是activity context,通常我们在各种类和方法间传递的是activity context. 区别联系: 复制代码 代码如下: public class MyActivity extends Activity {    public void method() {       mContext = this;    // since Activity extends Context       m

  • Android全局获取Context实例详解

    Android全局获取Context实例详解 在弹出Toast 启动活动 发送广播 操作数据库 使用通知等等时都需要Context 如果操作在活动中进行是很简单的,因为活动本身就是一个Context对象 但是当逻辑代码脱离了Activity类,此时使用Context就需要一些技巧了: 我们可以定制一个自己的Application类,以便管理程序内一些全局状态信息,比如全局Context 代码如下: public class MyApplication extends Application{ p

  • Android编程实现为ListView创建上下文菜单(ContextMenu)的方法

    本文实例讲述了Android编程实现为ListView创建上下文菜单(ContextMenu)的方法.分享给大家供大家参考,具体如下: ContextMenu称为上下文菜单,一般在控件上长按时弹出.今天我们学习ContextMenu的用法,这里与listview相结合,先在ListView显示几个Item,然后在Item上长按,弹出一个菜单(就是ContextMenu),点击菜单上的项目,提示刚才长按的Item的Position. main.xml文件 <?xml version="1.0

  • 避免 Android中Context引起的内存泄露

    Context是我们在编写Android程序经常使用到的对象,意思为上下文对象. 常用的有Activity的Context还是有Application的Context.Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源.在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题. 本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄

  • 深入解析Android App开发中Context的用法

    Context在开发Android应用的过程中扮演着非常重要的角色,比如启动一个Activity需要使用context.startActivity方法,将一个xml文件转换为一个View对象也需要使用Context对象,可以这么说,离开了这个类,Android开发寸步难行,对于这样一个类,我们又对他了解多少呢.我就说说我的感受吧,在刚开始学习Android开发时,感觉使用Context的地方一直就是传入一个Activity对象,久而久之感觉只要是Context的地方就传入一个Activity就行

  • 全面解析Android应用开发中Activity类的用法

    Activity类处于android.app包中,继承体系如下: 1.java.lang.Object 2.android.content.Context 3.android.app.ApplicationContext 4.android.app.Activity activity是单独的,用于处理用户操作.几乎所有的activity都要和用户打交道,所以activity类创建了一个窗口,开发人员可以通过setContentView(View)接口把UI放到activity创建的窗口上,当ac

  • Android App开发中ViewPager组件的入门使用教程

    首先让大家有个全局的认识,直接上个项目,看看仅仅通过这几行代码,竟然就能完成如此强悍的功能.下篇再仔细讲讲为什么要这么写. 效果图: 实现了三个view间的相互滑动 第一个VIEW向第二个VIEW滑动: 第二个VIEW向第三个VIEW滑动: 一.新建项目,引入ViewPager控件 ViewPager.它是google SDk中自带的一个附加包的一个类,可以用来实现屏幕间的切换. 1.在主布局文件里加入 <RelativeLayout xmlns:android="http://schem

  • Android App开发中使用RecyclerView实现Gallery画廊的实例

    什么是RecyclerView         RecyclerView是Android 5.0 materials design中的组件之一,相应的还有CardView.Palette等.看名字我们就能看出一点端倪,没错,它主要的特点就是复用.我们知道,Listview中的Adapter中可以实现ViewHolder的复用.RecyclerView提供了一个耦合度更低的方式来复用ViewHolder,并且可以轻松的实现ListView.GridView以及瀑布流的效果. RecyclerVie

  • Android app开发中的Fragment入门学习教程

    在Android3.0上开始引入了一个新概念叫Fragment.它有自己的布局文件,可以作为组件排布,也可以相互组合去实现不同的布局显示.使用Fragment可以重复利用代码,并且可以满足不同设备尺寸的需求.Fragment不能单独存在,只能存在于Activity中,而一个Activity可以拥有多个Fragment.很重要的一点是,Fragment可以和Activity中的其它组件一起使用,无需重写所有Activity的接口.所以使用Fragment就可以这样来完成上例中"主界面-详细界面&q

  • 浅谈Android app开发中Fragment的Transaction操作

    在Android中,对Fragment的操作都是通过FragmentTransaction来执行.而从Fragment的结果来看,FragmentTransaction中对Fragment的操作大致可以分为两类: 显示:add() replace() show() attach() 隐藏:remove() hide() detach() 对于每一组方法,虽然最后产生的效果类似,但方法背后带来的副作用以及对Fragment的生命周期的影响都不尽相同. add() vs. replace() 只有在

  • Android App开发中Gradle构建过程的配置方法

    在build文件中使用了Android或者Java插件之后就会自动创建一系列可以运行的任务. Gradle中有如下一下默认约定的任务: 1. assemble 该任务包含了项目中的所有打包相关的任务,比如java项目中打的jar包,Android项目中打的apk 2. check 该任务包含了项目中所有验证相关的任务,比如运行测试的任务 3. build 该任务包含了assemble和check 4. clean 该任务会清空项目的所有的输出,删除所有在assemble任务中打的包 assemb

  • Android App开发中自定义View和ViewGroup的实例教程

    View Android所有的控件都是View或者View的子类,它其实表示的就是屏幕上的一块矩形区域,用一个Rect来表示,left,top表示View相对于它的parent View的起点,width,height表示View自己的宽高,通过这4个字段就能确定View在屏幕上的位置,确定位置后就可以开始绘制View的内容了. View绘制过程 View的绘制可以分为下面三个过程: Measure View会先做一次测量,算出自己需要占用多大的面积.View的Measure过程给我们暴露了一个

  • Android App开发中创建Fragment组件的教程

    你可以认为Fragment作为Activity的一个模块部分,有它自己的生命周期,获取它自己的事件,并且你可以在Activity运行的时候添加或者移除它(有点像你可以在不同的Activity中重用的一个"子Activity").这节课程讲述如何使用Support Library继承Fragment类,所以你的应用程序仍然是兼容运行的系统版本低于Android1.6的设备. 注意:如果你决定你的应用要求的最低的API级别是11或者更高,你不需要使用Support Library,反而能使

  • Android App开发中RecyclerView控件的基本使用教程

    概述 RecyclerView出现已经有一段时间了,相信大家肯定不陌生了,大家可以通过导入support-v7对其进行使用. 据官方的介绍,该控件用于在有限的窗口中展示大量数据集,其实这样功能的控件我们并不陌生,例如:ListView.GridView. 那么有了ListView.GridView为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecor

随机推荐