当前造车新势力越来越火,汽车智能化成为风口,很多手机应用希望拓展车机场景,云音乐及旗下 Look 直播也在车机端场景进行了一些探索,下面分享过程中的一些总结和心得体会
当前车载接入方式主要有三种,第一种是以华为 Hicar 为代表的手机app扩展接入,第二种是提供对外的 OpenApi,车企自行研发应用进行接入,最后一种是最为普遍的车机独立 app 接入。
这种方式并不要求给车机提供独立的车载版 apk,而是由手机端的应用接入 Hicar sdk,直接在原有的工程上开发。
目前多家手机厂商采用的车联方案都是基于 Android 系统自带的 MediaSession 框架进行模板化开发,手机端的应用只需要根据厂商提供的模板准备数据,具体的UI展示由车机设备完成,开发者无需关心屏幕适配及UI风格统一的问题,具体的播控指令同步也是通过 MediaSession 框架完成的。
该接入方式需要自己制定 Media Data Tree 的结构。因为 ViewTree 的展示是交给外部进行渲染的,我们往往只能通过 onPlayFromMediaId 回调里的 mediaId 和 extras 来获取车机上点击播放的媒体信息,mediaId可以构造成例如 tab -> page -> listId -> songId 的层级关系,我们就可以知道播放的是具体来自哪个页面中哪个歌单中的哪首歌了,这也是 Android 官方的 Universal Android Music Player Sample 中采用的实现方式。
这种车载接入方式有如下特点
这种实现方式是服务端根据我们的服务内容提供对应的 OpenAPI 接口。厂商可以自行设计需求方案和视觉方案,根据不同的需求范围去调用不同的接口来获取数据并展示,但是最后一般需要通过我们的审核才能发布。这种接入方式中的开发资源也是由厂商自己提供的,承载的平台包括 Linux、Android 等多个系统环境。由于这种方式不是本文的重点,就不在此赘诉了。
这种接入方式有以下特点
可以看出上述 OpenAPI 的实现方式还是存在一些比较关键的问题,所以一般来说我们会优先采用独立 app 接入的方式,这是目前更为普遍的方式,也是本文主要描述的接入方式。这种方式与手机应用开发其实类似,但也有一些特点
针对上文中提到的车载独立 app 开发的一些特点,我们在渠道分包、解耦车机依赖、语音操作接入、分辨率适配等方面进行了一系列探索,下面介绍几个相关的方案设计
上面提到车机系统比较碎片化,要实现车机的方控、桌面 widget、仪表显示等控制,一般有两种情况
考虑到上层业务代码最好能不感知平台差异,决定对渠道接入能力做一层封装隔离如上图所示,将渠道依赖的能力抽象为 EnvironmentDependency 接口,不同渠道依赖各自的车机 sdk 实现该接口,Mediasession 规范单独实现一个通用类。业务层看到的是渠道无关的DependcyWrapper 代理实例,只需在各业务处理时机调用代理的对应方法即可,规避了业务层写渠道相关的代码。方控响应能力抽象为 EventCallback 接口,业务实现后注入对应 dependcy 实例,由其适时触发。针对分渠道打包问题,采用 AGP 自带的 productFlavors 方案,不同的渠道包含不同的源文件夹,隔离 sdk 依赖。
flavorDimensions "channel"
productFlavors {
//小鹏
xp {
dimension "channel"
buildConfigField("String", "channel", "\"xp\"")
}
//比亚迪
byd {
dimension "channel"
buildConfigField("String", "channel", "\"byd\"")
}
......
}
要做语音控制,首先需要思考如下问题
解决了这些基本问题后,再来考虑下一个比较完善的语音助手的完整交互流程,助手唤起后,会首先进入询问态并提示语音支持的操作类型,接着用户输入,如果输入超时会提示助手即将关闭,正常输入后进行请求解析,获取结果后某些操作执行会直接关闭面板,而某些操作将直接在面板展示结果并回到询问态,若无法解析则直接提示并回到询问态,由此可见客户端上整个流程比较适合抽象为一个状态机
前置的视觉交互设计中,考虑到驾驶时的场景,常用的操作区域要尽量放在靠近驾驶侧的一边,同时交互流程要尽可能简单,页面跳转层级不宜过多。除去主流的横屏布局之外,比亚迪、小鹏等车机屏幕也会存在竖屏的情况。常见的屏幕适配方案包括 smallestWidth 适配、头条的修改 DisplayMetrics#density 方案、使用百分比布局等。结合项目的实际情况,我们建议大部分的布局都采用流式布局,只需要在布局中改变 recyclerView 的方向就可以适配横竖屏的切换,同时卡片布局尽量扁平化,ConstraintLayout 中的 Guideline、layout_constraintHeight_percent 等属性都能帮助我们很方便的实现百分比布局,如果遇到比例特别奇怪的屏幕,页面又不能使用流式布局时,可以考虑结合 sw 限定符的方案,让视觉同学给出布局调整策略,单独针对少量特殊的屏幕进行适配。在进行视觉适配开发时,我们的第一反应当然是让厂商提供所有可能涉及的车机设备,然而这是不现实的,从我们的对接经验来看,测试车机是相当紧缺的,部分厂商甚至连车机都暂时无法提供,只提供文档,让我们自行适配后再内部测试。在这种情况下,我们只能模拟不同的分辨率设备。adb shell wm size 命令就是解决方法,其接受 总长度像素值x总宽度像素值 格式的参数,运行后即可调整成对应的长宽比,测试过程只需要在同一设备上运行不同参数的命令即可实现不同分辨率的模拟。
上面提到车机相比于手机,总体性能上要落后很多。在一开始,一方面由于历史包袱、组件复用等因素,另一方面编写代码时也往往忽略了性能相关问题,使得 app 运行在车机上的体验相当糟糕,安装慢、启动速度慢、卡顿丢帧等性能问题很明显的就暴露了出来,于是我们做了一系列针对性的优化
减小包体积包括代码和资源两方面,通常的做法如下:
多进程运行需要占用更多的系统资源,在性能较弱的设备上,单app多进程的运行方式会给设备 CPU、内存等带来更多压力
和进程相似,线程过多在启动中频繁切换带来了很大的开销成本,主线程得到执行的时间也会减少
启动过程中文件 IO 过多也会拖慢启动速度,尽量减少不必要的文件读写
为了更快地展示界面或者执行某项具体功能,最好减少启动流程中 Activity 的跳转层级,每多一个 Activity 就会增加几百毫秒的耗时;在请求一些接口时,也要考虑到请求时机,是否可以前置并行请求,或者合并请求,减少接口的 RT
下面分享一个性能优化的实例,在与某家车厂的合作过程中,厂商反馈语音唤起阶段从冷启动到开始播放速度特别慢,将近 8s 之久。我们在手机上测试是完全没有问题的,但是受限于车机的性能,在前后反复数轮的沟通联调下,我们主要做了以下优化
最终将时间压缩到 3s 内,我们的优化过程从前期对耗时明显部分着重优化,效果明显,到后期分析启动日志,一点点抠细节,最终通过厂商方面的验收。在开始着手优化前,需要量化好具体指标,明确好目标再着手进行,用数据来衡量优化效果能让优化过程更加顺畅
车载开发过程中,还遇到了一些之前手机应用开发不常见的问题,印象深刻,也在这里分享下
车载场景,用户主动下载及更新 app 的频率相对手机来说要低很多,所以预装是很重要的铺量手段,但当我们好不容易与某渠道谈成预装后,却发现一个奇怪的问题,所有用 RN 实现的页面进入进入或者预加载就会引起应用的 crash,崩溃堆栈提示的直接原因是 libjsexcutor.so 这个 RN 依赖的 js 解析库加载失败了,于是初步看了下 RN 崩溃位置的源码,发现 RN 的 so 库都是通过 SoLoader 这个 facebook 的工具加载的(官方文档说主要用来兼容 4.3 以下版本的 so 加载依赖问题),而应用中其他业务 so 的都是正常工作的,所以就猜测 SoLoader 在应用预装场景会存在问题,于是复现并重点查看 Soloader 相关的日志上图为问题渠道上的 RN 加载日志,而下图为正常场景下的 RN 加载日志可以看到两者的区别就在于问题渠道上,标红处的 so 查找路径没有被添加(该路径实际就是应用安装后的 so 路径的软链接),而正常渠道上是在该路径上找到了 RN 相关的 so 并进行了加载,顺着该思路查看了下 SoLoader 的源码,发现有如下逻辑即判断当前应用是系统应用后,就不将 app 默认 so 路径加入查找路径,导致 RN 相关用 Soloader 加载的库都会失败,定位到原因后,再仔细过了下 SoLoader 加载 so 相关源码,发现其提供了 setSystemLoadLibraryWrapper 的设置接口,可以由上层来定义针对系统应用场景如何加载依赖的 so,所以我们只要设置该场景用应用原本的 so 加载方式即可解决问题,如下代码所示
SoLoader.setSystemLoadLibraryWrapper {
ReLinker.loadLibrary(context, it)
}
iptables -F
iptables -X
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
javax.net.ssl.SSLHandshakeException: com.android.org.bouncycastle.jce.exception.ExtCertPathValidatorException:
Could not validate certificate: Certificate not valid until Wed Dec 16
09:00:05 GMT+08:00 2015 (compared to Sun Oct 12 16:20:03 GMT+08:00 1980)
看起来是时间和证书有效期对不上,查看系统时间发现确实不对,原来该车机每次启动后都会重置系统时间,而 SSL 客户端的校验过程是包含证书有效期校验的,调整系统时间后即可解决问题 上述可见,测试车机会因为一些特殊设定而带来一些奇怪的开发问题,不过比较好的一点是这些测试车机往往是已经 root 过的,所以命令权限足够大,可以进行深入地分析。
参与车载应用从启动到正式上架的全过程,技术之外,还有一些其他的体会
本文介绍了目前车载开发的一些现状,分享了一些开发过程的设计思路和遇到的典型问题,希望能对大家的应用上车有所帮助!
本文发布自网易云音乐大前端团队,文章未经授权禁止任何形式的转载。我们常年招收前端、iOS、Android,如果你准备换工作,又恰好喜欢云音乐,那就加入我们 grp.music-fe(at)corp.netease.com!