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

深入解析Android App开发中Context的用法,第1张

概述Context在开发Android应用的过程中扮演着非常重要的角色,比如启动一个Activity需要使用context.startActivity方法,将一个xml文件转换为一个View对象也需要使用Context对象,可以这么说,离开了这个类,Android开发

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,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,getInstrumentation(),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,this);       Application app =packageInfo.makeApplication(false,mInstrumentation);      context.setouterContext(service);      service.attach(context,data.info.name,data.token,ActivityManagerNative.getDefault());

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

Context对资源的访问
很明确,不同的Context得到的都是同一份资源。这是很好理解的,请看下面的分析

得到资源的方式为context.getResources,而真正的实现位于ContextImpl中的getResources方法,在ContextImpl中有一个成员 private Resources mResources,它就是getResources方法返回的结果,mResources的赋值代码为:

mResources = mResourcesManager.gettopLevelResources(mPackageInfo.getResDir(),display.DEFAulT_disPLAY,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,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.

总结

以上是内存溢出为你收集整理的深入解析Android App开发中Context的用法全部内容,希望文章能够帮你解决深入解析Android App开发中Context的用法所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1149650.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存