☰
Android Context 全面剖析:理解生命周期与避免内存泄漏
2026/10/29 18:50:56 网站建设 项目流程

把context-mode单独拎出来当标题的时候,我第一反应不是某个开源库或配置项,而是 Android 日常开发里最容易被低估的Context对象。做了这么多年应用层开发,几乎每一个内存泄漏、崩溃、主题错乱的问题,追根到底都能落在“Context 到底是什么、我该传哪个 Context、为什么不能乱存 Context”这三件事上。这篇文章就把围绕 Context 的这套模式彻底拆开,从系统源码里的装饰器结构,到实际项目里的泄漏事故,再到我平时排查问题的完整链路,一次性讲清楚。适合刚接触 Android 的开发,也适合被 Context 相关泄漏坑过的中级工程师,读完之后你再传 Context 会下意识多看一眼生命周期。

1. Context 家族的内部构造:抽象类、包装类和真正干活的实现类

1.1 先回答最基础的问题:Context 到底是一个什么东西

Context在 Android 里是一个抽象类,类名直译就是“上下文”。它提供的是一整套访问系统资源和能力的入口:启动Activity、获取SharedPreferences、读取res/xml、拿到PackageManager、访问文件目录、注册广播接收器,统统要走它。按理说一个对象承担这么多职责,本身就该被认真对待,但实际开发里大多数人对它的理解停留在“哪个方法需要就传哪个”的阶段。

从继承关系看,Context的直接已知子类有两个:ContextWrapper和ContextImpl。其中ContextImpl才是真正干活的实现类,ContextWrapper像一个透明的包装壳,把ContextImpl包起来,默认情况下一层一层透传调用。我们平时写的Activity、Service、Application都继承自ContextWrapper,而不是直接继承Context。

Activity的继承链是Activity -> ContextThemeWrapper -> ContextWrapper -> Context,Service和Application则是ContextWrapper的子类。为什么Activity中间多了个ContextThemeWrapper?因为Activity要挂载 UI,需要主题资源,而ContextThemeWrapper专门提供了setTheme()、getTheme()、obtainStyledAttributes()这一套和主题相关的实现。这是第一个容易忽略的点:Application没有主题概念,你在Application里怎么都拿不到Theme,所以任何需要自定义主题弹窗的地方都必须往Activity的 Context 上靠。

1.2 ContextImpl 才是幕后真正干活的类

ContextWrapper不管套多少层,最终mBase都会指向某个ContextImpl实例。ContextImpl持有的是进程级别的资源信息:ApplicationInfo、ActivityThread、LoadedApk、Resources、PackageManager、SharedPreferences底层实现等等。你可以通过ContextWrapper.getBaseContext()一级一级往内层拿,但正常情况下不需要也不应该去拿mBase做操作。

ContextImpl不是简单地new一下就能创建的,它由LoadedApk在启动Activity、Service、或者初始化Application的时候通过createContext来构建。创建时会传入不同的参数,导致不同来源的ContextImpl行为有细微差异:

  • Activity的ContextImpl带activityToken,也就是ActivityRecord对应的 token,这个 token 是和 WindowManager 通信的关键标识。没有它,你就没法往窗口上挂 View,也拿不到有效的窗口 token。
  • Service和Application的ContextImpl没有activityToken。
  • Activity的ContextImpl还关联了classLoader、mainThread等,保证组件运行在正确的线程。

我在带新人的时候经常用一句话概括:Context的每个实例都代表“这个组件在系统中注册过的一份身份证明”,Activity 的身份证明里额外带了一张“窗口通行证”。你如果拿着Application的身份证明去弹Dialog,系统不认,因为没有窗口 token;拿着过期的 Activity 身份证明去执行耗时任务,系统不回收它,内存就泄漏了。

1.3 Application 对象的另一个身份:全局单例

说到Application,它本质上就是一个ContextWrapper,而且它的生命周期和进程完全一致。进程启动时创建,进程被杀时销毁,在绝大多数情况下进程内只有一个实例。所以它天然是全局单例,适合被长生命周期组件持有。

但这里有个容易搞混的点:Application的getApplicationContext()返回的其实也是同一个Application实例。而Activity里调用getApplication()和getApplicationContext(),前者返回Application,后者返回的同样是一个ContextImpl包装出来的Application对象,两者在引用关系上通常一致,只是 API 层面一个是Application类型、一个是Context类型。项目中需要全局单例时,直接用getApplicationContext()就行,不用费劲在工具类里传Application对象。

2. ContextWrapper 装饰器模式:这个“包装外壳”为什么值得理解

2.1 装饰器模式在 Android 里的具体投影

ContextWrapper的源码特别简单,它把Context的所有方法都包了一层透传:

public class ContextWrapper extends Context { Context mBase; public ContextWrapper(Context base) { mBase = base; } protected void attachBaseContext(Context newBase) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = newBase; } @Override public Resources getResources() { return mBase.getResources(); } @Override public Object getSystemService(String name) { return mBase.getSystemService(name); } // ... }

attachBaseContext()只能调用一次,如果重复调用会直接抛IllegalStateException。这个限制在实际开发中经常导致问题,后面我会提到。ContextWrapper的所有默认行为都是转发给mBase,但它允许子类对任意方法进行 override,这正是装饰器模式的标准姿势。

如果看不懂抽象的设计模式概念,可以类比成快递包装:ContextImpl是货物本身,ContextWrapper是贴着“易碎”标签的箱子。货还是那个货,但加了箱子之后你可以额外控制运输方式、放防震泡沫、甚至临时改派送路线。你拿到手的是箱子,但真正起作用的是箱子里的货物。

2.2 自定义 ContextWrapper 的实战用途

装饰器模式的价值在于“不改原有实现,就能在调用链上做手脚”。Android 内部大量依赖这个机制,最常见的三个应用场景:

  • 多语言切换方案
  • 自定义资源或动态字体
  • 系统能力的 mock 与测试

多语言是目前项目里比较刚需的场景。系统Resources在Configuration改变时更新,如果你的 App 没有跟随系统语言,而是自己维护一套语言设置,就需要在Activity.attachBaseContext()里包一层自定义ContextWrapper,覆盖getResources()返回指定语言下的Resources:

class LocalContextWrapper(base: Context) : ContextWrapper(base) { override fun getResources(): Resources { val config = Configuration(base.resources.configuration) config.setLocale(AppLanguageManager.currentLocale()) return base.createConfigurationContext(config).resources } } class MainActivity : AppCompatActivity() { override fun attachBaseContext(newBase: Context) { super.attachBaseContext(LocalContextWrapper(newBase)) } }

很多网上的文章到这里就结束了,但实际项目里真正的坑不在Activity,而在Application的初始化阶段。attachBaseContext()是在Application的构造函数之后、Activity启动之前调用的,如果语言 Manager 本身依赖Context或者资源文件,就会形成鸡生蛋的问题。我自己踩过:AppLanguageManager在初始化时要读res/xml里的语言配置,而语言又需要从SharedPreferences读,结果在Application.attachBaseContext()里还没法完整初始化。解决办法是把语言配置拆成不依赖 Context 的轻量模块,或者提前在ActivityThread创建 application 实例、调用attachBaseContext之后再初始化全局单例。

2.3 覆写 getSystemService 时最容易犯的错

自定义ContextWrapper还有一种常见用法:替换LayoutInflater实现全局字体。比如让整个 App 的所有 TextView 默认使用自定义字体,可以覆写getSystemService():

override fun getSystemService(name: String): Any? { if (Context.LAYOUT_INFLATER_SERVICE == name) { val inflater = super.getSystemService(name) as LayoutInflater return inflater.cloneInContext(this) } return super.getSystemService(name) }

这里有两个关键点,不处理干净就会出现“明明改了却有些页面没生效”的情况。

第一,LayoutInflater必须用cloneInContext(this),否则inflate出来的 View 拿到的 Context 引用还是原来的,你的覆写根本不会生效。cloneInContext()会创建一个新的PhoneLayoutInflater,并把内部mContext替换为当前 wrapper。

第二,覆写返回的LayoutInflater会被反复调用,不要每次getSystemService都new一个新的LayoutInflater。虽然 inflater 本身不重,但高频布局时频繁创建有害无益。更好的做法是在 wrapper 内部缓存一个实例。

2.4 系统 ContextWrapper 的内嵌关联

ContextWrapper不仅仅出现在应用层。系统服务ContextImpl内部也大量用到包装类,比如ContextThemeWrapper、SystemServiceRegistry返回的各种 manager,本质上都是围绕“Context 提供能力”这一设计的延伸。在 hprof 堆转储文件里,你能看到一层一层的 wrapper 链,ContextWrapper的mBase字段指向内层 context,ContextImpl的mOuterContext又指回外层组件。这两个字段是后来分析内存泄漏时最重要的线索之一。

3. 常见 context-mode 泄漏事故:Activity 被静态引用、Handler 回调、Dialog 的窗口

3.1 先明确一个原则:Context 的使用周期不能超过组件生命周期

绝大多数 Context 泄漏的本质只有一句话:长命的东西拿了短命的引用。Application的 Context 是长命对象,它想拿谁就拿谁,没问题;Activity的 Context 是短命对象,一旦它被长命对象持有,销毁后无法回收,就泄漏了。

拿静态引用来说,这是最粗暴的泄漏方式:

object ImageLoader { private var context: Context? = null fun init(context: Context) { this.context = context } } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ImageLoader.init(this) } }

如果init()被传入Activity,等到MainActivity销毁后,静态的ImageLoader.context还指向它,这个 Activity 连同它整个View树、Window、以及关联的Bitmap全部滞留堆里。你在 Memory Profiler 里会看到这个 Activity 实例的引用路径顶端是静态字段。

这种问题的修复思路很简单:单例工具类只允许使用getApplicationContext()。ImageLoader.init(this.applicationContext)即可。但我见过很多历史项目,初始化代码写死在Activity里,传的就是this,重构时只改调用处不改接收方,泄漏依旧。所以正确的做法是接收方内部做一层兜底:

fun init(context: Context) { this.context = context.applicationContext }

只要在入口处把传入的 Context 强制转成applicationContext,即使外部传了 Activity 也不会被长引。

3.2 Handler、View.postDelayed 带来的隐性持有链

Handler导致的泄漏非常经典。Kotlin 写法里大家很喜欢写:

val handler = object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { // ... } }

匿名内部类会隐式持有外部Activity的引用。如果发送了一条延时消息,消息在MessageQueue里排队,而handler又被Looper持有着,那么Activity在延时消息触发前就无法回收。解决方式不外乎几种:

  • onDestroy里handler.removeCallbacksAndMessages(null)
  • 使用Handler静态内部类 +WeakReference<Activity>
  • 改用 Lifecycle 感知组件

我推荐第二个方案的同时要提醒:WeakReference不是万能药。如果handler被延迟消息引用,WeakReference指向的Activity虽然可以被回收,但消息回调执行时可能已经拿到一个即将死亡的 Activity,轻则空指针,重则页面状态错乱。更贴近工程实践的写法是在组件销毁时主动移除所有待处理消息,再配合弱引用兜底。

View.postDelayed()是另一个隐蔽入口。View本身持有它所属 context 的引用(View.mContext),如果你在Activity销毁时没有移除Runnable,这个 Runnable 又持有了外部类引用,泄漏链会变成MessageQueue -> Runnable -> 匿名类 -> Activity。很多页面“退出后内存不回收”的案子都是这个原因。

3.3 Dialog 和 Toast 为什么特别容易泄漏

Dialog是典型的“必须用 Activity Context”的场景。创建AlertDialog时如果你传了Application的 context,本身不会崩溃,但真正show()的时候系统要拿 window token,而Application没有 token,于是抛BadTokenException。所以大家通常会老老实实传Activity。

问题出在:Dialog是存在Activity对象里的吗?实际上不一定。Dialog持有Context引用,Dialog的Window又持有View树,View又持有Context,形成一条往返链。如果某个Dialog被放入单例或者静态集合(比如为了“全局控制弹窗”)而没有在页面销毁时关闭,Activity 就跟着泄漏了。

正确做法是:不要在单例里长期持有Dialog对象。需要控制全局弹窗时,用Activity生命周期回调配合判断,页面唤起弹窗、页面销毁立即 dismiss。

Toast也值得提一句。Toast本身依赖系统服务NotificationManager,你用Applicationcontext 也能弹,所以很多人随手传applicationContext,这没有错。但如果传入的是Activity context,Toast对象虽然不会长期持用,部分系统实现仍会短暂保留,唯一影响可以忽略。真正要注意的是自定义Toast里的View,它内部持有创建它的 Context,仍然不能传易回收的组件实例。

3.4 静态 View 保存与 Fragment 引用陷阱

还有一类事故,代码里为了缓存“同一个 View”做动画或性能优化,把 View 塞到静态变量里。View 的mContext指向创建它的 Activity,所以静态 View 一保存,Activity 就锁死了。这个问题在列表 item 缓存、自定义 Layout 预加载里经常出现,自查方式很简单:搜索companion object/static修饰的View、Context、Dialog、LayoutInflater字段,基本一眼就能扫出来。

Fragment也有类似的坑。Fragment获取到的 Activity Context 如果被保存在成员变量,而且 Fragment 所在的 Activity 重建时,旧的 Fragment 已经销毁但它的mHost会通过FragmentManager保留一段时间。单例里的 Fragment 引用同样会导致整棵 Activity 树泄漏。我的经验是:Fragment 持有 Context 的成员变量能少则少,需要时直接从requireContext()拿瞬时对象,绝不放进属性缓存。

4. 不同场景下该选哪一种 Context:一份长期有效的选择表

4.1 四类 Context 生命周期对照

Android 里你能接触到的 Context 无非四种:Application、Activity、Service、ContentProvider内部获取到的 context。它们能力各有边界,生命周期也不一样。

Context 类型生命周期启动 Activity创建 Dialog加载主题资源注册广播接收器
Application进程存活期间可(需加FLAG_ACTIVITY_NEW_TASK)不可(无 token)可以,但无主题可以
ActivityActivity 销毁即失效正常可以可以,带主题可以,但需在onDestroy注销
Service服务停止即失效可(需加 FLAG)不可可以可以
ContentProvider的 context进程级,通常等同 Application需加 FLAG不可可以,无主题可以

读这张表时不要只看“能不能”,要意识到“该不该”。比如Application启动 Activity 可以加FLAG_ACTIVITY_NEW_TASK,但非用户直接行为下从后台拉起 Activity 会被系统限制,这不是 Context 的问题,而是应用启动后台限制的问题。所以即使技术上可行,业务上也不推荐。

4.2 这几个典型场景的实测结论

我自己在项目里把 Context 选择总结成几个固定套路,几乎不用动脑就能避开坑:

  • 需要一个单例的LayoutInflater或Resources:用applicationContext
  • 需要AlertDialog、PopupWindow、Toast自定义 View:用Activity
  • 需要启动一个Activity:用当前Activity,如果只能拿到applicationContext,务必加FLAG_ACTIVITY_NEW_TASK
  • 需要访问SharedPreferences、getFilesDir()、getCacheDir():applicationContext和Activity没有本质区别,统一用应用级
  • 需要数据库或网络层:永远只持有applicationContext,禁止从请求回调里传 Activity 向下层传

这里有一个需要特别留意的细节:getFilesDir()用 Application 和 Activity 拿的到底一不一样?在绝大多数设备上,同一应用内ContextImpl的filesDir是一样的。但如果你在一个ContextWrapper里覆写了getFilesDir(),情况就变了。这也是为什么我不建议“文件目录”这个场景混用不同 Context 去拼路径的原因。生产环境我见过因为ContextWrapper和原生 Context 交替使用导致SharedPreferences加载路径不一致的诡异 bug,排查成本非常高。

4.3 降级与升级:Context 引用的单向规则

工程上我习惯把 Context 传递看成一种“降级链”:Activity是最上层,可以向下传给任何对象,但只在它活着的时候有效;applicationContext是最底层,适合被任何长生命周期对象持有,却没法提供 UI 能力。

所谓“降级”是指:如果一个对象只需要资源、数据库、网络、SharedPreferences,那么不管调用方手里是 Activity 还是 Service,全部收编成applicationContext。这不仅防泄漏,还让工具类可以被任意组件调用,不依赖具体页面。

如果反过来,一个对象明确要展示 UI,那就不能降级,必须持有Activity引用。但这种情况下要用生命周期感知的方式管理,比如:

  • 用LifecycleOwner接口传值,而不是直接塞Activity
  • 把 UI 操作封装在onResume/onDestroy中执行
  • 用LifecycleScope或协程里的repeatOnLifecycle处理异步回调后的 UI 刷新

每次写代码前问一句:“这个 Context 是会被短命组件持有,还是会被长命组件持有?”答案就出来了。

5. 从“context-mode”看上下文管理的工程化套路:生命周期感知与引用降级

5.1 用 registerActivityLifecycleCallbacks 统计 Context 引用

很多团队在落地 Context 规范时,第一步是在Application里挂一个全局的ActivityLifecycleCallbacks,把当前前台 Activity 缓存下来,用于全局弹窗提醒、崩溃后重启之类的场景。这个思路本身没问题,但实现时容易埋雷。

如果你这样写:

class App : Application() { var currentActivity: Activity? = null private set override fun onCreate() { super.onCreate() registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityResumed(activity: Activity) { currentActivity = activity } override fun onActivityDestroyed(activity: Activity) { // 没有在这里把 currentActivity 置空? } }) } }

一旦onActivityDestroyed没有置空,这个currentActivity就会成为长期持有的泄漏源。看似简单的全局 Activity 记录,做不好就是另一个泄漏大户。我的建议是:

  • 记录一个WeakReference<Activity>,同时维护一个当前 resumed 的状态标志
  • onActivityDestroyed里比较引用,是同一个实例就清空
  • 不要单独写“当前 Activity”这种全局变量,能改成onPageShow回调或ViewModel尽量改

另外,通过registerActivityLifecycleCallbacks还可以做一个开发期监控:在 Release 包不开启,Debug 包每次onActivityDestroyed后,利用Handler.postDelayed在下一个主线程空闲时手动触发一次 GC,再检查当前 Activity 数量是否符合预期。配上 LeakCanary,Context 泄漏的发现时间能从线上用户反馈缩短到开发自测阶段,这个投入非常值。

5.2 Context 降级的边界:哪些能力注定无法降级

前文提到降级到applicationContext,但要划清楚边界。无法降级的能力包括:

  • 创建与窗口相关的 UI:Dialog、PopupWindow、View附加到窗口
  • 读取当前页面的主题属性:Activity的Theme和Application的Theme不同,自定义控件里obtainStyledAttributes尽量使用当前页面的 context
  • 获取LayoutInflater中与页面相关的实例:虽然可以用applicationContext创建 inflater,但 inflate 出来的 View 会丢失 Activity 的 theme 信息
  • Activity启动时的intent跳转结果:除非用FLAG_ACTIVITY_NEW_TASK,否则没有 Activity 上下文无法在任务栈中正常启动

有降级能力的包括:

  • 数据库、网络、存储目录、SharedPreferences
  • PackageManager、NotificationManager、AlarmManager等系统服务
  • 大部分ContentResolver操作

把这些边界写进代码规范,比任何口口相传都靠谱。我见过项目里一个新同事为了“避免 Activity 泄漏”,把所有 Dialog 都改成applicationContext创建,结果 Dialog 不能 show,这个教训说明“一刀切降级”是错的。降级的前提是确认调用链完全不需要 UI token。

5.3 不让 Activity Context 进入 ViewModel 和网络层

架构上,ViewModel不应该持有Context,这是 Android 官方反复强调的。原因是 ViewModel 的生命周期大于 Activity/ Fragment,它会在配置变更后继续存活。如果不小心在AndroidViewModel里传了 Activity,而这个 Activity 已经销毁,ViewModel 还持有旧引用,泄漏就形成了。

但实际项目里不可能完全不碰 Context,比如ViewModel要做资源格式化、要读 app 配置,怎么办?两条路:

  • 使用AndroidViewModel,它内部有Application的引用,可以安全拿到应用级 Context
  • 在ViewModel里通过SavedStateHandle或资源 id 传递数据,由 View 层负责解析

网络层和仓库层同理,永远只接收applicationContext或者在初始化时传一次成功后就地缓存。这样各层生命周期边界清晰,Context 引用就不容易出现“越级”持有。

6. 排查 Context 泄漏的实操链路:从 LeakCanary 提示到 hprof 确认

6.1 先判断是“疑似泄漏”还是“真泄漏”

不是所有内存不回收都叫泄漏。页面退出后 Activity 实例短时间内还在内存里,可能是系统缓存,也可能是动画还没结束、异步任务还在收尾。所以排查的第一步是确认是否真的发生了 Activity 实例滞留:

打开 Android Studio 的 Memory Profiler,进入目标页面再退出,然后对堆转储,选择分析器里搜索Activity类,看目标 Activity 的实例数。如果退出后实例数只增不减,或者恢复到之前页面依然有 2~3 个相同实例,基本可以确认有引用未被释放。

另一种做法是写一段开发期埋点:

class DebugActivityWatcher : ActivityLifecycleCallbacks { private val destroyedActivities = mutableSetOf<String>() override fun onActivityDestroyed(activity: Activity) { destroyedActivities.add(activity.javaClass.name) checkLeakedActivities() } private fun checkLeakedActivities() { // 延迟一个心跳时间后触发 GC,并再次统计 handler.postDelayed({ System.gc() // 输出全类名列表,供日志比对 }, 1000) } }

这个方法比较土,但非常好用,能在不依赖工具的情况下快速把泄漏范围圈到具体页面。

6.2 LeakCanary 的集成与结果解读

LeakCanary 是目前最省心的方案,接入成本低,Debug 构建自动开启,检测到明显泄漏会生成通知和堆转储。它的原理就是在 Activity / Fragment 销毁时用ObjectWatcher观察对象,如果一段时间后对象没有被 GC 回收,就认为发生泄漏,然后自动转储 hprof 并分析引用链。

集成之后有个关键动作:如果项目里有大量历史遗留泄漏,先把 LeakCanary 的报告收进一个系统,分批修复,而不是一次全改。我一般建议按引用链里的“最短强引用路径”去修,路径越短,越容易定位到罪魁祸首。比如典型的MainActivity -> this$0 -> ImageLoader.context这样的引用链,一眼就看出是工具类持有。

6.3 hprof 分析的本质:跟着引用链找根

如果 LeakCanary 没有捕获,或者想自己深挖,就得学会手动分析 hprof。流程如下:

  1. 生成 hprof:Memory Profiler 里 Dump Java Heap,或用am dumpheap命令
  2. 转换格式:Android 的 hprof 不是标准 JVM hprof,用 Android Studio 自带 analyzer,或通过hprof-conv转成标准格式
  3. 打开 MAT(Eclipse Memory Analyzer)或 IntelliJ 的分析器,导入转换后的文件
  4. 在 Dominator Tree 里找目标 Activity 类,右键Path to GC Roots,排除弱引用和软引用,剩下的强引用链就是泄漏路径

实操中你会发现,很多引用链最终都指向MainThread或Looper,因为 Handler 消息、FrameCallback、系统回调都挂在主线程上。这时要重点看中间经过的字段名:class MainActivity的某个非静态内部类、静态字段、单例对象,字段名基本预示了代码里哪个持有了 Context。

6.4 我处理过的两类“LeakCanary 误报”和真实修复案例

LeakCanary 偶尔也会给出看似“误报”的结果。最常见两类:

  • Fragment因 View 未及时销毁而报 Activity 泄漏,实际是 View 还在执行转场动画,稍等后会自动回收
  • 系统输入法、剪贴板服务、TextClassificationManager等系统服务短暂持有 Activity,等待系统回收即可

处理原则:先看引用链里有没有自己代码的类名,没有的话大部分是系统缓存,可以忽略或升级到最新版排查;有自己代码类名的,无一例外都是真问题。

举个例子,我之前维护的一个老项目,每次从详情页返回后内存都上涨 20~30MB。LeakCanary 提示三个页面同时泄漏,引用链指向一个叫PageRecordManager的单例。打开代码发现,PageRecordManager为了统计“用户从哪个页面跳过来”,在onCreate里把每个 Activity 都加入了一个ArrayList<Activity>,但清空时机只在onDestroy的分支条件下执行,有些正常返回没走对应分支。修法很简单:把集合改成存页面名,不存 Activity 实例;同时onDestroy无条件移除。修改后内存直接回落。

所以我的经验是:Context 泄漏往往不是单个字段的问题,而是设计时就允许长生命周期组件和短生命周期组件混存了。修字段是治标,把“不持有 Activity 引用”写进架构规范才是治本。

6.5 最后的自检习惯

项目里我推行过一道“自杀式”检查:每个模块合并代码前,检索一遍是否有static修饰的Context、Activity、View、Dialog、LayoutInflater字段,以及单例对象构造器里有没有直接存 Activity。这个习惯花不了多少时间,但把大量泄漏挡在了上线之前。再配上进程到进程的监控,Context 这类基础问题基本可以不依靠线上反馈就能提前暴露。

关于context-mode,我最终的体会是:它从来不是要你背一个模式,而是要你建立“Context 是有生命周期的资源”这一底层认知。拿 Activity 当全局变量用,迟早要还;拿 Application 去撑 UI,当场就崩。每次传 Context 时多问一句容不容易被回收,多看一眼对象的存活期,很多崩溃和线上问题都能提前消掉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询