转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/21829971 (来自singwhatiwanna的博客)java
Context在android中的做用不言而喻,当咱们访问当前应用的资源,启动一个新的activity的时候都须要提供Context,而这个Context究竟是什么呢,这个问题好像很好回答又好像难以说清楚。从字面意思,Context的意思是“上下文”,或者也能够叫作环境、场景等,尽管如此,仍是有点抽象。从类的继承来讲,Context做为一个抽象的基类,它的实现子类有三种:Application、Activity和Service(姑且这么说,暂时无论ContextWrapper等类),那么这三种有没有区别呢?为何经过任意的Context访问资源都获得的是同一套资源呢?getApplication和getApplicationContext有什么区别呢?应用中到底有多少个Context呢?本文将围绕这些问题一一展开,所用源码版本为Android4.4。android
Context是一个抽象基类,咱们经过它访问当前包的资源(getResources、getAssets)和启动其余组件(Activity、Service、Broadcast)以及获得各类服务(getSystemService),固然,经过Context能获得的不只仅只有上述这些内容。对Context的理解能够来讲:Context提供了一个应用的运行环境,在Context的大环境里,应用才能够访问资源,才能完成和其余组件、服务的交互,Context定义了一套基本的功能接口,咱们能够理解为一套规范,而Activity和Service是实现这套规范的子类,这么说也许并不许确,由于这套规范实际是被ContextImpl类统一实现的,Activity和Service只是继承并有选择性地重写了某些规范的实现。数据结构
首先,它们都间接继承了Context,这是它们的相同点。app
不一样点,能够从几个方面来讲:首先看它们的继承关系ide
Activity的继承关系源码分析
Service和Application的继承关系this
经过对比能够清晰地发现,Service和Application的类继承关系比较像,而Activity还多了一层继承ContextThemeWrapper,这是由于Activity有主题的概念,而Service是没有界面的服务,Application更是一个抽象的东西,它也是经过Activity类呈现的。.net
下面来看一下三者在Context方面的区别设计
上文已经指出,Context的真正实现都在ContextImpl中,也就是说Context的大部分方法调用都会转到ContextImpl中,而三者的建立均在ActivityThread中完成,我以前写过一篇文章Android源码分析-Activity的启动过程,在文中我指出Activity启动的核心过程是在ActivityThread中完成的,这里要说明的是,Application和Service的建立也是在ActivityThread中完成的。下面咱们看下三者在建立时是怎么和ContextImpl相关联的。code
Activity对象中ContextImpl的建立
代码为ActivityThread中的performLaunchActivity方法
if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); /** * createBaseContextForActivity中建立ContextImpl的代码 * ContextImpl appContext = new ContextImpl(); * appContext.init(r.packageInfo, r.token, this); * appContext.setOuterContext(activity); */ CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config); if (customIntent != null) { activity.mIntent = customIntent; } ... }能够看出,Activity在建立的时候会new一个ContextImpl对象并在attach方法中关联它,须要注意的是,建立Activity使用的数据结构是ActivityClientRecord。
Application对象中ContextImpl的建立
代码在ActivityThread中的handleBindApplication方法中,此方法内部调用了makeApplication方法
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { java.lang.ClassLoader cl = getClassLoader(); 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); } } ... }看代码发现和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.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返回结果为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,看看它的实现
@Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); }上面代码中mPackageInfo是包含当前应用的包信息、好比包名、应用的安装目录等,原则上来讲,做为第三方应用,包信息mPackageInfo不可能为空,在这种状况下,getApplicationContext返回的对象和getApplication是同一个。可是对于系统应用,包信息有可能为空,具体就不深刻研究了。从这种角度来讲,对于第三方应用,一个应用只存在一个Application对象,且经过getApplication和getApplicationContext获得的是同一个对象,二者的区别仅仅是返回类型不一样。