Android源码分析-全面理解Context

来源:互联网 发布:双流区2016年公交优化 编辑:程序博客网 时间:2024/04/16 14:07


前言

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.   
  17.     if (customIntent != null) {  
  18.         activity.mIntent = customIntent;  
  19.     }  
  20.     ...  
  21. }  

可以看出,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.   
  7.     Application app = null;  
  8.   
  9.     String appClass = mApplicationInfo.className;  
  10.     if (forceDefaultAppClass || (appClass == null)) {  
  11.         appClass = "android.app.Application";  
  12.     }  
  13.   
  14.     try {  
  15.         java.lang.ClassLoader cl = getClassLoader();  
  16.         ContextImpl appContext = new ContextImpl();  
  17.         appContext.init(thisnull, mActivityThread);  
  18.         app = mActivityThread.mInstrumentation.newApplication(  
  19.                 cl, appClass, appContext);  
  20.         appContext.setOuterContext(app);  
  21.     } catch (Exception e) {  
  22.         if (!mActivityThread.mInstrumentation.onException(app, e)) {  
  23.             throw new RuntimeException(  
  24.                 "Unable to instantiate application " + appClass  
  25.                 + ": " + e.toString(), e);  
  26.         }  
  27.     }  
  28.     ...  
  29. }  

看代码发现和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.   
  24.     //if (r != null) {  
  25.     //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "  
  26.     //            + r + " " + resDir);  
  27.     //}  
  28.   
  29.     AssetManager assets = new AssetManager();  
  30.     if (assets.addAssetPath(resDir) == 0) {  
  31.         return null;  
  32.     }  
  33.   
  34.     //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);  
  35.     DisplayMetrics dm = getDisplayMetricsLocked(displayId);  
  36.     Configuration config;  
  37.     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);  
  38.     final boolean hasOverrideConfig = key.hasOverrideConfiguration();  
  39.     if (!isDefaultDisplay || hasOverrideConfig) {  
  40.         config = new Configuration(getConfiguration());  
  41.         if (!isDefaultDisplay) {  
  42.             applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);  
  43.         }  
  44.         if (hasOverrideConfig) {  
  45.             config.updateFrom(key.mOverrideConfiguration);  
  46.         }  
  47.     } else {  
  48.         config = getConfiguration();  
  49.     }  
  50.     r = new Resources(assets, dm, config, compatInfo, token);  
  51.     if (false) {  
  52.         Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "  
  53.                 + r.getConfiguration() + " appScale="  
  54.                 + r.getCompatibilityInfo().applicationScale);  
  55.     }  
  56.   
  57.     synchronized (this) {  
  58.         WeakReference<Resources> wr = mActiveResources.get(key);  
  59.         Resources existing = wr != null ? wr.get() : null;  
  60.         if (existing != null && existing.getAssets().isUpToDate()) {  
  61.             // Someone else already created the resources while we were  
  62.             // unlocked; go ahead and use theirs.  
  63.             r.getAssets().close();  
  64.             return existing;  
  65.         }  
  66.   
  67.         // XXX need to remove entries when weak references go away  
  68.         mActiveResources.put(key, new WeakReference<Resources>(r));  
  69.         return r;  
  70.     }  
  71. }  

根据上述代码中资源的请求机制,再加上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。




转自:http://www.cnblogs.com/android100/p/Android-Context.html


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 二手房交税后房主不卖怎么办 二房东收不到租拖欠房租怎么办 房东不给换门锁怎么办 租房到期房东联系不到租客怎么办 廉租房名下有车怎么办 路边停车收忘记交费怎么办 考编忘记交费了怎么办 深圳公租房入库了接下来怎么办 公租房5年以后怎么办 教务系统密码忘记了怎么办 林科大教务处密码忘记了怎么办 智学号密码忘了怎么办 正方教务管理系统忘记密码怎么办 正方教务系统忘记密码怎么办 教务网密码忘了怎么办 电动车解除限速报警器不响怎么办 公租房住满5年怎么办 广州公租房收入超标怎么办 深圳法院拍卖房子不肯搬走怎么办 上海奉贤公租房的期满怎么办 公租房人口少了怎么办 公租房太远了怎么办 商品房没有门厅业主该怎么办 公帐付款备注错了怎么办 我是农村户口在外省交社保怎么办 北京租房遇到黑中介怎么办 上海租房子不让带孩子怎么办 租的房子没窗户怎么办 北京安河桥安河家园租房被骗怎么办 领完失业金后怎么办 北京公租房太小怎么办 申请公租房太小怎么办 房东电费收贵了怎么办 申请公租房工资超了怎么办 重庆公租房工资超了怎么办 公租房申请父母房子贷款怎么办 公产房父母去世办公证怎么办 动迁过程中承租人去世了怎么办 公租房的房间带阳台怎么办 公租房合同到期没有社保怎么办 租房合同没有到期违约了怎么办