UC-Weex的优化实践之路
Posted GoCN
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UC-Weex的优化实践之路相关的知识,希望对你有一定的参考价值。
大家下午好!我是来自阿里巴巴移动事业群 UC 研发中心的龚攀峰,今天我带来的分享主题是 UC-Weex 的优化实践之路。
先简单介绍一下背景,先聊下传统移动端开发的一些痛点。首先是客户端发版的节奏相对偏慢,整个流程比较重。其次成本非常高,客户端的动态能力弱导致覆盖慢。第三个痛点是现在团队的业务需要多端开发,安卓、ios,也有可能是 H5,这就带来了很高的研发成本。所以我们这边期望的方向,第一个是如果能够统一技术栈,这样可以降低我们的开发成本。第二个是希望业务发布具备 H5 的动态更新能力,而且在体验上跟原生的体验是相媲美的。
从2015年开始,我们一直在朝着这个方向去探索,从早期的 React Native 到后面的 Weex,都做过比较多的实践。这里主要比较一下 React Native 和 Weex 的一些差异,它们很多点事比较类似的,例如布局系统,开发语言,测试手段,还有发布模型。我们最终选择了 Weex,核心的几个点:第一个是因为 Weex 是把 Web 这一块也统一了,安卓、IOS、Web,能够做到一次编写多端运行。第二个原因是它的这种技术栈更偏向于 Web 化,跟我们之前期望的目标相对来说是一致的。
说完技术选型,简单说下 Weex 的整个技术架构。整个技术架构相对来说还是比较简单的。首先在上层,我们会基于 Vue 框架开发,当然你也可以选择其他框架进行开发。然后我们会通过转化器去转成一个 JS Bundle 的文件,通过服务器去下发到客户端。在客户端通过 JS 引擎跟端做交互,利用端的渲染能力完成整个渲染。这种架构第一个好处统一了整个上层的技术栈,能够做到一次编写多端运行。第二,因为它本身运用了客户端的渲染能力,所以它跟原生的体验是一致的。
整个核心方向是比较美好的,跟我们预期也是一致的,但是它的现状能力却没有办法支撑我们一些大型的复杂场景。所以我们是希望通过不断的实践去拓宽整个 Weex 的能力边界,完善整个生态。
接下来会从三个层面具体展开讲我们做的一些事情。首先刚刚提到的 Weex 或者是 React 这种技术架构,它解决了开发效率的问题,因为它跟 H5 的开发体验其实是一致的。但我们评估整个效能,其实是要去评估整个全链路的,我们希望把工程化的一些配套能够搭建起来,真正提升全链路的效能。
这个是早期核心的发布链路,在开发阶段创建工程,做本地开发和静态检测。然后本地打包、真机验证,生成的产物上传到静态资源服务器,最后通过配置下发,客户端去检测更新。
其实这里面各个环节都会存在一些问题,在开发阶段缺乏自动化保障的手段,整个打包缺少一些版本管理的机制,这样会出现一些安全的问题。在发布这一块,整个能力其实也是非常欠缺的,而且整个过程缺乏监控跟度量。
这是我们改造过之后现在发布的一个流程,首先我们是搭建了前端的工具流平台,在这里可以对我们的分支定期做核心性能的测试,包括内存、FPS测试,这样帮助我们提前发现一些问题。在发布环节,我们可以通过前端的平台针对分支打包,并上传到下载升级平台。
除了这个之外,我们能够同步提交升级策略。所谓的升级策略就是说我们发布这个JS文件,希望把它精准的投放到目标用户去进行升级,这些升级维度例如:根据版本,根据地域或者是说根据精准的用户画像去做下发。做到这一块需要客户端这边的支撑,客户端会做一些更新的检查,这个升级请求会在下载升级平台根据用户画像去在当前所有的升级策略中匹配到最优解。
有了这套链路之后,我们有比较强的控制能力。举个例子,我们现在这种灰度的发布,之前就是直接全量下发了,出现问题影响面非常大。现在我们可以根据平台设置自己的控量范围,比如说我们设置一个2万用户的灰度,我们可以设置5000、10000、20000 这样三个放量阶段。这样在发布的过程中,到了某个阶段的升级量,升级下发会暂停,系统会去分析当前异常日志,如果是OK的,整个链路会接着跑下去。如果说中间有些异常问题会触发报警,这样就能保证整个灰度通道是比较高效和安全的发布。除此之外,在这里面也可以去做A/B Test等更多的事情。
之前很粗的分析过一个数据,以某个垂直类项目为例,开发到上线时间相对比我们之前的开发模式提升了1倍以上,而且开发到上线是覆盖双平台。上线之前,我们即使只有3个业务,支撑也比较困难。上线之后,服务了10个以上的垂直大业务,而且每周的发布将近30多次,整个运转是比较良好的,而且每天的覆盖率也是可接受的水平,超过了85%以上。
说一下核心性能的持续优化。我们知道类似 Weex 这种框架,它的核心其实是 JS 引擎,浏览器也是具备这种JS引擎能力的,在这一块的积累可以帮助我们去做更多的事情。
先说一下 Framework 的瘦身之路。Weex 本身的 size 是差不多3M,当然 React 可能更大。3M对于我们来说是一个非常庞大的数字,我们有必要去做一个瘦身的优化。首先我们采用的是一些常规裁剪手段,针对V4、V7做一些功能的裁剪。我们发现这一块即使做得再极致,也带来不了多大收益。因为整个 Framework 70%以上是JS引擎。
基于另外一个背景,本身UC浏览器,其实自带JS引擎,所以我们在思考能不能抛弃这种自带的引擎,把浏览器本身的V8分离出来,然后去做共享。整个思路是可行的,下面V8共享方案的核心流程。
第一步,我们把 UCWebView 本身内核的V8进行分离,就是下面的UC V8,第二步我们需要对整个SO加载这一块进行改造,支持外部的JS引擎加载。第三步我们把UCV8的路径添加到共享库。做完这三步之后,我们发现还有一个问题,V8的初始化,因为依赖于浏览器内核的一些特殊的设置,导致我们如果想用V8的话需要提前用浏览器做一些初始化的设置。
我们的解决方案是,首次会通过 UCWebView 去初始化 UCV8 的 JS 引擎,然后把整个 V8 的SO区做一个拷贝和重命名。这样就能够独立去加载V9这个JS引擎来达到我们的目的。结果是非常明显的,Size直接从3M以上降到500K。但是它的缺点也是比较明显,它依赖于UC内核初始化,不利于维护且影响首次启动时长。第三个非常重要在于非UC系产品没办法使用,对这套方案的推广非常有限。
我们开始考有没有其他的解决方案。在安卓平台,WebView 本身也是天然支持JS的执行,所以我们想有没有可能把它的JS执行能力利用起来,但是这里面又会有比较多的挑战。第一,我们没有办法使用系统 WebView 的Js引擎,没有办法直接去调用Js引擎的一些代码。第二,本身系统 WebView 对调用接口能力有限。第三就是安卓平台不同ROM的兼容性,特别是在4.4内核做了一次大的升级,这些都是方案的瓶颈。我们做了一些预研,首先把引擎关键的流程做了分析。发现类似这种框架,关键点无非四步。第一步,在JNI端通过V8构建整个上下文环境。第二步,加载 JS Framework 做初始化。第三步,加载 JsBundle 创建页面。第四步,构建 Native-JS 双向通道,用来做消息的交互。所以如果我们能用系统 WebView 替换掉这四步,核心的方案是可行的。
这就是我们核心的解决方案。旧的这种模型可以看到左边黄色的这块,构建整个上下文加载框架以及加载,都是通过本身的一个Bridge去使用JS引擎,当然这可能是V8,也可能是JSC,去做整个JS的加载。JS到Native之间的这条链路,其实通过在V8会去注册一些全局函数进行映射,把这条链路去打通。
我们的替换方案,首先我们不能用独立的JS引擎,我们只能用替换的 WebView。首先第一步,构建整个上下文,初始化以及加载整个JsCode,我们都可以看成是代码执行,通过这种接口去完成了替换。还有一步要解决的,就是从JS到Native的一个通道。其实分为两步,第一步构建Native的一个Bridge,然后把上层的Native接口去打通,通过外部去做注入。第二步,在JS层通过一个 JSBridge,把向上的 callNative 这个通道对接到 NativeBridge,这样就完成了前面四步的操作。
看代码会更清晰一点,左边是旧的流程,大家可以看到它直接通过V8去构建整个数字包的环境,去加载整个 JSFramework,右边其实就是替换的方案,转换为一个JS代码,交给 WebView 去做执行。这个是JS到Native通道,上面可以看到它会做全区函数的注册。下面就是我们的替换方案,左边是NativeBridge,右边是JSBridge,会通过NativeBridge去做打通。
做完这些整个核心链路是通的,但是我们也遇到了其他的一些挑战。第一个就是兼容性的问题。从安卓4.0到7.0,目前已经解决了,主要解决的一些问题: 一是JS引擎对一些特性的支持不太一致;二是我们发现在4.4以下ROM有比较多兼容性的问题。第二个是调试能力,目前也是支持的,我们通过对接远程调试协议,完成远端的调试。第三个是异常捕获,因为原先的方案其实很简单,通过V8就可以拿到整个全局的异常,在这一块我们是通过对顶层JSFM进行捕获。
这套方案的结果,首先是完全丢弃了独立的JS引擎,使接入size降到了500KB左右,所有APP都可以接入使用。但是它有一个隐患,因为它是使用系统 WebView 控制的,所以稳定性上我们并没有很强的控制能力,所以我们也再思考有没有优化点。
在前面讲到我们做完V8的共享之后,已经支持了动态引擎的加载,结合前面的系统 WebView 以及动态加载JS引擎的能力,我们能够提供一套比较完整的解决方案,我们会默认使用系统 WebView 去做JS引擎,然后去做JS引擎的动态更新。更新后,会做一个替换,这样在稳定性这方面也是能够得到控制的。所以整个来看,这个是一套大幅度对 Framework 瘦身的方案。
另外大家比较关心的就是秒开率。早期我们的秒开率是非常差的,只有40%。我们针对整个链路做了一些梳理,整个分为三层。第一层是整个JS文件的获取,第二层是框架加载,第三层是业务的加载。
第一层,现在获取的方式比较简单,网络或者本地内置。比较常规的方案是采用缓存的这种方案。在缓存的基础上,我们通过工程平台做预下发。能够针对指定用户去做下发,可以根据画像去圈用户,对我们想下发的用户去做预下发,所以能够达到省流量的效果。
第二块较简单,就是在JS引擎下载、JSFM下载和资源I/O读取,我们可以去做一些预加载和并发的处理。
最后一层是业务加载。业务加载涉及到JS的执行和渲染。其实我们做了很多事情,第一个我们对V8 CodeCache编译期的结果做缓存,主要是节省编译的开销。第二个是对业务框架做预加载,这个是针对具体的业务,其实是运行期的一些缓存,这一块我们并不会去做真实的UI创建以及跟Native之间的交互,只是加载了整个业务框架。
整个这一套做下来,秒开率从40%到了80%以上,当然也并不是一个理想的数据。接下来我们做了两件事情。第一个我们发现 JS Bundle size 对整个加载速度影响非常大。所以我们会针对性做一些瘦身。第二就是随着这种业务迭代的发展,我们发现整个模块越来越复杂,所以在业务拆分以及模块化这方面是不得不做的事情。
最后一块是业务服务能力提升。业务服务能力提升是这样的,Weex 适合单页面的场景或运营页面,但是我们的场景是什么?我们的场景是一些垂直类的业务,它的业务形态相对来说比较复杂,本身有非常多的子页面,包括有一些非常复杂的交互,这也是我们面临着比较巨大的挑战。
首先是针对单页面的场景,我们做了单页面路由的解决方案,它主要解决的是能够在一些单独的页面之内做一些子页面的跳转。第一套方案是在JS去实现,但是它的缺点非常明显,因为它没办法用 Native 的窗口机制能力,所以体验是比较差的。当然好处是动态性比较强。2.0方案我们做了一些修改,结合Native的窗口能力,包括手势系统以及过场动画,来达到跟窗口一样的体验。当然缺点是迭代更新需要重新发版,这并不是一个很大的问题,因为本身这种窗口之间的切换是比较稳定的行为。
然后是三端通信能力,加入了 Weex 这种新的形态之后,业务形态变得比较复杂,所以会有Native、Web、Weex。对我们业务场景来说是一个机会,因为能够结合他们自身的特性,去创造一些新的业务场景,这就需要我们把整个通道打通。早期我们在 Web 跟 Native 之间通过 JSDK 把通道打通。在 Weex 进来之后,通过它本身的模块,JSBridge 跟 Native 通道做一个打通,然后我们又借助 Native 的 Bridge 把 Weex 跟 Bridge 也打通了,这样就能够完成整个链路的搭建,方便我们去做结合场景的尝试。
当然我们还做了其他能力的增强,在基础组件方面,比如SVG、富文本组建、Web组件等支持。针对动画能力,接入了 Canvas、Lottie 的支持。在复杂交互方面遇到比较多的瓶颈,我们针对这种类信息的场景,做了类似于上推下拉组件以及其他支持多Tab栏的基础组件。页面跳转支持单页面内路由能力。外部通信支持三端通信能力。H5降级考虑到IOS审核的风险,我们具备整套降级H5解决能力。
接下来举一些典型的业务场景,我列了三个场景。第一个场景是一个垂直业务,首先它具备非常丰富的富文本组件,富文本视频,各种各样基础的文件支持。它本身是有一些复杂的交互,可能对 Native 层面来说,它并不是一个非常复杂的交互,但是对于类似这种动态框架的话,它是一个比较复杂的交互。第二个场景是一个类似于弹窗、对话框的场景,大家可以看到上面这一层是可以实现的,底下是一个H5的页面,H5跟 Weex 之间是有一些交互的,所以我们在前面三端通信打通之后,能够轻松地去实现这种场景。第三个,大家可以看到这个红色的方框是 Weex 的,其他的是Native实现的,能够针对一些局部的UI卡片去做这种尝试。
除了上面三部分,我们也在继续探索能力的提升。第一块就是新的通信架构的设计,因为Weex在解决类似手势交互这种场景会遇到比较大的瓶颈,我们希望能够有一些新的解决方案去解决这些问题。第二个是业务模块化的解决方案。第三我们会继续去做一些新业务场景的探索,继续拓宽它的整个能力边界。
以上是关于UC-Weex的优化实践之路的主要内容,如果未能解决你的问题,请参考以下文章