Android源码分析-全面理解Context (转)

来源:互联网 发布:淘宝网店制作 编辑:程序博客网 时间:2024/04/26 17:33

转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/21829971 (来自singwhatiwanna的博客)

前言

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方法

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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方法

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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就是典型的例子。

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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类

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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>()

为什么说getApplication返回的都是同一个Application对象呢,是因为Activity和Service的getApplication返回的Application对象是由ActivityThread创建它们的时候通过它们的attach方法来传递给它们的,也就是说所有Activity和Service所持有的Application均是ActivityThread内部的Application,由于一个应用只有一个包信息,所以ActivityThread内部只可能创建出一个Application,原因是当执行packageInfo.makeApplication的时候,如果已经创建过Application了,packageInfo.makeApplication方法就不会再创建新的Application。关于一个应用只有一个包信息,从代码的逻辑来看的确是这样的,在ActivityThread内部同样有一个列表专门用于维护所有应用的包信息:

final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<String, WeakReference<LoadedApk>>()

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

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  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。
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 歌华有线欠费1个月怎么办 唐小僧倒闭了百姓投的钱怎么办? 手机号被别人注册了华为账号怎么办 华为账号手机号显示已被注册怎么办 买了鑫和陌车的怎么办 注册游戏账号时乱输入的邮箱怎么办 yy频道解邦不能开直播怎么办 淘宝的淘金币快过期了怎么办 乐透啦彩票让骗了6万怎么办 交了认筹金不能进抢购平台怎么办 爱奇艺会文学会员办了想退款怎么办 海淘信用卡入账但是砍单怎么办 褐色分泌物流了好几天了怎么办? 淘宝买的衣服一直不发货怎么办 从国外寄东西到国内被税了怎么办 百度网盘上传文件数量有限制怎么办 腾讯视频上传文件过限制大小怎么办 三星s7打网页又卡又慢怎么办 路由器的上网账号和口令忘了怎么办 小米笔记本移动热点连接不上怎么办 移动宽带密码重置后认证失败怎么办 移动光纤不记得账号和密码怎么办? 宽带为什么交了钱还是不能用怎么办 小孩被虎牙直播诱导支付了款怎么办 房间里4g网络信号差怎么办 移动4g网络信号不满格怎么办 大风号无法上传视频暂停服务怎么办 过了竞牌保证金交付时间怎么办 亚马逊产品上架后货物没到怎么办 工行企业网银证书过期了怎么办 海淘转运地址国家填错了怎么办 集装箱实重与申报重量不一样怎么办 微博复制的淘口令找不到了怎么办 买了移动手机不能用联通卡怎么办 移动手机用联通卡网速慢怎么办 移动手机插联通卡没反应怎么办 移动手机办了联通大王卡怎么办 qq被冻结但是有至尊宝怎么办 qq被冻结了有至尊宝怎么办 移动电话卡注销了里面的钱怎么办 罗麦的oa上经理喜报没截图怎么办