三星Z Fold 7发布之后,我们团队的应用收到了很多折叠屏用户的反馈,说应用在折叠屏上卡顿严重,响应很慢。
我负责了这次性能调优的工作。经过两周的努力,我们把应用在Z Fold 7上的响应时间从平均800毫秒降到了160毫秒,降低了80%。用户的反馈也明显好转了。
这篇文章我想分享一下这次性能调优的过程和经验。从折叠屏的特殊性,到具体的优化手段,再到效果验证,聊聊如何在折叠屏手机上做好应用性能优化。如果你也在做折叠屏适配或者性能优化,希望这篇文章能给你一些参考。
先说明一下,我们的应用是一个图片编辑类应用,对渲染和交互的要求比较高。不同类型的应用,优化的重点可能不一样,但思路是相通的。
折叠屏的特殊性
在说具体的优化之前,先说说折叠屏手机的特殊性。理解了这些特殊性,才能针对性地做优化。
第一个特殊性是屏幕尺寸变化大。折叠屏手机展开之后屏幕尺寸和平板差不多,比普通手机大很多。应用在展开状态下需要渲染更多的内容,布局也更复杂。如果没有做好适配很容易出现卡顿。特别是我们的图片编辑应用,展开之后画布变大了,需要渲染的像素多了好几倍,渲染压力自然就大了。
第二个特殊性是折叠状态切换。用户在使用过程中可能会随时折叠或者展开手机。这时候应用需要重新布局适应新的屏幕尺寸。如果布局切换的过程很慢,用户就会感觉到明显的卡顿。很多应用在折叠切换的时候会有几百毫秒的延迟,甚至会出现黑屏或者闪屏。这个体验非常不好。
第三个特殊性是性能调度。折叠屏手机为了控制发热和续航,在展开状态下的性能调度可能和折叠状态不一样。有些手机在展开大屏幕的时候会限制CPU和GPU的频率以控制发热。这时候应用的性能就会受到影响。Z Fold 7在这方面做得还不错,展开状态下的性能释放比较充分,但长时间高负载运行还是会降频。
第四个特殊性是分屏和多窗口。折叠屏手机支持分屏和多窗口模式,用户可能会同时使用多个应用。这时候每个应用能分到的性能资源就更少了。如果应用在分屏模式下没有做好优化就会很卡。理解了这些特殊性我们就可以针对性地做优化了。
第一步:性能分析和定位问题
做性能优化的第一步,不是上来就改代码,而是先做性能分析,定位问题在哪里。
我们用了Android Studio的Profiler工具,对应用在Z Fold 7上的运行情况做了详细的分析。主要分析了三个方面:CPU使用率、GPU渲染时间、内存使用情况。
分析之后,我们发现了几个主要的性能瓶颈。
第一个瓶颈是布局渲染太慢。
应用在展开状态下,主界面的布局非常复杂,有很多嵌套的View。每次渲染的时候,measure和layout的时间很长,导致界面响应慢。特别是在折叠切换的时候,重新布局需要将近500毫秒,用户能明显感觉到卡顿。
第二个瓶颈是图片渲染效率低。
我们的应用是图片编辑类,需要频繁地渲染图片。原来的实现方式是每次编辑操作都重新渲染整张图片,而且没有做缓存。在展开状态下,图片分辨率很高,渲染一次需要几百毫秒,导致操作响应很慢。
第三个瓶颈是主线程阻塞。
我们发现有很多耗时操作在主线程执行,比如文件读写、图片解码、数据库查询等。这些操作阻塞了主线程,导致UI无法及时响应用户的操作。
第四个瓶颈是内存占用过高。
应用在展开状态下,同时加载了很多高分辨率的图片,内存占用很高,经常超过500MB。高内存占用会导致频繁的GC,GC的时候会暂停所有线程,也会造成卡顿。
定位了这些问题之后,我们就开始针对性地优化了。
第二步:布局优化
第一个优化方向是布局优化。
原来的主界面布局有很多层嵌套,最深的地方有8层嵌套。这么深的嵌套,measure和layout的时候需要递归遍历,非常耗时。
我们做了几个优化。
第一个优化是减少布局嵌套。我们用ConstraintLayout替换了原来的LinearLayout和RelativeLayout的嵌套组合。ConstraintLayout可以用一层布局实现复杂的界面,大大减少了嵌套层数。优化之后主界面的嵌套层数从8层降到了3层,measure和layout的时间减少了60%。
第二个优化是使用ViewStub延迟加载。主界面有一些不常用的功能面板,比如高级设置面板、滤镜面板等。原来的实现是这些面板都在布局里只是设置为不可见。这样虽然看不见但还是会参与measure和layout浪费性能。我们用ViewStub替换了这些面板。ViewStub是一个轻量级的View,默认不参与布局,只有在需要的时候才inflate出来。这样初始布局的渲染时间大大减少了。
第三个优化是避免过度绘制。我们用手机的开发者选项里的"调试GPU过度绘制"功能,发现主界面有很多过度绘制的区域。有些地方被绘制了4到5层非常浪费GPU资源。我们优化了背景色的设置,去掉了不必要的背景,合并了一些重叠的View。优化之后大部分区域的过度绘制降到了2层以下,GPU渲染时间减少了30%。布局优化之后主界面的渲染时间从300毫秒降到了100毫秒,折叠切换的时间从500毫秒降到了150毫秒,效果很明显。
第三步:图片渲染优化
第二个优化方向是图片渲染优化。
原来的图片渲染方式效率很低,每次编辑操作都重新渲染整张图片,而且没有做缓存。在展开状态下,图片分辨率很高,渲染一次需要几百毫秒。
我们做了几个优化。
第一个优化是分层渲染。我们把图片编辑的操作分成了不同的层,比如基础层、滤镜层、文字层、贴纸层等。每次编辑操作只重新渲染受影响的层,其他层保持不变。然后把所有层合成起来显示到屏幕上。这样每次编辑操作的渲染量大大减少了,从渲染整张图片变成了只渲染一个层。渲染时间从300毫秒降到了50毫秒。
第二个优化是渲染缓存。我们对渲染结果做了缓存。如果用户连续做了几个相同的操作,或者撤销重做的时候直接从缓存里取结果不需要重新渲染。缓存用了LRU策略,最多缓存最近的10个渲染结果。有了缓存之后很多操作的响应时间降到了几乎为零,因为直接从缓存里取结果就可以了。
第三个优化是降低预览分辨率。在用户编辑的过程中我们不需要显示最高分辨率的图片,只需要显示一个预览就可以了。我们把预览的分辨率降到了屏幕分辨率而不是原始图片的分辨率。这样渲染的数据量大大减少了,渲染速度也快了很多。只有在用户导出图片的时候才用最高分辨率渲染。这样既保证了编辑过程的流畅性,又保证了最终导出的质量。
第四个优化是使用GPU渲染。原来的图片渲染是用CPU做的效率不高。我们改用了GPU来做图片渲染,用OpenGL ES实现了图片的滤镜和变换操作。GPU的并行计算能力比CPU强很多,特别适合图片处理这种并行任务。改用GPU渲染之后复杂滤镜的渲染时间从200毫秒降到了30毫秒,效果非常明显。图片渲染优化之后编辑操作的平均响应时间从400毫秒降到了80毫秒,用户体验提升很大。
第四步:主线程优化
第三个优化方向是主线程优化。
我们发现有很多耗时操作在主线程执行,阻塞了UI响应。我们把这些操作都移到了后台线程。
第一个优化是文件读写异步化。
原来的图片保存和加载都是在主线程做的,文件大的时候会卡住几秒钟。我们改成了异步操作,在后台线程读写文件,主线程只负责显示进度和结果。这样用户在保存图片的时候,还能继续操作界面,不会卡住。
第二个优化是图片解码异步化。
原来的图片解码也是在主线程做的,高分辨率的图片解码需要几百毫秒。我们改成了异步解码,在后台线程解码图片,解码完成之后再更新到UI上。这样界面不会因为解码图片而卡住。
第三个优化是数据库查询异步化。
我们的应用有一些本地数据库,用来保存用户的项目和设置。原来的数据库查询是在主线程做的,数据多的时候会有延迟。我们改成了异步查询,用Room数据库的异步查询接口,查询结果通过回调返回。这样主线程不会被数据库查询阻塞。
第四个优化是避免主线程的锁竞争。
我们发现在多线程操作的时候,有些锁的竞争会导致主线程被阻塞。我们优化了锁的粒度,把大锁拆成了小锁,减少了锁竞争的概率。还把一些不需要加锁的操作去掉了锁,提高了并发性能。
主线程优化之后,主线程的阻塞时间大大减少了,UI响应更流畅了。
第五步:内存优化
第四个优化方向是内存优化。
应用在展开状态下内存占用很高,经常超过500MB,导致频繁GC和卡顿。我们做了几个优化。
第一个优化是图片采样率。
原来加载图片的时候,是按原始分辨率加载的,一张高分辨率的图片可能就要占几十MB内存。我们根据屏幕的分辨率,计算合适的采样率,加载缩小后的图片。这样内存占用大大减少了,一张图片从几十MB降到了几MB。
第二个优化是图片复用。
我们用了inBitmap选项,让Bitmap可以复用。在频繁创建和销毁Bitmap的场景下,复用Bitmap可以减少内存分配和GC的次数。我们还建立了一个Bitmap对象池,复用不需要的Bitmap。
第三个优化是及时释放资源。
原来的实现中,有些图片和资源在不需要的时候没有及时释放,导致内存一直占用。我们优化了生命周期管理,在Activity销毁或者页面切换的时候,及时释放不需要的图片和资源。还加了onTrimMemory的回调,在系统内存不足的时候,主动释放一些可以重建的缓存。
第四个优化是减少对象创建。
我们发现在渲染循环中,有很多临时对象被频繁创建和销毁,比如Paint、Rect、Path等。这些对象的频繁创建会导致内存抖动和频繁GC。我们把这些对象改成了成员变量或者对象池,复用它们,减少了对象创建的次数。
内存优化之后,应用的内存占用从500MB降到了200MB,GC的次数也大大减少了,卡顿明显减少。
第六步:折叠切换优化
第五个优化方向是折叠切换优化。
折叠切换是折叠屏手机特有的场景,也是用户最容易感知到卡顿的地方。我们做了几个优化。
第一个优化是保存和恢复状态。
在折叠切换的时候,Activity会被重建,如果没有保存状态,用户的编辑内容和操作进度就会丢失。我们用了onSaveInstanceState和ViewModel来保存状态,在重建之后恢复状态。这样用户切换折叠状态之后,可以继续之前的操作,不会丢失内容。
第二个优化是平滑过渡动画。
原来的折叠切换是直接跳变的,用户会感觉到很突兀。我们加了平滑的过渡动画,在切换的时候,界面会平滑地缩放和布局,过渡更自然。动画的时间控制在200毫秒左右,既流畅又不会太慢。
第三个优化是预加载。
我们在用户开始折叠的时候,就预加载新状态下需要的资源和布局。这样等折叠完成的时候,界面已经准备好了,不需要再等加载。预加载用了一个折叠状态的监听回调,在折叠角度变化到一定阈值的时候触发。
折叠切换优化之后,切换的时间从500毫秒降到了150毫秒,而且过渡更平滑了,用户体验提升很大。
效果验证
优化完成之后,我们做了详细的效果验证。
我们用了自动化测试工具,模拟用户的常用操作,比如打开应用、编辑图片、切换滤镜、保存图片、折叠切换等,测量每个操作的响应时间。
优化之前,平均响应时间是800毫秒,最长的操作超过2秒。优化之后,平均响应时间是160毫秒,最长的操作不超过300毫秒。响应时间降低了80%。
我们还做了用户体验测试,邀请了20个折叠屏用户,让他们使用优化前后的应用,打分评价。优化之前的平均得分是3.2分(满分5分),优化之后的平均得分是4.5分,提升很明显。用户的反馈主要是"流畅多了"、"不再卡顿了"、"折叠切换很顺滑"。
我们还监测了线上的性能数据。优化上线之后,折叠屏用户的ANR率从0.8%降到了0.1%,崩溃率也下降了。用户的留存率和使用时长都有提升。
总的来说,这次性能调优的效果是显著的,达到了预期的目标。
经验总结
最后总结一下这次性能调优的经验。
第一,先分析再优化。
做性能优化,不要上来就改代码。先用工具分析,定位问题在哪里,然后针对性地优化。盲目优化可能花了很多时间,效果却不好。
第二,从最大的瓶颈开始优化。
性能优化要抓主要矛盾,先优化影响最大的瓶颈。我们这次最大的瓶颈是图片渲染,优化图片渲染带来的提升最大。先解决大问题,再解决小问题,效率最高。
第三,优化之后要验证。
每做一个优化,都要测量效果,确认优化确实有效。有时候你以为优化了,实际上可能没有效果,甚至有反效果。用数据说话,不要凭感觉。
第四,折叠屏适配要重视。
折叠屏手机越来越多,折叠屏用户的需求不能忽视。折叠屏有它的特殊性,需要针对性地做适配和优化。做好折叠屏适配,能提升这部分用户的体验,也能体现应用的质量。
第五,性能优化是持续的工作。
性能优化不是一次就能做完的,随着功能的增加和设备的更新,性能问题会不断出现。要建立持续的性能监测和优化机制,定期检查性能指标,及时发现和解决问题。
写在最后
这次在三星Z Fold 7上的性能调优,让我们对折叠屏应用的性能优化有了深入的理解。通过布局优化、图片渲染优化、主线程优化、内存优化和折叠切换优化,我们把响应时间降低了80%。
折叠屏手机是未来的一个趋势,越来越多的用户会使用折叠屏设备。做好折叠屏适配和性能优化,是应用开发者必须重视的事情。
希望这篇文章的经验,能给正在做折叠屏适配或者性能优化的你,带来一些参考和启发。
最后用一句话来结束这篇文章:"性能优化没有终点,只有不断的迭代和改进。"
愿你的应用在折叠屏上也能流畅运行,给用户带来好的体验。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录