最近在做一款带屏智能音箱的性能优化,经过一段时间的调优,把语音指令的响应时间从原来的2秒多,降到了400毫秒左右,降了80%,体验提升非常明显。
带屏智能音箱,这两年很火,在传统智能音箱的基础上,加了一块屏幕,可以显示天气、歌词、视频、菜谱等内容,交互更丰富。但带屏之后,系统也更复杂了,性能问题也更多了,尤其是语音响应速度,直接影响用户体验。
今天来分享一下这次性能调优的过程和经验,从问题定位到原因分析,再到具体的优化措施,以及最终的效果,希望能给做智能硬件或者Android性能优化的朋友一些参考。
一、背景和问题
先说说项目背景和遇到的问题。
我们做的这款带屏智能音箱,用的是Android系统,屏幕是7英寸的触摸屏,搭载了语音助手,可以通过语音控制,查询天气、播放音乐、设置闹钟、看视频等。硬件配置不算高,用的是中低端的芯片,内存1GB,存储8GB,成本控制得比较严。
最开始,功能都做出来了,但体验很差,尤其是语音响应速度,用户说完话,要等2秒多,屏幕才会有反应,有时候甚至要3秒以上,用户体验非常不好,很多测试用户都反馈,反应太慢了,像个傻子。
产品经理给的目标是,语音响应时间要控制在500毫秒以内,最好能做到300毫秒,这样用户才会觉得"快",体验才好。
2秒多到500毫秒,要降75%以上,这个目标还是很有挑战性的。但既然接了这个任务,就硬着头皮上吧,开始了漫长的性能调优之路。
二、第一步:性能 profiling,找到瓶颈
性能调优的第一步,不是上来就改代码,而是先做性能 profiling,找到瓶颈在哪里,不然就是瞎优化,白费力气。
我们用了几种工具,来分析语音响应的整个链路:
1. 系统日志埋点
在语音响应的各个关键节点,加了日志埋点,记录每个节点的时间戳,包括:
- 语音结束(VAD检测到语音结束)
- 语音开始上传
- 语音上传完成
- 云端开始识别
- 云端识别完成,返回结果
- 设备收到云端结果
- 开始解析指令
- 开始UI渲染
- UI渲染完成,屏幕显示结果
通过这些埋点,我们就能清楚地看到,时间都花在了哪个环节。
2. Android Profiler
用Android Studio的Profiler,分析CPU、内存、网络的使用情况,看在语音响应的过程中,CPU是不是跑满了,内存是不是有泄漏,网络是不是有延迟。
3. Systrace
用Systrace抓取系统的调用链,看各个线程的执行情况,有没有线程阻塞,有没有锁竞争,有没有不合理的调度。
经过一周的 profiling,我们终于找到了瓶颈所在,时间主要花在了这几个地方:
- 语音上传和云端识别,占了约600毫秒:这个是网络和云端的时间,设备端优化空间不大。
- 指令解析和业务逻辑,占了约400毫秒:这个是设备端的问题,有很大的优化空间。
- UI渲染,占了约800毫秒:这个是最大的瓶颈,渲染太慢了。
- 进程启动和资源加载,占了约300毫秒:有些功能是冷启动的,需要启动进程,加载资源。
加起来,总共2秒多,和实际测的差不多。
找到了瓶颈,就可以针对性地优化了。我们的优化策略是,云端和网络的时间,尽量优化,但主要精力放在设备端,尤其是UI渲染和指令解析,这两块优化空间最大。
三、优化一:UI渲染优化,从800ms降到150ms
UI渲染是最大的瓶颈,占了800毫秒,我们花了最多的精力在这上面。
问题1:布局嵌套太深,过度绘制严重
最开始,我们的UI布局,嵌套了很多层LinearLayout和RelativeLayout,最深的地方有七八层,导致measure和layout的时间很长,而且过度绘制很严重,很多像素被绘制了好几遍。
优化措施:
- 用ConstraintLayout替代嵌套的LinearLayout,减少布局层级,最深的地方从七八层降到了三四层。
- 用merge标签,减少不必要的根布局。
- 用ViewStub,延迟加载不常用的布局。
- 去掉不必要的背景,减少过度绘制,把过度绘制从3x-4x降到了1x-2x。
优化之后,measure和layout的时间,从原来的200多毫秒,降到了50毫秒左右。
问题2:图片太大,解码慢
UI上用了很多图片,比如背景图、图标、天气图标等,最开始没有做压缩,图片都很大,有的背景图有两三MB,解码的时候非常慢,而且占用内存多。
优化措施:
- 对图片进行压缩,根据屏幕分辨率,提供合适尺寸的图片,不要用比屏幕大很多的图。
- 用WebP格式替代PNG和JPG,同样的画质,体积小30%左右。
- 图片解码放在子线程,不要在主线程解码,避免阻塞UI线程。
- 用图片缓存,解码过的图片缓存起来,不要重复解码。
优化之后,图片解码的时间,从原来的300多毫秒,降到了50毫秒左右。
问题3:主线程做了太多事情
最开始,很多操作都放在了主线程,比如数据解析、文件读取、数据库查询,导致主线程阻塞,UI渲染不及时。
优化措施:
- 把所有耗时操作,都放到子线程去做,主线程只负责UI渲染。
- 用Handler和Looper,做线程间通信,把结果post到主线程更新UI。
- 用线程池,管理子线程,避免频繁创建和销毁线程。
优化之后,主线程的负担大大减轻,UI渲染更流畅了。
问题4:动画不流畅,掉帧严重
UI切换的时候,有动画效果,但最开始动画很卡,掉帧严重,看起来很不流畅,也增加了整体的响应时间。
优化措施:
- 用属性动画替代补间动画,性能更好。
- 减少动画的复杂度,不要同时做太多动画。
- 用硬件加速,让动画在GPU上渲染,更流畅。
- 对低端设备,简化或关闭动画,保证响应速度。
经过这一系列优化,UI渲染的时间,从原来的800毫秒,降到了150毫秒左右,效果非常明显。
四、优化二:指令解析优化,从400ms降到80ms
指令解析是第二大瓶颈,占了400毫秒,主要是从云端返回的JSON结果,解析成业务对象,然后做一系列的业务逻辑处理。
问题1:JSON解析慢
最开始用的是Gson解析JSON,Gson虽然方便,但性能不是最好的,尤其是在低端设备上,解析比较大的JSON的时候,比较慢。
优化措施:
- 用Fastjson替代Gson,Fastjson的解析速度更快,在低端设备上优势更明显。
- 对JSON结构进行精简,去掉不必要的字段,减少解析的数据量。
- 用流式解析,对于特别大的JSON,用流式解析,不需要把整个JSON都加载到内存。
优化之后,JSON解析的时间,从原来的150毫秒,降到了30毫秒左右。
问题2:业务逻辑复杂,串行执行
指令解析之后,要做一系列的业务逻辑处理,比如查询数据库、读取配置、调用接口、状态更新等,最开始这些操作都是串行执行的,一个做完才做下一个,加起来时间很长。
优化措施:
- 把没有依赖关系的操作,并行执行,用线程池同时跑,节省时间。
- 把可以提前做的操作,提前做,比如在等云端返回的时候,就开始准备数据,不要等结果回来了才开始。
- 用缓存,把常用的数据缓存起来,不要每次都查数据库或读文件。
- 优化数据库查询,加索引,减少查询时间。
优化之后,业务逻辑的处理时间,从原来的250毫秒,降到了50毫秒左右。
这样,指令解析这块,从400毫秒降到了80毫秒,效果也很明显。
五、优化三:进程启动和资源加载优化,从300ms降到50ms
有些功能,比如看视频、看菜谱,需要启动一个新的Activity,甚至是一个新的进程,冷启动的时间比较长,占了300毫秒。
问题1:Activity冷启动慢
Activity冷启动的时候,要做很多事情,加载布局、初始化控件、加载数据、渲染UI,这些都在主线程做,导致启动慢。
优化措施:
- 用懒加载,不要在onCreate里做所有事情,只做最必要的初始化,其他的等页面显示出来再做。
- 用预加载,在用户可能用到这个功能之前,就提前初始化,提前加载数据,等用户真正用的时候,就很快了。
- 优化启动流程,去掉不必要的初始化,把能异步的都异步化。
- 用Activity的预热,在应用启动的时候,就把常用的Activity预热一下,减少冷启动的次数。
问题2:资源加载慢
有些功能需要加载大量的资源,比如视频页面要加载视频播放器,菜谱页面要加载大量的图片,这些资源加载比较慢。
优化措施:
- 资源预加载,在应用启动的时候,就把常用的资源加载到内存,用的时候直接取。
- 资源懒加载,只加载当前需要的资源,不要一次性加载所有资源。
- 资源压缩,对图片、视频等资源进行压缩,减少加载时间。
- 用缓存,把加载过的资源缓存起来,不要重复加载。
经过优化,进程启动和资源加载的时间,从300毫秒降到了50毫秒左右。
六、优化四:网络和云端优化,从600ms降到120ms
网络和云端的时间,虽然不是设备端的问题,但也有一些可以优化的地方。
问题1:语音上传慢
最开始,语音是等用户说完了,才开始上传,这样上传的时间就完全叠加在了响应时间里。
优化措施:
- 用流式上传,用户说话的同时,就开始上传语音,不需要等说完了再传,这样可以节省上传时间。
- 对语音进行压缩,用更高效的编码格式,减少上传的数据量。
- 优化网络连接,用长连接,减少握手时间。
优化之后,语音上传的时间,从原来的300毫秒,降到了50毫秒左右。
问题2:云端识别慢
云端识别的时间,主要取决于云端的处理能力,设备端能做的不多,但也有一些优化空间。
优化措施:
- 和云端团队沟通,优化识别模型,提高识别速度。
- 用边缘计算,把一些简单的识别放在设备端做,不需要都传到云端。
- 对常用的指令,做本地缓存,用户说常用指令的时候,直接本地处理,不需要走云端,速度更快。
优化之后,云端识别的时间,从原来的300毫秒,降到了70毫秒左右。
这样,网络和云端的时间,从600毫秒降到了120毫秒。
七、优化效果汇总
经过这一系列的优化,我们来汇总一下效果:
| 环节 | 优化前 | 优化后 | 降幅 |
|---|---|---|---|
| 语音上传和云端识别 | 600ms | 120ms | 80% |
| 指令解析和业务逻辑 | 400ms | 80ms | 80% |
| UI渲染 | 800ms | 150ms | 81% |
| 进程启动和资源加载 | 300ms | 50ms | 83% |
| 总计 | 2100ms | 400ms | 81% |
总的响应时间,从原来的2.1秒,降到了400毫秒,降了80%还多,达到了产品经理的目标(500毫秒以内),甚至超出了预期。
用户体验的提升非常明显,以前说完话要等两秒多,现在几乎是话音刚落,屏幕就有反应了,感觉很跟手,很流畅,测试用户的反馈都很好,说像换了一台设备一样。
而且,优化之后,CPU和内存的占用也降低了,设备运行更流畅,发热也减少了,续航也有所提升,算是意外的收获。
八、性能调优的经验总结
这次性能调优,花了大概一个月的时间,踩了不少坑,也积累了一些经验,分享给大家:
1. 先 profiling,再优化
这是最重要的一点,性能调优一定要先做 profiling,找到瓶颈在哪里,再针对性地优化,不要凭感觉,不要上来就改代码,那样很可能白费力气,甚至越优化越差。
2. 从最大的瓶颈开始优化
优化要抓主要矛盾,先优化占时间最多的环节,这样投入产出比最高。比如我们这次,UI渲染占了800毫秒,是最大的瓶颈,我们先优化这块,很快就看到了明显的效果。
3. 不要过早优化,也不要过度优化
优化要适度,不要为了优化而优化,有些地方优化空间很小,却要花很大的精力,甚至会让代码变得复杂,难以维护,这样就得不偿失了。先优化那些投入小、收益大的地方,等主要瓶颈解决了,再考虑细节优化。
4. 优化要持续进行
性能优化不是一次性的,不是优化完了就完事了,随着功能的增加,代码的变化,性能可能会下降,所以要建立性能监控,持续关注性能,定期做优化,保持良好的性能。
5. 低端设备优先
做智能硬件,尤其是成本控制比较严的产品,一定要在低端设备上做优化,因为高端设备性能好,很多问题暴露不出来,只有在低端设备上,才能发现真正的性能问题。我们这次优化,就是在最低配的设备上做的,优化好了,高配设备自然就更流畅了。
6. 用户体验是最终标准
性能优化的最终目标,是提升用户体验,不是追求某个数字。有些优化,虽然数据上好看,但用户感知不强,就没必要花太多精力;有些优化,虽然数据提升不大,但用户感知很明显,就值得去做。始终把用户体验放在第一位。
九、写在最后
这次智能音箱带屏的性能调优,把响应时间降了80%,效果还是很显著的,也让我对性能优化有了更深的理解。
性能优化,是一个持续的过程,没有最好,只有更好。随着技术的发展,用户对体验的要求也越来越高,我们需要不断地优化,才能给用户带来更好的体验。
希望这篇分享,能给做智能硬件或者Android性能优化的朋友一些参考。如果你也有性能优化的经验或者问题,欢迎在评论区交流讨论。
最后,性能优化之路,道阻且长,行则将至,与大家共勉。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录