context详解

什么是Context?
想必大家都不陌生,在 Android 开发中离不开 Context 调用各种跟系统有关的 API 都必须用到 Context 。我们可以将她理解为上下文环境,大概就是里面存储一堆全局变量,这些变量在调用系统 API 时需要用到。文字始终难以表达我想说的,咱们来分析原理吧!

Context  哪里来的?
开发 Android 应用必须得有一个 Activity ,然后在你的 Activity 里面调用各种系统 API 比如,
[java]  view plain copy
  1. String string = getString(R.string.app_name);  
这行代码看似简单,但是如果不让你在 Activity 中调用你该怎么获取这个 string ?
[java]  view plain copy
  1. Context context;  
  2. context.getString(R.string.app_name);  
你需要一个 Context 对象,那在 Activity 中为何不需要呢?
答案是,Activity 本来就是一个 Context 他继承自 Context,你打开 Activity 可以知道他的继承顺序如下:
Activity -> ContextThemeWrapper -> ContextWrapper -> Context

Context 是一个抽象类,所有方法都没实现,ContextWrapper 实现了所有方法,但是他只是一个代理,打开源码你可以看到他是这样的:
[java]  view plain copy
  1. @Override  
  2. public AssetManager getAssets() {  
  3.     return mBase.getAssets();  
  4. }  
  5.   
  6. @Override  
  7. public Resources getResources()  
  8. {  
  9.     return mBase.getResources();  
  10. }  
他仅仅调用 mBase 的方法,(这种在设计模式里叫 代理模式 Proxy ),看来我们得弄清楚这个 mBase 哪来的了!
查看源码你会发现只有2个地方设置了 mBase,
[java]  view plain copy
  1. public ContextWrapper(Context base) {  
  2.     mBase = base;  
  3. }  
  4.   
  5. /** 
  6.  * Set the base context for this ContextWrapper.  All calls will then be 
  7.  * delegated to the base context.  Throws 
  8.  * IllegalStateException if a base context has already been set. 
  9.  *  
  10.  * @param base The new base context for this wrapper. 
  11.  */  
  12. protected void attachBaseContext(Context base) {  
  13.     if (mBase != null) {  
  14.         throw new IllegalStateException("Base context already set");  
  15.     }  
  16.     mBase = base;  
  17. }  
一是构造函数,一是 attachBaseContext 。
我们先来分析构造函数!Activity 继承自她,所以分析得 Activity 怎么创建的,由于 Activity 的创建过程比较复杂,而此篇重点不是他,所有我在这里直接给出结果,Activity 创建的时候调用的是无参数构造函数。所以 mBase 不是来自构造函数,那就是来自 attachBaseContext 了,下面我们来分析 Activity 在哪里调用了 attachBaseContext() 方法:
查阅 Activity 源码可以找到一处调用
[java]  view plain copy
  1. final void attach(Context context, ActivityThread aThread,  
  2.         Instrumentation instr, IBinder token, int ident,  
  3.         Application application, Intent intent, ActivityInfo info,  
  4.         CharSequence title, Activity parent, String id,  
  5.         NonConfigurationInstances lastNonConfigurationInstances,  
  6.         Configuration config) {  
  7.     attachBaseContext(context);  
就是 Activity.attach 方法,此方法在 framework 创建 Activity 的时候调用的,要知道这个 context 参数是什么,我们得从 framework 代码去看,
代码:android.app.ActivityThread 
[java]  view plain copy
  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {  
  2.       
  3.     Activity activity = null;  
  4.     try {  
  5.         java.lang.ClassLoader cl = r.packageInfo.getClassLoader();  
  6.         activity = mInstrumentation.newActivity(  
  7.                 cl, component.getClassName(), r.intent);             
  8.     } catch (Exception e) {  
  9.     }  
  10.   
  11.     try {  
  12.         if (activity != null) {  
  13.             ContextImpl appContext = new ContextImpl();  
  14.             appContext.init(r.packageInfo, r.token, this);  
  15.             appContext.setOuterContext(activity);  
  16.   
  17.             CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());  
  18.             Configuration config = new Configuration(mCompatConfiguration);  
  19.            
  20.             activity.attach(appContext, this, getInstrumentation(), r.token,  
  21.                     r.ident, app, r.intent, r.activityInfo, title, r.parent,  
  22.                     r.embeddedID, r.lastNonConfigurationInstances, config);  
看到了,context 就是此处的 appContext 他是 ContextImpl 对象,好了,现在我们知道 ContextImpl 才是真正的实现者了。
所以 getString() 的真正调用是 ContextImpl.getResource.getString() ,getString() 的真正实现在 Resource (android.content.res.Resources) 。

有哪几种 Context?
  1. Activity :刚刚我们已经分析
  2. Service :也是继承自 ContextWrapper 跟 Activity 一样
  3. BroadcastReceiver : onReceiver(Context context )
  4. Application:
记住所有 Context 真正实现都是 ContextImpl 
Activty、Service 的 Context 都是每次创建的,而不是全局唯一,所以不要将 Activity 、Service 当做全局 Context 引用,这样会导致 Activity 无法销毁,一直被引用者。

那么什么样的 Context 才能做全局引用呢?
答案是:Application ,如果你没有自定义的 Application 怎么获取这个 Context 呢,你会发现 Context 有一个  getApplicationContext() 方法,他在 ContextImpl 的实现如下:

android.app.ContextImpl
[java]  view plain copy
  1. @Override  
  2. public Context getApplicationContext() {  
  3.     return (mPackageInfo != null) ?  
  4.             mPackageInfo.getApplication() : mMainThread.getApplication();  
  5. }  

android.app.LoadedApk
[java]  view plain copy
  1. Application getApplication() {  
  2.     return mApplication;  
  3. }  

这个mApplication 对象就是程序的 Application 。所以你可以把他强转成你自定义的 Application 

BroadcastReceiver 的 Context
[java]  view plain copy
  1. Application app = packageInfo.makeApplication(false, mInstrumentation);  
  2.   
  3. if (localLOGV) Slog.v(  
  4.     TAG, "Performing receive of " + data.intent  
  5.     + ": app=" + app  
  6.     + ", appName=" + app.getPackageName()  
  7.     + ", pkg=" + packageInfo.getPackageName()  
  8.     + ", comp=" + data.intent.getComponent().toShortString()  
  9.     + ", dir=" + packageInfo.getAppDir());  
  10.   
  11. ContextImpl context = (ContextImpl)app.getBaseContext();  
  12. sCurrentBroadcastIntent.set(data.intent);  
  13. receiver.setPendingResult(data);  
  14. receiver.onReceive(context.getReceiverRestrictedContext(),  
  15.         data.intent);  

从代码得知,他是 Application 的 ContextImpl 对象,所以他是全局的

<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]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值