Android源码分析-全面理解Context
转载请注明出处: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
- if(activity!=null){
- ContextappContext=createBaseContextForActivity(r,activity);
- /**
- *createBaseContextForActivity中创建ContextImpl的代码
- *ContextImplappContext=newContextImpl();
- *appContext.init(r.packageInfo,r.token,this);
- *appContext.setOuterContext(activity);
- */
- CharSequencetitle=r.activityInfo.loadLabel(appContext.getPackageManager());
- Configurationconfig=newConfiguration(mCompatConfiguration);
- if(DEBUG_CONFIGURATION)Slog.v(TAG,"Launchingactivity"
- +r.activityInfo.name+"withconfig"+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;
- }
- ...
- }
Application对象中ContextImpl的创建
代码在ActivityThread中的handleBindApplication方法中,此方法内部调用了makeApplication方法
[java] view plain copy
- publicApplicationmakeApplication(booleanforceDefaultAppClass,
- Instrumentationinstrumentation){
- if(mApplication!=null){
- returnmApplication;
- }
- Applicationapp=null;
- StringappClass=mApplicationInfo.className;
- if(forceDefaultAppClass||(appClass==null)){
- appClass="android.app.Application";
- }
- try{
- java.lang.ClassLoadercl=getClassLoader();
- ContextImplappContext=newContextImpl();
- appContext.init(this,null,mActivityThread);
- app=mActivityThread.mInstrumentation.newApplication(
- cl,appClass,appContext);
- appContext.setOuterContext(app);
- }catch(Exceptione){
- if(!mActivityThread.mInstrumentation.onException(app,e)){
- thrownewRuntimeException(
- "Unabletoinstantiateapplication"+appClass
- +":"+e.toString(),e);
- }
- }
- ...
- }
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
- publicResourcesgetTopLevelResources(StringresDir,intdisplayId,
- ConfigurationoverrideConfiguration,CompatibilityInfocompatInfo,IBindertoken){
- finalfloatscale=compatInfo.applicationScale;
- ResourcesKeykey=newResourcesKey(resDir,displayId,overrideConfiguration,scale,
- token);
- Resourcesr;
- synchronized(this){
- //Resourcesisappscaledependent.
- 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,"Returningcachedresources"+r+""+resDir
- +":appScale="+r.getCompatibilityInfo().applicationScale);
- }
- returnr;
- }
- }
- //if(r!=null){
- //Slog.w(TAG,"Throwingawayout-of-dateresources!!!!"
- //+r+""+resDir);
- //}
- AssetManagerassets=newAssetManager();
- if(assets.addAssetPath(resDir)==0){
- returnnull;
- }
- //Slog.i(TAG,"Resource:key="+key+",displaymetrics="+metrics);
- DisplayMetricsdm=getDisplayMetricsLocked(displayId);
- Configurationconfig;
- booleanisDefaultDisplay=(displayId==Display.DEFAULT_DISPLAY);
- finalbooleanhasOverrideConfig=key.hasOverrideConfiguration();
- if(!isDefaultDisplay||hasOverrideConfig){
- config=newConfiguration(getConfiguration());
- if(!isDefaultDisplay){
- applyNonDefaultDisplayMetricsToConfigurationLocked(dm,config);
- }
- if(hasOverrideConfig){
- config.updateFrom(key.mOverrideConfiguration);
- }
- }else{
- config=getConfiguration();
- }
- r=newResources(assets,dm,config,compatInfo,token);
- if(false){
- Slog.i(TAG,"Createdappresources"+resDir+""+r+":"
- +r.getConfiguration()+"appScale="
- +r.getCompatibilityInfo().applicationScale);
- }
- synchronized(this){
- WeakReference<Resources>wr=mActiveResources.get(key);
- Resourcesexisting=wr!=null?wr.get():null;
- if(existing!=null&&existing.getAssets().isUpToDate()){
- //Someoneelsealreadycreatedtheresourceswhilewewere
- //unlocked;goaheadandusetheirs.
- r.getAssets().close();
- returnexisting;
- }
- //XXXneedtoremoveentrieswhenweakreferencesgoaway
- mActiveResources.put(key,newWeakReference<Resources>(r));
- returnr;
- }
- }
代码:单例模式的ResourcesManager类
- publicstaticResourcesManagergetInstance(){
- synchronized(ResourcesManager.class){
- if(sResourcesManager==null){
- sResourcesManager=newResourcesManager();
- }
- returnsResourcesManager;
- }
- }
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
- @Override
- publicContextgetApplicationContext(){
- return(mPackageInfo!=null)?
- mPackageInfo.getApplication():mMainThread.getApplication();
- }
应用中Context的数量
到此已经很明了了,一个应用中Context的数量等于Activity的个数+ Service的个数+ 1,这个1为Application。更多相关文章
- Android(安卓)Support Design 中 CoordinatorLayout 与 Behavior
- Android(安卓)-- 再来一发Json
- android开发相关资源
- Android(安卓)一般动画Animation和属性动画Animator
- android 反纠结app开发: 在线程中更新view
- 引用系统资源 error: Error: Resource is not public.
- Android的StatusBar分析
- android HorizontalScrollView的简单使用
- Android(安卓)之最新最全的Intent传递数据方法