做了几年安卓开发,这些坑我帮你踩过了

做了几年安卓开发,这些坑我帮你踩过了

大家好,我是 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++ 的静默错误太多了,不严查根本发现不了。


最后的建议

踩了这么多坑之后,我总结了几条安卓开发的"保命法则":

  1. 生命周期是底线------在 onDestroy 里清理所有资源,别让 Activity 空耗
  2. 主线程只做 UI 操作------其他全交协程
  3. LeakCanary + StrictMode------两个工具常驻开发环境
  4. ViewModel + Repository 是正道------数据跟 UI 解耦,配置变更也不怕
  5. 真机测试胜过一切模拟器------尤其是国产机型,各有各的"特色"

安卓开发不是在写代码,是在跟碎片化做斗争。 但说实话,正是这种挑战性,让每一次"修好一个诡异的 bug"都特别有成就感。

标签:Android、安卓开发、内存泄漏、ANR、避坑指南

写在中间的一些补充

写到这里我突然想起来,这篇文章最初的版本其实很短,就是简单列了几个要点。但后来我觉得,技术文章如果只是罗列知识点,那跟AI生成的有什么区别?

所以我特意加入了更多我的个人经历和感受。

做技术这些年,我最深的体会是:干货并不值钱,值钱的是你对这个技术方向的判断和思考。当你能用自己的话把一个复杂概念讲清楚,才说明你真的懂了。

这也是为什么我写文章不喜欢用那些"首先、其次、最后"的模板结构。生活不是模板,技术也不应该是。## 常见内存泄漏场景

泄漏类型 发生场景 检测方式 修复方案
Context泄漏 单例持有Activity LeakCanary 改用ApplicationContext
Handler泄漏 延时消息未清除 Memory Profiler 静态Handler+弱引用
匿名内部类 匿名类隐式持有外部引用 MAT分析 改为静态内部类
资源未关闭 Cursor/Stream未close StrictMode try-with-resources
注册未解绑 广播/观察者未解绑 LeakCanary onDestroy中解绑
相关推荐
逐光老顽童2 天前
Java 与 Kotlin 混合开发避坑指南:30 个真实案例实录
android·kotlin
爱勇宝3 天前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
Yeyu3 天前
刷新一帧的艺术:invalidate / postInvalidate / postInvalidateOnAnimation全解析
android
潘潘潘3 天前
Android OTA 升级原理和流程介绍
android
plainGeekDev3 天前
null 判断 → Kotlin 可空类型
android·java·kotlin
plainGeekDev3 天前
getter/setter → Kotlin 属性
android·java·kotlin
YXL1111YXL3 天前
Handler 消息回收与协程异步执行的时序陷阱
android
恋猫de小郭3 天前
KMP / CMP 鸿蒙版本 Beta 发布,他有什么特别之处?
android·前端·flutter
三少爷的鞋3 天前
Android 协程并发控制:别动线程池,控制好并发语义就够了
android