0赞
赞赏
更多好文
上周五,我刚喝完第三杯咖啡,产品经理就拍桌子:“用户投诉App一打开就卡死,这周必须解决!”
打开崩溃日志——java.lang.OutOfMemoryError。
真·血泪教训:上线前没测性能,现在全靠运维同事半夜救火。
这次,我们没靠“大概能行”,而是实打实优化了内存泄漏和启动速度。
下面全是血泪经验,没理论,只有能直接抄的代码。
一、内存泄漏:不是“偶尔崩溃”,是每天都在漏!
为什么重要?
用户一打开App就闪退,你猜用户会干嘛?卸载。
我们之前一个Activity泄漏,导致用户用着用着内存爆了,崩溃率从2%飙到15%。
1. 工具:LeakCanary不是加了就行,得会用!
-
别只加依赖:
implementation 'com.squareup.leakcanary:leakcanary-android:2.12'
必须在build.gradle里加debugImplementation,release包别带! -
怎么用?
打开App,触发崩溃点,LeakCanary会生成报告。
关键看这个:* com.example.MainActivity has leaked: * GC ROOT static com.example.MainActivity$1.this$0 (anonymous class) * references com.example.MainActivity$1.mContext * references com.example.MainActivity.mContext * leaks com.example.MainActivity instance这说明
MainActivity没被回收,因为匿名内部类持有它的引用。
2. 一个真实案例:Fragment泄漏
错误写法(我们团队踩过):
class UserFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 问题:匿名类持有Fragment引用
lifecycle.addObserver(object : LifecycleObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
// 比如监听网络状态
}
})
}
}
修复:
class UserFragment : Fragment() {
private val lifecycleObserver = object : LifecycleObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { /*...*/ }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 正确:用成员变量,避免匿名类
lifecycle.addObserver(lifecycleObserver)
}
override fun onDestroy() {
super.onDestroy()
// 关键!必须移除监听
lifecycle.removeObserver(lifecycleObserver)
}
}
💡 经验:任何监听器、回调,都必须在
onDestroy里移除。LeakCanary报告里90%的泄漏都是这个原因。
二、启动速度:冷启动从2.3s→0.7s,用户说“快多了”
为什么用户会走?
Google数据:启动时间超过3秒,53%用户会直接卸载。
我们之前冷启动2.3秒(小米10实测),用户投诉“像在等加载”。
1. 优化启动速度的实操清单
| 问题 | 解决方案 | 效果(实测) |
|---|---|---|
| SplashActivity太长 | 用SplashScreen API(Android 12+) | 启动图显示时间↓50% |
| Application初始化慢 | 把SDK初始化移到WorkManager后台 | 启动时间↓0.8s |
| 主线程加载大图片 | 用Glide懒加载,别在onCreate里 | 启动时间↓0.5s |
具体操作:
-
Step 1:用
SplashScreen替代传统SplashActivity
res/drawable/splash.xml:<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/white" /> <item> <bitmap android:src="@drawable/splash_logo" android:gravity="center" /> </item> </layer-list>AndroidManifest.xml:<activity android:name=".MainActivity" android:theme="@style/SplashTheme"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>res/values/themes.xml:<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowSplashScreenBackground">@drawable/splash</item> </style>✅ 实测:启动图显示时间从1.2s→0.6s,用户感觉“瞬间就进去了”。
-
Step 2:把SDK初始化扔到后台
以前:class App : Application() { override fun onCreate() { super.onCreate() // 问题:这里初始化了友盟、极光,耗时1秒+ UMAnalytics.init(this) JPushInterface.init(this) } }优化后:
class App : Application() { override fun onCreate() { super.onCreate() // 用WorkManager启动,不阻塞主线程 WorkManager.getInstance(this).enqueue( OneTimeWorkRequestBuilder<InitWork>() .build() ) } } class InitWork : CoroutineWorker(context, params) { override suspend fun doWork(): Result { // 这里初始化SDK UMAnalytics.init(applicationContext) JPushInterface.init(applicationContext) return Result.success() } }✅ 实测:冷启动从2.3s→1.5s(主线程没被卡住)。
三、避坑指南:我们踩过的雷,别再踩
-
坑:以为加了LeakCanary就安全了
- 我们加了,但没定期看报告。
- 后果:上线后崩溃率又上来了。
- 解法:每周跑一次LeakCanary,把报告发到团队群里。
-
坑:只优化SplashActivity,忽略Application
- 以为Splash优化了就行,结果
Application.onCreate()里初始化了3个SDK,耗时1.5秒。 - 解法:用Profiler看整个启动过程(Android Studio → Profiler → CPU → Start Profiling)。
- 以为Splash优化了就行,结果
-
坑:用
AsyncTask做初始化- 以前用
AsyncTask,结果Android 10+直接不执行。 - 解法:用
WorkManager或CoroutineScope(Dispatchers.IO)。
- 以前用
四、效果:用户说“这App真香了”
- 内存泄漏:崩溃率从15%→0%(上线后1个月数据)。
- 启动速度:
- 冷启动:2.3s → 0.7s(小米10实测)
- 热启动:0.8s → 0.2s
- 用户反馈:
“上次打开App等了3秒,这次点一下就进来了,真快!”
—— 某忠实用户留言
最后说点实在的
别等用户骂了才优化。
上周我们团队把LeakCanary加进CI流程,每次Build自动跑内存检测。
现在,性能优化不是“需要做”,而是“必须做”。
试试看:
- 在
build.gradle里加debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'- 用
SplashScreen替换SplashActivity- 把SDK初始化扔到
WorkManager
半小时搞定,用户却能多用你半年。
