尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Android随笔-Context

Android随笔-Context
📅 发布时间:2026/7/15 21:43:24

Android 中 Context 详解:类型、使用场景与限制

一、Context 是什么

Context 是 Android 系统的抽象接口,它提供了访问系统资源、启动组件、获取系统服务等核心能力。可以把它理解为**“应用环境的句柄”**——没有它,你就无法与 Android 系统交互。

publicabstractclassContext{// 获取资源publicabstractResourcesgetResources();// 启动 ActivitypublicabstractvoidstartActivity(Intentintent);// 获取系统服务publicabstractObjectgetSystemService(Stringname);// 获取 SharedPreferencespublicabstractSharedPreferencesgetSharedPreferences(Stringname,intmode);// ... 大量其他方法}

二、Context 的继承体系

Context(抽象类) │ ┌────────────────┼────────────────┐ │ │ │ ContextWrapper Application Service │ (getApplicationContext()) │ ┌────┴────┐ │ │ Activity ContextThemeWrapper │ AppCompatActivity

核心实现类

类型类名说明
Applicationandroid.app.Application全局单例,代表整个应用
Activityandroid.app.Activity代表一个界面,携带 UI 主题
Serviceandroid.app.Service后台服务组件
ContextWrapperandroid.content.ContextWrapper包装器,可代理其他 Context

三、四种 Context 详解

1. Application Context(应用上下文)

// 获取方式ContextappContext=getApplicationContext();// Activity/Service 中ContextappContext=context.getApplicationContext();// 任意 Context 中ContextappContext=MyApp.getInstance();// 自定义 Application 单例

自定义 Application 单例示例:

publicclassMyAppextendsApplication{privatestaticMyAppinstance;@OverridepublicvoidonCreate(){super.onCreate();instance=this;}publicstaticMyAppgetInstance(){returninstance;}}

特点:

  • 全局唯一,生命周期与 Application 一致(应用存活期间一直存在)
  • 不持有 Activity 引用,不会导致内存泄漏
  • 没有 UI 主题(无法用于创建 Dialog、启动带主题的 Activity)

适用场景:

场景原因
获取全局配置/资源getResources()、getString()
启动 ServicestartService()
发送广播sendBroadcast()
获取系统服务getSystemService()
操作数据库/Room生命周期长,不会随 Activity 销毁而关闭
操作 SharedPreferences同上
单例类中持有 Context避免内存泄漏,必须传 Application Context
图片加载库初始化Glide、Coil 等需要长生命周期 Context
网络请求库初始化Retrofit/OkHttp 配置中需要

不适用场景:

场景原因
创建 Dialog没有 UI 主题,会崩溃
启动 Activity(标准方式)没有 Activity 栈,需要FLAG_ACTIVITY_NEW_TASK
布局 Inflate(带主题的)没有主题,样式会丢失
显示 Toast虽然可以,但推荐用 Activity Context

2. Activity Context(界面上下文)

// 获取方式ContextactivityContext=this;// 在 Activity 中ContextactivityContext=requireContext();// 在 Fragment 中

特点:

  • 生命周期与 Activity 绑定,Activity 销毁后 Context 失效
  • 持有 Activity 引用,滥用会导致内存泄漏
  • 携带 UI 主题(ContextThemeWrapper继承而来)

适用场景:

场景原因
创建 Dialog/AlertDialog需要依附于 Activity 窗口
启动 Activity自动加入 Activity 返回栈
布局 Inflate需要 Activity 的主题解析属性
显示 Toast需要与当前界面关联
请求权限requestPermissions()需要 Activity
启动 ForResultstartActivityForResult()需要 Activity
视图操作View.getContext()通常就是 Activity Context

不适用场景:

场景原因
单例中持有Activity 销毁后单例仍持有引用 → 内存泄漏
异步回调中使用网络请求回调里用 Activity Context,用户退出后崩溃
后台任务中WorkManager、后台线程中不应持有 Activity Context

内存泄漏典型示例:

// ❌ 错误:单例持有 Activity ContextpublicclassSingletonManager{privatestaticContextcontext;publicstaticvoidinit(Contextctx){context=ctx;// 持有 Activity 引用!}}// 正确做法:传 Application ContextSingletonManager.init(getApplicationContext());

正确做法:

// ✅ 正确:单例持有 Application ContextpublicclassSingletonManager{privatestaticContextappContext;publicstaticvoidinit(Contextctx){appContext=ctx.getApplicationContext();// 永远用 Application}publicstaticContextgetContext(){returnappContext;}}

3. Service Context(服务上下文)

// 获取方式ContextserviceContext=this;// 在 Service 中

特点:

  • 生命周期与 Service 绑定
  • 没有 UI 主题(Service 本身无界面)
  • 可以启动 Activity(需加FLAG_ACTIVITY_NEW_TASK)

适用场景:

场景原因
后台播放音乐服务中控制 MediaPlayer
后台下载/上传服务中执行网络操作
启动前台服务startForeground()
发送通知NotificationManager需要 Context

不适用场景:

场景原因
创建 DialogService 没有窗口令牌,无法显示 Dialog
启动普通 Activity需要FLAG_ACTIVITY_NEW_TASK,否则崩溃
布局 Inflate(带主题)没有主题

4. ContextWrapper(包装上下文)

// 常见使用方式ContextWrapperwrapper=newContextWrapper(baseContext);ContextThemeWrapperthemedContext=newContextThemeWrapper(baseContext,R.style.MyTheme);

特点:

  • 代理模式,包装另一个 Context
  • 可修改主题、资源等
  • Application、Service都继承自ContextWrapper

适用场景:

场景原因
动态切换主题ContextThemeWrapper给 View 换主题
插件化/换肤代理 Resources 实现资源替换
多语言切换代理getResources()返回特定 Locale 资源

动态切换主题示例:

publicclassThemeHelper{publicstaticContextwrapContext(Contextcontext,intthemeResId){returnnewContextThemeWrapper(context,themeResId);}// 使用publicstaticvoidapplyTheme(Viewview,intthemeResId){ContextthemedContext=newContextThemeWrapper(view.getContext(),themeResId);LayoutInflaterinflater=LayoutInflater.from(themedContext);// 用 themedContext inflate 的布局会应用新主题}}

四、Context 使用场景对照表

操作ApplicationActivityService说明
startActivity()⚠️ 需 NEW_TASK✅⚠️ 需 NEW_TASKActivity 最自然
startService()✅✅✅都可以
bindService()✅✅✅都可以
sendBroadcast()✅✅✅都可以
registerReceiver()✅✅✅都可以
getResources()✅✅✅都可以
getSharedPreferences()✅✅✅都可以,但推荐 Application
getSystemService()✅✅✅都可以
createDeviceProtectedStorageContext()✅✅✅都可以
创建 Dialog❌✅❌只有 Activity 有窗口
布局 Inflate(带主题)❌✅❌只有 Activity 有主题
启动 ForResult❌✅❌只有 Activity 有回调
请求权限❌✅❌只有 Activity 有界面
显示 PopWindow❌✅❌需要 Activity 窗口

五、常见错误与最佳实践

错误 1:单例持有 Activity Context

// ❌ 错误publicclassBadManager{privatestaticContextcontext;publicstaticvoidinit(Contextctx){context=ctx;// 如果是 Activity,就泄漏了}}// ✅ 正确publicclassGoodManager{privatestaticContextappContext;publicstaticvoidinit(Contextctx){appContext=ctx.getApplicationContext();// 永远用 Application}}

错误 2:异步回调中使用 Activity Context

// ❌ 错误:网络请求回调中直接使用 ActivitypublicclassMainActivityextendsAppCompatActivity{privateTextViewtextView;privateApiServiceapi;publicvoidfetchData(){api.getData().enqueue(newCallback<Data>(){@OverridepublicvoidonResponse(Call<Data>call,Response<Data>response){// 用户可能已经退出 Activity,这里操作 UI 崩溃或泄漏textView.setText(response.body().toString());}@OverridepublicvoidonFailure(Call<Data>call,Throwablet){// 同样可能崩溃}});}}// ✅ 正确:使用 Application Context 或判空publicclassMainActivityextendsAppCompatActivity{privateTextViewtextView;privateApiServiceapi;publicvoidfetchData(){api.getData().enqueue(newCallback<Data>(){@OverridepublicvoidonResponse(Call<Data>call,Response<Data>response){// 方式1:使用 Application Context 做非 UI 操作saveToDB(getApplicationContext(),response.body());// 方式2:判空后再操作 UIif(!isDestroyed()&&!isFinishing()){textView.setText(response.body().toString());}}@OverridepublicvoidonFailure(Call<Data>call,Throwablet){if(!isDestroyed()&&!isFinishing()){Toast.makeText(MainActivity.this,"请求失败",Toast.LENGTH_SHORT).show();}}});}privatevoidsaveToDB(Contextcontext,Datadata){// 使用 Application Context 操作数据库}}

错误 3:Application Context 创建 Dialog

// ❌ 崩溃:android.view.WindowManager$BadTokenExceptionAlertDialog.Builderbuilder=newAlertDialog.Builder(getApplicationContext());AlertDialogdialog=builder.create();dialog.show();// ✅ 正确:使用 Activity ContextAlertDialog.Builderbuilder=newAlertDialog.Builder(MainActivity.this);AlertDialogdialog=builder.create();dialog.show();

错误 4:Service 中启动 Activity 不加 FLAG

// ❌ 崩溃:Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flagIntentintent=newIntent(this,MainActivity.class);startActivity(intent);// ✅ 正确Intentintent=newIntent(this,MainActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);

错误 5:在 BroadcastReceiver 中启动 Dialog

// ❌ 错误:BroadcastReceiver 没有窗口publicclassMyReceiverextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){// 这里 context 是 Application Context 或 ReceiverRestrictedContextAlertDialog.Builderbuilder=newAlertDialog.Builder(context);builder.show();// 崩溃!}}// ✅ 正确:启动一个 Activity 来显示 DialogpublicclassMyReceiverextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){IntentactivityIntent=newIntent(context,DialogActivity.class);activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(activityIntent);}}

六、Context 数量关系

一个 App 中: Application Context = 1 个(全局唯一) Activity Context = N 个(有几个 Activity 实例就有几个) Service Context = M 个(有几个 Service 实例就有几个) 总 Context 数 = 1 + N + M

七、为什么创建 Dialog 不能使用 Application Context

核心原因:Dialog 必须依附于一个窗口(Window),而 Application Context 没有窗口。

Dialog 的创建流程

当你调用Dialog.show()时,内部发生了这些:

// Dialog.java 简化版publicvoidshow(){// 1. 创建 WindowmWindow=newPhoneWindow(mContext);// 需要 Context 来创建窗口// 2. 获取 WindowManagermWindowManager=(WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);// 3. 把 Dialog 的 DecorView 添加到 WindowmWindowManager.addView(mDecor,params);// 这里需要 WindowToken}

关键点在第 3 步:WindowManager.addView()需要一个WindowToken。

WindowToken 是什么

WindowToken 是窗口的"身份证",Android 窗口管理系统(WMS)用它来识别和管控窗口。

窗口层级关系: PhoneWindow(Activity 的根窗口) ↓ WindowToken(由 AMS 分配,绑定到 Activity 的 token) ↓ Dialog 的窗口(子窗口,必须挂在某个父窗口下)

Dialog 是子窗口(Sub-Window),它必须挂载在一个父窗口(通常是 Activity 的 PhoneWindow)下面。这个挂载关系靠WindowToken维系。

Application Context 为什么不行

Application 没有 WindowToken

// Application 继承关系:Application→ContextWrapper→Context// Activity 继承关系:Activity→ContextThemeWrapper→ContextWrapper→Context↑ 这里多了ContextThemeWrapper,携带了窗口信息

Activity 在创建时,AMS 会分配一个IBinder token给它,这个 token 就是 WindowToken 的来源:

// ActivityThread.javafinalActivityClientRecordr=newActivityClientRecord();r.token=token;// 来自 AMS 的 WindowToken

Application 没有参与 Activity 的启动流程,AMS 不会给它分配 token。

报错现场:

当你用 Application Context 创建 Dialog 并show():

// 崩溃日志:android.view.WindowManager$BadTokenException:Unabletoaddwindow--tokennullis not valid;is your activity running?

token null指的就是 WindowToken 为 null。

Activity Context 为什么可以

Activity 的mToken在attach()方法中被设置:

// Activity.javafinalvoidattach(Contextcontext,ActivityThreadaThread,Instrumentationinstr,IBindertoken,// ← 这个 token 就是 WindowTokenApplicationapplication,Intentintent,...){mWindow=newPhoneWindow(this);mWindow.setWindowManager(...,token,...);// 把 token 传给 WindowManagermToken=token;}

所以当你用 Activity Context 创建 Dialog:

Dialogdialog=newDialog(activity);// activity 的 mToken 有值dialog.show();

Dialog 内部会复用 Activity 的 WindowToken,把自己作为子窗口挂载到 Activity 的窗口树下。

如果非要用 Application Context 显示弹窗怎么办

方案 1:用 System Alert 窗口(需要权限)

// AndroidManifest.xml 中声明权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>// 代码Dialogdialog=newDialog(getApplicationContext());dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);dialog.show();

这种窗口不依附于任何 Activity,是系统级浮窗(类似微信视频通话的小窗)。

方案 2:用 Toast(本身就是系统窗口)

Toast.makeText(getApplicationContext(),"提示",Toast.LENGTH_SHORT).show();

Toast 使用系统窗口(TYPE_TOAST),不需要 Activity Token。

方案 3:启动一个透明的 Activity

// 用 Application Context 启动一个透明主题的 Activity,再在里面显示 DialogIntentintent=newIntent(getApplicationContext(),TransparentDialogActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);getApplicationContext().startActivity(intent);

总结

问题答案
Dialog 为什么需要窗口因为 Dialog 是子窗口,必须挂载到父窗口下
父窗口怎么识别靠 WindowToken(AMS 分配的 IBinder)
Activity 有没有 WindowToken有,AMS 在启动 Activity 时分配
Application 有没有 WindowToken没有,Application 不参与窗口管理
不用 Activity 怎么显示弹窗System Alert 窗口、Toast、透明 Activity

一句话:Dialog 是"窗中窗",Application 不是窗,所以挂不上。


八、总结

类型生命周期有 UI 主题主要用途防泄漏
Application应用存活期间❌全局操作、单例、数据库最安全
Activity界面存活期间✅UI 相关、Dialog、跳转最易泄漏
Service服务存活期间❌后台任务、播放、下载较安全
ContextWrapper取决于被包装者可配置主题切换、插件化取决于内部

核心原则:

能用 Application Context 的地方,绝不用 Activity Context。
必须 UI 操作的地方,确认生命周期后再用 Activity Context。
单例、异步、后台任务,一律传 Application Context。

相关新闻

  • 巴西全年重点行业展会汇总
  • RFID固定资产管理系统:技术原理与部署实
  • 【MATLAB进阶绘图第1棒】三Y轴图实战:从数据到出版级图表的美学定制

最新新闻

  • 代码可对话:基于LangChain+Chroma的RAG实践指南
  • 旧衣上门回收是真的吗?旧衣上门回收是坑还是真香?爱宝拉实测揭秘真相! - 快递物流资讯
  • 北京急出手邮票纪念币工艺品不用久等!5 家全域快速上门实体商行推荐 - 深鉴新闻
  • Rasa Action Server异步实践:从async/await到任务队列
  • Android随笔-更新UI必须在主线程吗?
  • Rust 异步编程中的取消安全性:CancellationToken 与 select! 的正确使用方法

日新闻

  • 告别启动盘残留:用Diskpart彻底清除U盘EFI分区与恢复完整空间
  • 2026 年宜春诚信的塑料缠绕膜厂家哪个好,缠绕膜背后的秘密:你不知道的成本陷阱 - 领域鉴赏官
  • Arch ECS 入门指南:10分钟掌握C#高性能数据驱动架构

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号