做了几年安卓开发,这些坑我帮你踩过了
大家好,我是 badhope。
安卓开发有个特点------入门简单,精通难。 难的不是语法,不是API,而是一台真机在手、万千碎片化机型等着给你"惊喜"的无力感。
这篇文章不谈高大上的架构设计,就聊聊我这些年踩过的、让人半夜惊醒的坑。
坑一:Context 泄漏------我写过最冤的 bug
刚入行的时候,我在一个单例里直接传了 Activity 的 Context:
kotlin
class SomeManager {
companion object {
var sContext: Context? = null
}
}
// 在Activity里
SomeManager.sContext = this
代码跑起来没啥问题,但每次退出这个页面再进去,内存就涨一点。直到有一天 OOM 了,我才意识到问题在哪------Activity 销毁了,但单例还拿着它的引用,GC 回收不了。
后来学乖了:需要长生命周期的对象,一律用 context.applicationContext。再用 LeakCanary 跑一遍,内存泄漏一目了然。
教训:别把 Activity 当传家宝,该放手时就放手。
坑二:主线程卡了5秒,ANR找上门
有一次我上线了一个新功能,结果第二天收到几十条 ANR 报告。
我排查了半天,最后发现是一个数据库查询没加索引,数据量大了以后在主线程跑了超过 5 秒,直接触发了 ANR。
那段时间我养成了一个习惯:所有网络请求、数据库读写、文件操作,一律丢到协程的 IO 线程跑。
kotlin
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
repository.fetchDataFromNetwork()
}
_uiState.value = result
}
还要推荐一个好东西------StrictMode。在 Application 的 onCreate 里打开它,主线程干了不该干的事(磁盘/网络操作),Logcat 直接飘红警告。
坑三:Handler 内存泄漏
Handler 导致的泄漏,我称之为"温柔刀"------平时不显山露水,但一旦 activity 被销毁了,延时消息还在消息队列里等着执行,activity 就被 Handler 活活"拽着"不让回收。
解决方案是一个标准套路:
kotlin
class SafeActivity : AppCompatActivity() {
// 静态内部类 + 弱引用
private class SafeHandler(activity: SafeActivity) : Handler(Looper.getMainLooper()) {
private val weakActivity = WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get()?.let { /* 处理消息 */ }
}
}
private val handler = SafeHandler(this)
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null) // 清空未处理消息
}
}
记住一个原则:你的 Activity 要销毁了,就别让它牵挂任何事。
坑四:NDK 版本升级,一夜回到解放前
这个坑比较小众,但踩到的人都会痛。
有次我升级了 NDK 版本,结果 JNI 层开始随机崩溃。我排查了整整两天,最后发现是NDK 版本的 ABI 兼容性变了,之前能编译通过的 C++ 代码在新版本下行为不符预期。
教训:NDK 版本一旦确定就锁死,升级前做好充分的回归测试。
还可以开启 -Wall -Werror 编译选项,把所有警告当作错误处理------C/C++ 的静默错误太多了,不严查根本发现不了。
最后的建议
踩了这么多坑之后,我总结了几条安卓开发的"保命法则":
- 生命周期是底线------在 onDestroy 里清理所有资源,别让 Activity 空耗
- 主线程只做 UI 操作------其他全交协程
- LeakCanary + StrictMode------两个工具常驻开发环境
- ViewModel + Repository 是正道------数据跟 UI 解耦,配置变更也不怕
- 真机测试胜过一切模拟器------尤其是国产机型,各有各的"特色"
安卓开发不是在写代码,是在跟碎片化做斗争。 但说实话,正是这种挑战性,让每一次"修好一个诡异的 bug"都特别有成就感。
标签:Android、安卓开发、内存泄漏、ANR、避坑指南
写在中间的一些补充
写到这里我突然想起来,这篇文章最初的版本其实很短,就是简单列了几个要点。但后来我觉得,技术文章如果只是罗列知识点,那跟AI生成的有什么区别?
所以我特意加入了更多我的个人经历和感受。
做技术这些年,我最深的体会是:干货并不值钱,值钱的是你对这个技术方向的判断和思考。当你能用自己的话把一个复杂概念讲清楚,才说明你真的懂了。
这也是为什么我写文章不喜欢用那些"首先、其次、最后"的模板结构。生活不是模板,技术也不应该是。## 常见内存泄漏场景
| 泄漏类型 | 发生场景 | 检测方式 | 修复方案 |
|---|---|---|---|
| Context泄漏 | 单例持有Activity | LeakCanary | 改用ApplicationContext |
| Handler泄漏 | 延时消息未清除 | Memory Profiler | 静态Handler+弱引用 |
| 匿名内部类 | 匿名类隐式持有外部引用 | MAT分析 | 改为静态内部类 |
| 资源未关闭 | Cursor/Stream未close | StrictMode | try-with-resources |
| 注册未解绑 | 广播/观察者未解绑 | LeakCanary | onDestroy中解绑 |