最近在做一个App的性能优化,目标机型是OPPO Find X5 Pro。这款手机用的是骁龙8 Gen1处理器,性能很强,但我们的App在上面运行还是有卡顿。
经过两周的调优,我把核心页面的响应时间从500ms降到了100ms,降了80%。滑动帧率从45fps提升到了稳定的60fps。
今天分享这次性能调优的实战过程,包括怎么分析瓶颈、用了哪些优化手段、每一步的效果如何。涉及布局优化、内存优化、启动优化、渲染优化等方面,希望能给做Android开发的朋友一些参考。
一、问题背景
先说说项目背景。
我们的App是一个内容类应用,主要功能是信息流展示、详情页浏览、视频播放等。用户反馈在OPPO Find X5 Pro上滑动信息流的时候偶尔卡顿,进入详情页的响应时间也比较长。
虽然Find X5 Pro性能很强,但我们的App因为历史原因,代码比较臃肿,性能问题很多。之前一直在加功能,没怎么关注性能,现在问题积累得多了,必须优化。
优化前的性能数据:
- 冷启动时间:1.8秒
- 信息流滑动帧率:平均45fps,最低30fps
- 进入详情页响应时间:500ms
- 内存峰值:350MB
- 偶尔出现ANR(应用无响应)
优化目标:
- 冷启动时间降到1秒以内
- 滑动帧率稳定60fps
- 详情页响应时间降到150ms以内
- 内存峰值控制在250MB以内
- 消除ANR
二、第一步:性能分析
优化的第一步,不是上来就改代码,而是先分析瓶颈在哪里。
1. 使用Profiler分析
Android Studio自带的Profiler是最基础的性能分析工具。我用它分析了CPU、内存、网络的使用情况。
发现的问题:
- CPU:信息流滑动的时候,主线程占用率高,有掉帧
- 内存:有内存泄漏,Activity退出后没有释放
- 网络:请求太多,有重复请求
2. 使用Systrace分析渲染
Systrace(现在叫Perfetto)是分析渲染性能的好工具。我抓取了滑动信息流时的trace,发现:
- 每帧的渲染时间超过16ms(60fps的要求),主要是布局和绘制耗时
- 有很多冗余的invalidate和requestLayout
- 主线程有太多任务,导致UI线程阻塞
3. 使用LeakCanary检测内存泄漏
接入了LeakCanary,跑了一遍,发现了好几处内存泄漏:
- 静态变量持有Activity引用
- 监听器没有反注册
- 单例持有Context引用
- Handler没有移除消息
4. 使用Lint检查代码
Android Lint检查出了很多性能相关的警告:
- 布局嵌套过深
- 使用了wrap_content的ImageView,导致多次测量
- 有冗余的布局层级
- 资源没有释放
分析结论: 瓶颈主要在四个方面:
- 布局复杂,渲染耗时
- 内存泄漏,导致GC频繁
- 启动慢,初始化任务太多
- 主线程任务太多,阻塞UI
三、第二步:布局优化
布局优化是性价比最高的优化,改完效果明显。
1. 减少布局嵌套
我们的信息流item布局,嵌套了5层。用ConstraintLayout重构之后,降到了2层。
重构方法:
- 用ConstraintLayout代替LinearLayout+RelativeLayout的组合
- 用merge标签减少多余的层级
- 用include复用公共布局
重构之后,item的测量和布局时间从8ms降到了3ms。
2. 使用ViewStub延迟加载
详情页有一些不常用的布局(比如分享面板、评论区),以前是写在布局里,用gone隐藏。这样虽然不显示,但还是会inflate,消耗时间。
改成ViewStub,只有在需要的时候才inflate。详情页的inflate时间从30ms降到了15ms。
3. 优化ImageView
以前的ImageView用的是wrap_content,导致每次加载图片都要重新测量和布局。改成固定尺寸,或者用adjustViewBounds,减少测量次数。
另外,用了图片库的占位图和渐入动画,避免图片加载时的闪烁和布局跳动。
4. 减少过度绘制
用手机的"调试GPU过度绘制"功能检查,发现我们的页面有很多过度绘制(红色区域)。
优化方法:
- 去掉windowBackground,因为每个页面都有自己的背景
- 去掉不必要的背景色
- 用clipRect减少重叠区域的绘制
优化之后,过度绘制从4x降到了2x以内。
布局优化效果:
- 信息流滑动帧率从45fps提升到55fps
- 详情页响应时间从500ms降到350ms
四、第三步:内存优化
内存优化能减少GC,提升流畅度。
1. 修复内存泄漏
根据LeakCanary的报告,逐一修复了内存泄漏:
- 静态变量改成弱引用,或者改用Application Context
- 监听器在onDestroy里反注册
- 单例用Application Context,不用Activity Context
- Handler在onDestroy里removeCallbacksAndMessages
修复之后,内存泄漏基本消除了。
2. 优化图片内存
图片是内存大户。我们的App里有很多图片,以前没有很好地控制。
优化方法:
- 根据ImageView的尺寸加载合适大小的图片,不要加载原图
- 用RGB565代替ARGB8888(对图片质量要求不高的地方)
- 图片缓存大小限制在合理范围,不要无限缓存
- 大图用区域解码(BitmapRegionDecoder)
优化之后,图片内存占用减少了30%。
3. 避免对象频繁创建
在onDraw、onMeasure、getView等频繁调用的方法里,不要创建新对象。
比如:
- 不要在onDraw里new Paint、new Path
- 不要在getView里new OnClickListener
- 用对象池复用对象(比如SparseArray、Pool)
优化之后,GC的频率明显降低了。
4. 优化数据结构
用更高效的数据结构:
- 用SparseArray代替HashMap<Integer, Object>
- 用ArrayMap代替HashMap(数据量小的时候)
- 避免自动装箱(int vs Integer)
内存优化效果:
- 内存峰值从350MB降到230MB
- GC频率从每分钟5次降到每分钟1次
- 滑动帧率稳定在58fps左右
五、第四步:启动优化
冷启动时间是用户的第一印象,很重要。
1. 分析启动耗时
用Debug.startMethodTracing抓取启动过程的trace,发现启动时间主要花在:
- Application.onCreate:800ms
- 首屏Activity inflate:300ms
- 首屏数据加载:400ms
- 其他:300ms
2. 异步初始化
Application.onCreate里有很多SDK初始化,以前都是同步执行,占了800ms。
优化方法:
- 非必要的SDK,放到子线程异步初始化
- 延迟初始化,用到的时候再初始化
- 用启动器框架(比如Jetpack App Startup)管理初始化顺序
优化之后,Application.onCreate降到了200ms。
3. 首屏优化
- 用SplashActivity做启动页,展示品牌logo,同时后台初始化
- 首屏布局简化,用ViewStub延迟加载非必要部分
- 首屏数据用缓存,先展示缓存数据,再加载新数据
- 用占位图,避免空白
优化之后,首屏显示时间从1.2秒降到了500ms。
4. 类预加载
用ClassLoader提前加载常用的类,减少第一次使用时的加载时间。
启动优化效果:
- 冷启动时间从1.8秒降到了900ms
- 达到了1秒以内的目标
六、第五步:渲染优化
渲染优化是保证流畅度的关键。
1. 主线程减负
主线程只做UI相关的操作,其他操作都放到子线程:
- 网络请求:本来就在子线程
- 数据库读写:用Room或异步查询
- 文件读写:放到子线程
- 复杂计算:放到子线程,结果post回主线程
用了RxJava和Kotlin协程,简化了异步操作。
2. 减少invalidate和requestLayout
检查代码,发现有很多不必要的invalidate和requestLayout。比如:
- 数据没变化也调用notifyDataSetChanged
- 布局参数没变也调用requestLayout
- 自定义View里频繁调用invalidate
优化方法:
- 用DiffUtil,只更新变化的item
- 只有布局参数变化时才调用requestLayout
- 自定义View里,只在需要重绘时调用invalidate
优化之后,每帧的渲染时间稳定在12ms以内。
3. 硬件加速
确保所有页面都开启了硬件加速。自定义View里,用硬件加速的API(比如setLayerType)。
对复杂的自定义View,用setLayerType(LAYERTYPEHARDWARE, null),把View渲染到离屏缓冲,提升动画流畅度。
4. 优化RecyclerView
RecyclerView是我们用得最多的控件,优化点很多:
- 用DiffUtil,不要全量notifyDataSetChanged
- 设置setHasFixedSize(true),避免不必要的布局
- 用setItemViewCacheSize,增加缓存数量
- 预加载(setInitialPrefetchItemCount)
- 优化ViewHolder,减少findViewById(用ViewBinding或DataBinding)
- 图片加载时,不要在滑动时加载(用pauseRequests/resumeRequests)
渲染优化效果:
- 滑动帧率稳定在60fps
- 详情页响应时间降到100ms
- 没有再出现ANR
七、第六步:网络优化
网络虽然不直接影响UI性能,但会影响数据加载速度,间接影响用户体验。
1. 减少请求数量
- 合并接口,一个页面的多个请求合并成一个
- 用缓存,不要重复请求
- 预加载,进入页面之前就开始加载
2. 优化请求速度
- 用HTTP/2,多路复用
- 启用gzip压缩
- 用CDN,减少网络延迟
- DNS预解析
3. 数据缓存
- 内存缓存+磁盘缓存
- 先展示缓存数据,再加载新数据
- 离线也能看缓存内容
八、优化效果总结
经过两周的调优,性能数据如下:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 冷启动时间 | 1.8s | 0.9s | 50% |
| 滑动帧率 | 45fps | 60fps | 33% |
| 详情页响应时间 | 500ms | 100ms | 80% |
| 内存峰值 | 350MB | 230MB | 34% |
| ANR次数 | 每周数次 | 0 | 100% |
所有指标都达到了预期目标,用户反馈也明显好转。
九、性能优化的经验总结
这次优化,我总结了几条经验。
1. 先测量,再优化
不要凭感觉优化,先用工具分析,找到真正的瓶颈。很多时候,瓶颈不在你以为的地方。
2. 从性价比最高的开始
布局优化、内存泄漏修复,这些改动小、效果大,先做。复杂的优化(比如启动优化、渲染优化)放在后面。
3. 优化是持续的
性能优化不是一次就完了。新功能上线,可能会引入新的性能问题。要建立性能监控体系,持续关注。
4. 不要过度优化
优化到一定程度就够了,不要为了极致性能把代码搞复杂。60fps和59fps,用户感知不到差别,但实现复杂度差很多。
5. 建立性能基线
每次发版前,跑一遍性能测试,和基线对比。如果性能下降了,要找到原因,不要让性能问题积累。
十、写在最后
性能优化是Android开发的基本功,也是最能体现工程师价值的地方。
这次在OPPO Find X5 Pro上的性能调优,让我对Android性能优化有了更深的理解。很多优化方法,其实都是基础,只是平时容易忽略。
性能优化没有银弹,就是一点点分析,一点点改进。只要方法对了,坚持做下去,效果一定会出来。
2022年了,手机性能越来越强,但App也越来越臃肿。性能优化永远不会过时。
希望这篇文章能给做Android开发的朋友一些参考。如果你也有性能优化的经验或问题,欢迎在评论区交流。
祝大家的App都能又快又稳。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录