如何把应用搬上车

本文做者:陆康、陈驰枻、聂帅前端

当前造车新势力愈来愈火,汽车智能化成为风口,不少手机应用但愿拓展车机场景,云音乐及旗下 Look 直播也在车机端场景进行了一些探索,下面分享过程当中的一些总结和心得体会java

目前车载开发的类型和特色

当前车载接入方式主要有三种,第一种是以华为 Hicar 为表明的手机app扩展接入,第二种是提供对外的 OpenApi,车企自行研发应用进行接入,最后一种是最为广泛的车机独立 app 接入。android

1. 手机 app 扩展接入,以华为 HiCar 为例

这种方式并不要求给车机提供独立的车载版 apk,而是由手机端的应用接入 Hicar sdk,直接在原有的工程上开发。git

目前多家手机厂商采用的车联方案都是基于 Android 系统自带的 MediaSession 框架进行模板化开发,手机端的应用只须要根据厂商提供的模板准备数据,具体的UI展现由车机设备完成,开发者无需关心屏幕适配及UI风格统一的问题,具体的播控指令同步也是经过 MediaSession 框架完成的。github

该接入方式须要本身制定 Media Data Tree 的结构。由于 ViewTree 的展现是交给外部进行渲染的,咱们每每只能经过 onPlayFromMediaId 回调里的 mediaId 和 extras 来获取车机上点击播放的媒体信息,mediaId能够构形成例如 tab -> page -> listId -> songId 的层级关系,咱们就能够知道播放的是具体来自哪一个页面中哪一个歌单中的哪首歌了,这也是 Android 官方的 Universal Android Music Player Sample 中采用的实现方式。shell

这种车载接入方式有以下特色api

  • 接入方便,直接在原有工程基础上开发,基础能力是现成的,交付形式为手机apk,与原来保持一致
  • 适配方便,例如 Hicar 针对不一样类型的应用,直接提供了模板化开发的能力,音频应用只需专一于音频数据的准备和播放服务的实现便可,其它繁琐的工做,例如绘制车机界面并保证各分辨率兼容性、管理音频桌面卡片和实现音频任务接续等都由 HiCar 完成
  • 更新方便,只须要手机上的应用更新了便可更新车机展现逻辑,相比更新车机应用,引导用户的成本低很多
  • 适用范围的局限性,即只与特定平台绑定,好比 Hicar 只支持华为手机,而且要求车机接入了华为 Hicar 系统,目前来讲,国产的几家主流手机厂商都在尝试推相似的生态,汽车厂商在互联网造车的势头中,也加快了这些系统的引入,但从总量来讲,仍然属于车机中的少部分

2. OpenAPI 接入

这种实现方式是服务端根据咱们的服务内容提供对应的 OpenAPI 接口。厂商能够自行设计需求方案和视觉方案,根据不一样的需求范围去调用不一样的接口来获取数据并展现,可是最后通常须要经过咱们的审核才能发布。这种接入方式中的开发资源也是由厂商本身提供的,承载的平台包括 Linux、Android 等多个系统环境。因为这种方式不是本文的重点,就不在此赘诉了。性能优化

这种接入方式有如下特色markdown

  • 我方投入的人力成本小,主要开发成本集中在厂商那一边
  • 能够适配各类环境,并不局限于某一种车机系统
  • 可控性较小,数据的获取有一部分依赖于厂商提供,我方只能拿到接口调用次数,在涉及到结算的问题上容易产生分歧
  • 迭代困难,依赖于厂商自身的开发资源

3. 独立 app 接入

能够看出上述 OpenAPI 的实现方式仍是存在一些比较关键的问题,因此通常来讲咱们会优先采用独立 app 接入的方式,这是目前更为广泛的方式,也是本文主要描述的接入方式。这种方式与手机应用开发其实相似,但也有一些特色网络

  • 车机系统的碎片化相好比今比较成熟的手机生态(绝大多数份额在头部厂商)更加严重,不少厂商基于 Android 研发本身的车机系统,针对方控、桌面 widget、仪表显示等设备依赖能力,厂商每每都会提供本身的一套接入 SDK,因此渠道分包势在必行
  • 车机应用的交互要求简洁,突出重点,应用支持语音操做对于用户来讲会是很大的吸引点
  • 测试车机设备比较缺少
  • 系统版本跨度较大,目前接触到的设备从 Android 4.3 能够一直覆盖到 Android 10
  • 性能通常较为羸弱,在开发时要格外注意性能的瓶颈

方案设计

针对上文中提到的车载独立 app 开发的一些特色,咱们在渠道分包、解耦车机依赖、语音操做接入、分辨率适配等方面进行了一系列探索,下面介绍几个相关的方案设计

1. 多渠道接入能力抽象

上面提到车机系统比较碎片化,要实现车机的方控、桌面 widget、仪表显示等控制,通常有两种状况

  1. 厂商的相关操控实现了 Android 原生的 MediaSession 规范,这种状况下咱们要响应相关的 KeyEvent,并在各类播放相关时机调用 MediaSession api 更新状态
  2. 厂商为相关操控提供了 sdk 接入,这种状况下咱们要按照厂商自定义的规范来

考虑到上层业务代码最好能不感知平台差别,决定对渠道接入能力作一层封装隔离 如上图所示,将渠道依赖的能力抽象为 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"")
        }
       ......
    }
复制代码

2. 语音控制的设计实现

要作语音控制,首先须要思考以下问题

  1. 是应用本身实现仍是使用车机能力?

从对接经验来看,目前提供车机语音开放能力的厂商并不广泛,个别厂商即便提供,其接入和自定义流程也比较复杂,须要至关长的周期,因此应用本身集成三方sdk来实现是更合理的选择,可是针对于一些须要支持车机自带语音助手的厂商咱们也要提供出对应的方案 2. 语音控制如何唤起?(除了页面点击外,可否提供其余快捷入口) 若是要实现特定短句唤起语音助手,就要求语音识别 sdk 在应用生命周期内长期收音,一直抢占着 mic 焦点,致使车机系统自带的语音助手没法工做(有个别车机实现了多麦克风阵列,即系统收音使用单独 mic 通道,但这种车机是极少数),所以,短句唤起方案是行不通的。那么,可否借助方控呢?方控广泛能提供确认键的响应,若是应用业务自己不须要确认键(如应用为直播业务,不须要暂停、恢复)则可直接使用确认键唤起语音助手,若是须要,也能够设计某种点按方式唤起(好比长按或者双击,这能够经过在业务层判断按键事件的时间间隔作到),固然,对应的引导也须要跟上,好比在用户首次进入时展现浮层加语音的引导 3. 如何从语音识别出的文字映射到对应操做?最方便的作法确定是客户端直接判断文字匹配性,好比识别到“下一首”就切换到下一个直播,可是这种作法容错性较低,用户稍微调整下说法就会失效,更加合理的作法是在语音转文字环节后再加上语义识别环节,流程以下

解决了这些基本问题后,再来考虑下一个比较完善的语音助手的完整交互流程,助手唤起后,会首先进入询问态并提示语音支持的操做类型,接着用户输入,若是输入超时会提示助手即将关闭,正常输入后进行请求解析,获取结果后某些操做执行会直接关闭面板,而某些操做将直接在面板展现结果并回到询问态,若没法解析则直接提示并回到询问态,因而可知客户端上整个流程比较适合抽象为一个状态机

  1. 若是须要对接不一样的车机自带的语音助手,涉及到控制相关的指令和播放信息的回调须要抽离出更为广泛的接口去实现,对于常见的指令,好比播放、暂停、上一首、下一首、收藏、搜索点播等须要封装成独立的方法,不一样的车机的 app 注册不一样的 server,客户端的实现则由同一个 client 处理,同时能将客户端处理后的结果返回给 server 端进行展现,这样作的好处是与车机对接的部分彻底交给 server 进行处理,client 只须要根据下发的指令进行对应的操做便可,前半部分是解耦的,后半部分是复用的

3. 多分辨率适配

前置的视觉交互设计中,考虑到驾驶时的场景,经常使用的操做区域要尽可能放在靠近驾驶侧的一边,同时交互流程要尽量简单,页面跳转层级不宜过多。除去主流的横屏布局以外,比亚迪、小鹏等车机屏幕也会存在竖屏的状况。 常见的屏幕适配方案包括 smallestWidth 适配、头条的修改 DisplayMetrics#density 方案、使用百分比布局等。结合项目的实际状况,咱们建议大部分的布局都采用流式布局,只须要在布局中改变 recyclerView 的方向就能够适配横竖屏的切换,同时卡片布局尽可能扁平化,ConstraintLayout 中的 Guideline、layout_constraintHeight_percent 等属性都能帮助咱们很方便的实现百分比布局,若是遇到比例特别奇怪的屏幕,页面又不能使用流式布局时,能够考虑结合 sw 限定符的方案,让视觉同窗给出布局调整策略,单独针对少许特殊的屏幕进行适配。 在进行视觉适配开发时,咱们的第一反应固然是让厂商提供全部可能涉及的车机设备,然而这是不现实的,从咱们的对接经验来看,测试车机是至关紧缺的,部分厂商甚至连车机都暂时没法提供,只提供文档,让咱们自行适配后再内部测试。在这种状况下,咱们只能模拟不一样的分辨率设备。adb shell wm size 命令就是解决方法,其接受 总长度像素值x总宽度像素值 格式的参数,运行后便可调整成对应的长宽比,测试过程只须要在同一设备上运行不一样参数的命令便可实现不一样分辨率的模拟。

性能优化

上面提到车机相比于手机,整体性能上要落后不少。在一开始,一方面因为历史包袱、组件复用等因素,另外一方面编写代码时也每每忽略了性能相关问题,使得 app 运行在车机上的体验至关糟糕,安装慢、启动速度慢、卡顿丢帧等性能问题很明显的就暴露了出来,因而咱们作了一系列针对性的优化

1. 减少包体积

减少包体积包括代码和资源两方面,一般的作法以下:

  • 图片压缩
  • 资源混淆
  • 减小 Dex 数量

2. 减小进程数

多进程运行须要占用更多的系统资源,在性能较弱的设备上,单app多进程的运行方式会给设备 CPU、内存等带来更多压力

3. 减小线程数

和进程类似,线程过多在启动中频繁切换带来了很大的开销成本,主线程获得执行的时间也会减小

4. IO优化

启动过程当中文件 IO 过多也会拖慢启动速度,尽可能减小没必要要的文件读写

5. 减小Activity的跳转次数

为了更快地展现界面或者执行某项具体功能,最好减小启动流程中 Activity 的跳转层级,每多一个 Activity 就会增长几百毫秒的耗时;在请求一些接口时,也要考虑到请求时机,是否能够前置并行请求,或者合并请求,减小接口的 RT

6. 优化布局层次,减小过分绘制

下面分享一个性能优化的实例,在与某家车厂的合做过程当中,厂商反馈语音唤起阶段从冷启动到开始播放速度特别慢,将近 8s 之久。咱们在手机上测试是彻底没有问题的,可是受限于车机的性能,在先后反复数轮的沟通联调下,咱们主要作了如下优化

  • 大幅减小包体积,删除大量无用业务代码,包体积减小约 80%,因包体积大幅减少,启动过程须要解压的dex数量也相应减小,加载的类变少,速度有数秒提高
  • 将播放进程合入主进程,多进程改成单进程,并去除 aidl,去除 aidl 通讯先后的几回文件读写,减小约2s左右耗时
  • 将 LoadingActivity 和首页 Activity 合并为一个,减小启动链路过程 activity 的数量,减小约数百毫秒耗时
  • 将多个接口合并成一个减小网络请求,减小约 200 毫秒耗时

最终将时间压缩到 3s 内,咱们的优化过程从前期对耗时明显部分着重优化,效果明显,到后期分析启动日志,一点点抠细节,最终经过厂商方面的验收。在开始着手优化前,须要量化好具体指标,明确好目标再着手进行,用数据来衡量优化效果能让优化过程更加顺畅

踩坑指南

车载开发过程当中,还遇到了一些以前手机应用开发不常见的问题,印象深入,也在这里分享下

1. 上了预装,RN页面咋都不行了

车载场景,用户主动下载及更新 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)
}
复制代码

2. 车机测试设备上的奇怪问题

  1. 某个渠道的测试车机连上公司 wifi 后,始终没法访问网络,与厂商沟通,他们告知也是首次提供测试车机给外部,内部使用是没问题的,因而只能本身定位。考虑到大几率与网络环境有关,遂用 iptables 工具查看车机网络规则( iptables 是运行在用户空间的应用软件,经过控制 Linux 内核 netfilter 模块,来管理网络数据包的处理和转发,数据包的详细流转流程以下图所示,能够在各个环节增长规则来拦截)

查看后果真发现部分规则比较特殊,猜想是测试车机原本是只给厂商内部使用的,为了防止流出后产生问题,对网络环境作了识别,一旦发现非厂商公司内网就丢弃数据包,因而用以下命令清理规则,问题解决

iptables -F
iptables -X
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
复制代码
  1. 某个渠道的车机,开发过程发现部分接口报错。仔细看了下,发现报错的接口都是 https 协议(开发阶段还在测试环境,大部分接口是 http 协议),adb 日志里看到的报错内容大体以下
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 过的,因此命令权限足够大,能够进行深刻地分析。

技术以外的体会

参与车载应用从启动到正式上架的全过程,技术以外,还有一些其余的体会

  • 车厂项目管理和互联网产品有较大区别,其做风比较严谨细致,求稳不求快,没有互联网快速迭代的理念,每每不太能接受部分问题先带上线后续迭代 fix 的作法,因此其测试周期一般比较长,问题反馈轮次较多,反馈问题的角度也比较多样(产品设计、内容运营、技术点),应用方须要有心理准备,耐心处理。
  • 在与车厂初步沟通时,就要对齐好交付标准,好比适配的需求范围、应用的性能指标等等,避免由于交付标准的不统一形成来回的沟通和返工,根据咱们自身的项目状况也要制定好本身的标准基线,平时经过 Monkey 和性能自动化测试保证 app 的稳定性
  • 目前各车厂接入 app 的总体流程还不能说很完善,存在文档欠缺、测试车机欠缺、模拟器不稳定、反馈问题响应较慢等问题,这就要求应用方早作功课,对依赖项要尽早梳理,和厂商及时沟通,预知风险,后面随着应用接入愈来愈广泛,厂商这块的建设应该会有改进。

小结

本文介绍了目前车载开发的一些现状,分享了一些开发过程的设计思路和遇到的典型问题,但愿能对你们的应用上车有所帮助!

本文发布自 网易云音乐大前端团队,文章未经受权禁止任何形式的转载。咱们常年招收前端、iOS、Android,若是你准备换工做,又刚好喜欢云音乐,那就加入咱们 grp.music-fe(at)corp.netease.com!

相关文章
相关标签/搜索