Context详解

前言

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(this, null, 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。

转载于:https://www.cnblogs.com/wenfei123chai/p/4584015.html

<think>好的,我现在要详细解答用户关于Go语言Context机制的问题。用户想要了解它的详细用法、原理、使用场景以及源码解析。我需要结合之前提供的引用内容,确保回答结构清晰,符合用户的要求。 首先,我需要回忆一下Go语言中Context的基本概念。Context主要用于控制goroutine的生命周期,以及在多个goroutine之间传递请求相关的数据。用户提供的引用中,提到了Context树的结构,根Context由Background和TODO生成,还有With系列方法派生子树。另外,Context还可以通过WithValue传递键值对数据,比如用户认证信息和请求ID。 接下来,我需要组织内容的结构。通常,这样的问题需要分几个部分来解答,比如设计目的、核心功能、使用场景、源码解析、常见问题等。这样可以一步步引导用户理解Context的机制。 设计目的方面,引用中提到Context用于控制goroutine的生命周期,传递数据。需要说明为什么需要Context,比如在并发操作中管理取消信号、超时等,避免goroutine泄漏。 核心功能部分,可以分点说明:树形结构管理、生命周期控制、数据传递。引用[1]提到Context树的结构,父节点可以派生多个子节点,形成树形结构。引用[5]提到了WithValue传递数据。这里需要详细说明每个功能的具体实现和使用方法。 使用场景需要举例子,比如HTTP请求处理、数据库操作超时控制、跨服务调用传递跟踪信息。引用[5]中的例子,如传递用户认证信息和请求ID,可以在这里使用。 源码解析部分,需要分析Context接口的结构,以及具体的实现,比如emptyCtx、cancelCtx、timerCtx、valueCtx。引用[3]提到了emptyCtx由Background和TODO生成,这两个是根Context。还需要解释WithCancel、WithDeadline、WithTimeout、WithValue的实现机制,结合引用中的内容,比如With系列方法如何派生子树。 常见问题方面,可以讨论为什么使用树形结构(比如引用[2]提到的结构体字段的必要性),数据传递的注意事项,比如只传递请求相关的数据,避免滥用。 需要注意用户提供的引用中,引用[1]和[3]涉及Context的结构和根节点,引用[5]涉及数据传递。引用[4]关于Gin框架的部分可能和Context的使用场景相关,比如在Web框架中如何处理请求的上下文。 需要确保回答中的LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,但可能在这个回答中数学公式较少,但如果有相关算法或结构说明可能需要使用,比如树结构的表示。不过目前看起来可能不需要,但需要注意用户的其他要求,比如中文回答、生成相关问题等。 最后,生成的相关问题需要围绕Context的机制,比如与goroutine的关系、数据传递的注意点、根Context的作用等。 现在需要检查是否有遗漏的信息,确保所有引用内容都被合理利用。例如,引用[2]提到源码的结构体字段设计,可能需要在源码解析部分详细说明各个Context实现的结构体字段,如cancelCtx的children字段用于存储子节点,以便在取消时传播信号。 总结,回答的结构大致如下: 1. 设计目的 2. 核心功能 3. 使用场景 4. 源码解析 5. 常见问题 6. 相关问题 每个部分需要结合引用内容展开,并确保正确引用标记。同时,代码部分需要用代码块展示,比如WithCancel的使用示例,以及可能的Context树结构图(如果需要的话,但用户示例中可能用文字描述)。 现在开始撰写正式回答,注意用中文,结构清晰,符合用户要求的格式,正确使用引用标记,并在末尾生成相关问题。</think>### Go语言Context机制详解 #### 一、设计目的 Context的核心设计目标是为**跨goroutine的请求链路**提供统一的**生命周期控制**和**数据传递**机制。通过树形结构管理上下文关系,实现高效的取消信号传播和超时控制[^1][^5]。 #### 二、核心功能 1. **树形结构管理** 每个Context通过`WithCancel`、`WithDeadline`等方法派生子节点,形成树形结构。父节点的取消操作会递归触发所有子节点的取消信号[^1]。 ```go ctx, cancel := context.WithCancel(parentCtx) defer cancel() // 触发子节点取消 ``` 2. **生命周期控制** - **取消控制**:通过`context.WithCancel`生成可手动取消的Context。 - **超时控制**:`context.WithTimeout`和`context.WithDeadline`实现自动超时终止。 ```go // 超时控制示例 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() ``` 3. **数据传递** 使用`context.WithValue`传递请求范围内的键值对(如用户身份、追踪ID),但需注意仅传递必要数据。 ```go ctx := context.WithValue(parentCtx, "requestID", "12345") ``` #### 三、使用场景 1. **HTTP请求处理** 在Web框架(如Gin)中传递请求上下文,管理中间件与处理函数的超时[^4]。 2. **数据库操作超时** 强制终止长时间未响应的数据库查询。 3. **跨服务调用** 传递分布式追踪信息(如OpenTelemetry)。 #### 四、源码解析 1. **Context接口** ```go type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any } ``` - `Done()`:返回一个通道,用于接收取消信号。 - `Value()`:获取键值对数据。 2. **emptyCtx(根Context)** `context.Background()`和`context.TODO()`返回的不可变空上下文,作为Context树的根节点[^3]。 3. **派生实现** - **cancelCtx**:通过`WithCancel`生成,存储子节点用于传播取消信号。 - **timerCtx**:通过`WithDeadline`生成,包含定时器实现超时。 - **valueCtx**:通过`WithValue`生成,以链表形式存储键值对。 #### 五、常见问题 1. **为什么使用树形结构?** 树形结构确保取消信号的高效传播,父节点取消时无需遍历所有子节点,直接通过树形依赖关系广播[^2]。 2. **数据传递的注意事项** 应避免传递函数参数或全局变量应处理的数据,仅传递**请求范围**内的元数据(如日志ID)。 3. **根Context的作用** `Background()`作为默认根节点,`TODO()`用于未确定父节点的临时场景[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值