8.3 性能防线与静态检查


8.3 性能防线与静态检查

本节摘要:前七章的纪律如果只靠 review 维持,会随人员流动而稀释;最后一道防线是机器:StrictMode 在运行时揪出主线程违规,Detekt 在提交前拦截 !!、可变数据类、GlobalScope 等模式,协程测试用虚拟时间让异步逻辑变成确定性断言。本节配置这三道防线,并给出启动路径与 ANR 的治理要点,收束全书的"安全消除术"。

先把督察放出来:StrictMode

StrictMode 是安卓内置的"运行时督察",开发构建里配置后,主线程上的网络、磁盘读写、Activity 泄漏当场抛出或打日志:

class SafeApp : Application() { override fun onCreate() { if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectNetwork() // 主线程网络 .detectDiskReads().detectDiskWrites() // 主线程磁盘 .detectCustomSlowCalls() // 自定义慢调用 .penaltyDeathOnNetwork() // 主线程网络直接崩 开发期零容忍 .penaltyLog() // 其余打日志 .build() ) StrictMode.setVmPolicy( StrictMode.VmPolicy.Builder() .detectActivityLeaks() // Activity 泄漏 .detectLeakedClosableObjects() // 流与游标未关闭 .detectLeakedSqlLiteObjects() .penaltyLog() .build() ) } super.onCreate() } }

第 5 章说"Main 上超过 50 毫秒的活必须挪走",StrictMode 就是这条纪律的自动执行者——它抓的是第 5.3 节"三种死法"里最隐蔽的那种:不崩、只慢。VmPolicy 抓的泄漏(Cursor 没关、Closable 流悬空)则对应第 6 章 use 扩展的用武场景:inputStream.use { ... } 在块结束时自动关闭,泄漏类事故从"记得关"变成"结构保证关"。

Detekt:把前七章的纪律写成规则

Detekt 是 Kotlin 的静态分析器,它的价值在于把本册散落各章的工程纪律固化为 CI 门槛。配置示例(规则名对应正文里的纪律):

detekt { config.setFrom(files("config/detekt/detekt.yml")) buildUponDefaultConfig = true } dependencies { detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.6") }

yaml 里重点开启的规则,每条都能在正文里找到出处:

规则 拦截的内容 对应章节
UnsafeCallOnNullableType 可空类型上用双感叹号 第 2 章
GlobalCoroutineUsage 使用 GlobalScope 第 5 章
WaitInCoroutineWithinRunBlocking 协程里阻塞等待 第 5 章
MutableDataClass 数据类含 var 字段 第 3 章
DataClassContainsFunctions 数据类塞业务方法 第 3 章
SwallowedException 空 catch 吞异常 第 2 章
TooManyFunctions 单文件函数过多(工具类复发信号) 第 6 章
LongParameterList 构造参数失控 第 6 章
MagicNumber 魔法数字(未提为 dp 扩展或常量) 第 6 章

配套动作有二。基线化存量:detektBaseline 任务把存量问题记入基线文件,新代码零容忍、旧代码分批清——这延续了第 7 章渐进迁移的哲学。ktlint 管格式:代码风格(缩进、导入排序)交给 ktlint 与官方风格指南,与 Detekt 分工(格式与语义分层),格式问题在提交前被自动修复。

三道防线的分层视图

三道防线的分层视图

协程测试:虚拟时间里的确定性

异步代码的可测性是它能否被称为"工程"的试金石。kotlinx-coroutines-test 的 runTest 用虚拟时间把 delay 变成零耗时跳转:

class OrderHistoryVMTest { @Test fun `刷新失败降级到缓存并提示`() = runTest { val repo = FakeOrderRepo(failWith = java.io.IOException("断网")) val saved = SavedStateHandle(mapOf("last_orders" to listOf(testOrder()))) val vm = OrderHistoryVM(repo, saved) vm.state.test { // turbine 或 kotlinx 的 flow 测试工具 assertEquals(OrderHistoryVM.UiState.Loading, awaitItem()) val state = awaitItem() assertTrue(state is OrderHistoryVM.UiState.Data && state.orders.isNotEmpty()) cancelAndIgnoreRemainingEvents() } vm.events.test { val e = awaitItem() assertTrue(e is OrderHistoryVM.Event.Toast) } } } class FakeOrderRepo(private val failWith: Exception? = null) : OrderRepo { override suspend fun fetchOrders(): List<Order> { failWith?.let { throw it } return listOf(testOrder()) } }

三个关键件。runTest:测试体内 delay(2000) 立即跳过,超时逻辑(withTimeout)也能在虚拟时间里确定性地走到——真实等待的测试既慢又飘。TestDispatcher 注入:第 5 章说"调度器当依赖注入",兑现处在这里——ViewModel 的调度器换成 TestDispatcher 后,launch 的执行时机完全可控(advanceUntilIdle 让所有排队协程跑完),并发代码的断言从"碰运气"变成"排队执行"。Fake 而非 Mock:协程代码用接口加假实现(FakeOrderRepo)比字节码 mock 框架稳定得多——挂起函数的 mock 语义各家实现不一,假实现就是普通 Kotlin 类。

这套测试直接检验第 8.2 节模板的核心行为:失败降级、缓存读取、事件发射。跑一次几百毫秒,进 CI 每次提交都执行——护航端的日常。

启动路径与 ANR:运行时的最后战场

静态检查管不到的运行时问题,集中在启动与主线程耗时。启动瘦身三原则:Application 的 onCreate 只做必要初始化(树根、崩溃平台),重初始化(图片库、推送 SDK)挪到首帧后或用 androidx.startup 与懒加载(第 2 章的 by lazy 在这里的正确用法是"纯计算加确定线程");首页数据走"缓存快照先行、网络异步补"——8.2 的降级策略在启动场景的复用;启动期的同步反序列化(读大配置文件建对象图)是慢启动最大公约数,要么砍体积要么异步化。ANR 治理回到第 5 章的毫秒预算:StrictMode 抓违规、主线程方法采样(线上性能平台)找热点、锁竞争(主线程等后台锁)与广播超时是三大高发源——协程化改造后前两类通常显著下降,因为阻塞调用被 withContext 结构性地挪走了。

崩溃平台做最终验收:本册体系跑三个月后,NPE 占比应显著下滑、剩余集中在平台类型边界(回看第 7 章收容纪律);ANR 率随协程化推进下降;新增状态类 bug(穷举遗漏)应接近零——编译器替你拦了。数字是安全消除术的最终证词

收束:五根支柱的工程化形态

全册至此收束。第 1 章的骨架配好了防线默认值;第 2 到第 6 章立起五根支柱;第 7 章封住边界的缺口;本章把人的纪律换成机器的门槛。回头看这套体系的本质:Kotlin 没有让写代码变容易,它让写错代码变难——可空性要处理、分支要穷举、可变要签字、并发要归树、逻辑要归位。每一条约束在写下时都多花了十秒,在维护时省下的是十小时。当团队的新人第一次提交就被 Detekt 拦下 !!,当他看到新增密封子类后的全网标红,这套体系就完成了自我复制——安全消除术的最后一课是:防线自己会长脚

本节要点回顾

  • StrictMode 当运行时督察:开发构建抓主线程网络磁盘与泄漏,第 5 章毫秒预算的自动执行者;
  • Detekt 把纪律固化成 CI 门槛:UnsafeCallOnNullableType、GlobalCoroutineUsage、MutableDataClass 等规则逐条对应正文;存量基线化,新代码零容忍;
  • ktlint 分工格式:风格问题自动修复,与 Detekt 的语义检查分层;
  • 协程测试三件套:runTest 虚拟时间、TestDispatcher 注入、Fake 优先于 mock——异步断言的确定性;
  • 启动三原则:onCreate 最小化、缓存快照先行、砍启动期同步反序列化;
  • 崩溃平台是最终证词:NPE 集中到边界、ANR 随协程化下降、穷举遗漏归零——数字验证体系。

全册完。下一步的延伸方向:Jetpack Compose 把单向数据流推向声明式 UI(本章机制全部可迁移);Kotlin Multiplatform 把领域层带到 iOS 侧(第 1 章骨架的纯 Kotlin domain 层正是为此留的门)。带着"这个机制防住了什么"的问题继续读下去,你会比大多数人学得快。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U