24、Android性能优化(下):启动优化、包体积优化、网络优化与电量优化
性能优化这个话题,咱们上一章聊了渲染和内存。今天继续往下挖,把启动、包体积、网络和电量这四个硬骨头啃下来。
说实话,这四项优化在项目里往往是「牵一发而动全身」。你启动快了,可能包就大了;你网络省流量了,可能电量就上去了。所以做优化,心里得有本账。
一、启动优化:用户的第一印象
启动速度有多重要?我见过一个数据:启动慢1秒,用户流失率增加7%。你想想看,用户点开App,盯着白屏看3秒,大概率就划走了。
1. 启动的三种模式
Android系统把启动分成了三类:
- 冷启动:进程从零开始,Application和Activity都要重新创建。这是优化的主战场。
- 温启动:进程还在,但Activity被回收了。需要重新走onCreate。
- 热启动:进程和Activity都在,直接显示。基本不用优化。
核心思路:冷启动阶段,从Application.onCreate到Activity.onWindowFocusChanged,这段时间越短越好。
2. 我常用的优化手段
我在项目中遇到过启动卡顿的问题,排查下来发现是第三方SDK初始化占了太多时间。后来总结了一套打法:
- Application.onCreate里只做必要的事。能懒加载的就懒加载,能异步的就异步。
- 使用IdleHandler。把非紧急的初始化任务放到主线程空闲时执行。
- 减少布局层级。启动页的布局越简单越好,最好用ConstraintLayout。
- 启动页预加载。用SplashScreen API,让用户感觉「秒开」。
// 示例:使用IdleHandler延迟初始化
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 在这里初始化非核心SDK
initNonCriticalSDKs();
return false; // 只执行一次
}
});
小技巧:可以用「启动耗时打点」工具,比如手动在关键方法前后加Log,或者用Firebase Performance。我习惯在Application.onCreate开头和Activity.onWindowFocusChanged结尾各打一个点,差值就是冷启动耗时。
二、包体积优化:小即是美
包体积大了,用户下载意愿低,更新也费流量。我记得有一次,一个版本包体从30MB涨到80MB,结果应用商店的下载转化率直接掉了15%。
1. 包体里到底装了啥?
用Android Studio的APK Analyzer看一眼,你会发现:
- res/:图片资源,尤其是png,占大头
- lib/:so库,每个架构一套
- classes.dex:代码编译后的字节码
- assets/:大文件,比如字体、模型
2. 我常用的瘦身三板斧
第一斧:图片压缩
能用WebP就别用PNG。WebP有损压缩后,体积能小30%-50%。我习惯在构建脚本里自动转换:
android {
defaultConfig {
// 启用WebP
vectorDrawables.useSupportLibrary = true
}
// 在build.gradle中配置图片压缩插件
apply plugin: 'com.kezong.fat-aar'
}
第二斧:移除无用资源
用Lint检查,或者用shrinkResources:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true // 自动移除未引用资源
}
}
}
第三斧:动态下发
大资源文件,比如游戏关卡、视频素材,用Play Feature Delivery或者自研的分包方案。用户用到时才下载。
注意:shrinkResources要配合minifyEnabled使用,否则可能误删。我曾经踩过坑,把一张启动图给删了,结果App启动就崩。后来加了keep规则才解决。
三、网络优化:快和省
网络优化说白了就两件事:让请求更快,让流量更省。
1. 请求合并与缓存
我见过很多App,一个页面发十几个请求。其实完全可以合并:
- 接口合并:把多个小接口合并成一个批量接口
- 本地缓存:用OkHttp的Cache,或者Room做离线缓存
- 预加载:用户滑动列表时,提前加载下一页数据
// OkHttp缓存配置示例
int cacheSize = 10 * 1024 * 1024; // 10MB
Cache cache = new Cache(cacheDir, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new CacheInterceptor()) // 自定义缓存策略
.build();
2. 数据压缩
用Gzip压缩请求体和响应体,能省30%-50%的流量。OkHttp默认支持,但记得在服务端也开启。
3. 减少DNS解析
DNS解析一次要几十毫秒。用HTTPDNS或者长连接,能省掉这个时间。
避坑指南:我曾经在弱网环境下测试,发现请求超时时间设得太短(5秒),导致频繁重试,反而更耗电。后来改成15秒超时,配合指数退避重试策略,效果好了很多。
四、电量优化:续航就是生命
电量优化是最容易被忽视的。用户可能不会直接说「你App耗电」,但他们会默默卸载。
1. 谁在偷电?
Android系统有Battery Historian工具,可以分析耗电情况。常见的耗电大户:
- WakeLock:持锁不释放,CPU无法休眠
- 网络请求:频繁唤醒无线模块
- GPS定位:一直开着高精度定位
- 后台服务:Service常驻后台
2. 我的优化策略
策略一:合理使用WakeLock
用完后必须释放。最好用带超时的acquire:
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyTag");
wl.acquire(30000); // 30秒后自动释放,防止忘记释放
// ... 执行任务
wl.release();
策略二:批量网络请求
把多个小请求合并成一个大请求,减少无线模块的唤醒次数。无线模块从休眠到满功率,耗电是持续传输的好几倍。
策略三:使用JobScheduler
把后台任务交给系统调度,比如在充电时、WiFi环境下才执行同步:
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, serviceComponent)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 仅在WiFi下执行
.setRequiresCharging(true) // 仅在充电时执行
.build();
曾经踩过的坑:有一次,我在定位SDK里忘了关GPS,结果用户反馈手机发烫。排查后发现是定位回调里没有判断页面是否可见。后来加了生命周期感知,页面不可见时自动切换到低功耗定位模式。
五、知识体系总览
下面这张图,是我对本章内容的总结。你可以把它当作优化时的检查清单:
嗯,以上就是我对启动、包体积、网络和电量优化的实战总结。每个点展开都能讲一整天,但核心思路就这些:量化、定位、解决、验证。你想想看,优化这件事,说白了就是跟系统「斗智斗勇」,但只要你掌握了方法,其实没那么难。