Weex在千牛开放中的应用实践
更多深度文章,欢迎访问云计算频道:https://yq.aliyun.com/cloud
摘要:在2017年1月12日 Weex Conf 2017上,阿里巴巴商家事业部无线千牛团队的无灵结合阿里巴巴无限商家端的实际业务分享了Weex在千牛开放中的应用实践,本文分享了面对业务的各种挑战,无线千牛团队是如何一步步转向Weex的,以及在实际过程中遇到挑战和所做的努力。本文是无灵关于Weex在千牛开放中的应用实践的分享整理。
本文整理自演讲嘉宾的分享视频以及PPT。
本次分享将主要介绍Weex在千牛开放平台中的一些应用实践,今天分享的内容主要分为以下四个部分:
千牛当前的业务场景与挑战
为什么选用Weex作为解决方案
基于Weex解决方案,我们还做了什么
目前isv使用改造后的效果的前后对比与当前的进展
首先来介绍一下千牛当前的业务场景。大家可能对于千牛的客户端不太熟悉,其实千牛是一个面向商家的移动客户端。千牛主要是面向淘宝商家的一站式管理店铺以及办公的工作平台,下图中的右侧图片就是千牛客户端的一张截图。
淘宝的商家可以在千牛平台上进行客服接待,管理自己的店铺,以及看数据报表等,最重要的一点就是商家还可以完成与自己店铺相关的所有的工具链路的支撑,比如交易管理的工具以及服务、商品管理的交易和服务等。这些服务与管理都是由千牛平台的isv提供的,因为isv能够根据用户的行为为用户提供多种多样的定制化服务,所以这部分我们交给了isv去实现。这样就基本构建起千牛的整体工作平台。为了实现这样的一站式工作平台,千牛提供了开放的插件体系来帮助isv开发出更加丰富的工具。除此之外,千牛平台还对于用户进行了画像,用户将根据画像被归纳到不同的用户域以及不同角色下面去,实现使得不同的用户所看到的工作台都是不同并且动态变化的,并且将为不同的用户提供不同的功能。
接下来解释一下插件的应用场景。千牛是一个数据驱动的平台,当淘宝的一条交易订单的数据发生变化之后,系统就将会收到一条消息,这条消息就将推送到千牛的客户端,之后客户端的数字区域就会发生变动。当用户发现有在客户端上有数字发生变动或者接收到了新消息的话,通过点击数字或者消息就可以跳转到isv的插件中去,之后就由插件完成由isv提供给用户的一些具体的工作,比如查看订单的具体详情,或者复制订单的发货的具体地址以及修改商品价格等一些相关的功能,这些功能都是在由isv提供的插件的内部完成的。在插件完成业务的处理之后,将会跳转回千牛的模块,进行比如像旺旺的接待或者沟通,在接待完成之后再跳转到isv的插件里面继续进行处理。而对于用户而言,看到则是整体的对于千牛客户端的操作流程,但是在千牛内部其实是先从千牛跳转到isv的插件应用,之后再跳转回千牛这样的一个分为三步的动作。所以isv所提供的插件的稳定性和用户体验的一致性都将会是一个需要我们关注的问题。
接下来是对于多角色多用户域的展示,通过下面的两张图片可以看出,千牛的工作平台针对不同的用户提供的服务和展现的界面是不一样的。在所有的工作台里面,在所有的O2O场景下,可能用户看到的是像左边这样图片的界面,在这样的界面里不会有太多的数据,而且将会突出其比较重要的工具。而对于企业的老板而言,更关心的可能是企业的订单数量等的与企业业务相关的数据。所以对于不同的用户域以及不同的用户角色而言,千牛的客户端都是需要进行动态构建的。
那么在使用Weex之前,千牛提供的是什么呢?其实之前,我们将平台分为了四层,首先是设备能力层,也就是将千牛的设备能力,比如拍照、网络、存储以及定位等基础的能力通过容器层进行开放。将这些能力开放给isv,之后通过容器层将isv的业务代码与千牛的代码进行相互沟通并呈现出来。再上一层则提供了MSUI组件库和离线包系统,离线包与之前提到的资源预加载的东西差不多。最上层就是千牛的业务能力层,在这一层会将千牛所能够提供的全部的商业能力进行了抽象并且以API的方式进行输出。这样一来,isv就可以通过API的调用来使用千牛官方所提供的能力,比如像改价、退款、支付以及旺旺接待等功能。
千牛所面对的挑战又是什么呢?其实大家都知道,在H5的开发方式下需要面对几个比较大的挑战,一个挑战是网络,网络对于isv的影响是巨大的;另外就是动画,其实isv比较难于实现一些与native相近的交互性的动画;第三个挑战就是安全问题,因为针对于一些运营商的劫持问题将会极大地困扰isv。除此之外,isv本身存在的问题也是需要面对的挑战,因为isv自身水平也是有高有低,参差不齐的,比如有一些isv能够将网络和动画问题解决得更好一些,能实现接近于native的交互式用户体验,而对于技术差一些的isv则往往不能实现。
另外一个方面的挑战就是isv的发布问题。因为isv可以对于自己的插件直接在服务器端进行发布。也就是说isv一旦入驻千牛平台,那么它的插件想更新什么就可以更新什么,如果isv在自己的插件中挂一个广告或者活动页面,即便是极大地损害了用户体验,但是平台也对其没有太大的管控力和约束能力。所以对于千牛平台而言,如何提高对于isv的管控能力也是非常大的挑战。另外就如同之前展示过的,千牛平台的动态性也是非常强的,所以需要针对于不同的用户域去展示不同的用户形态。以上这些就是千牛需要面对的挑战。
那么我们为什么选用Weex呢?其实在接入Weex之前,我们也进行了一些在React Native上面的尝试。在React Native方案提出之后,我们认为这个方案非常好,能够在很大程度上解决我们当时的开发方式所带来的问题。所以在2015年11月份的时候,我们就开始对于自身的业务代码进行试用,经过验证发现能够满足在比如长列表加载速度等问题上面的要求,所以在2016年3月份的时候就开始尝试封装起来,并且将框架提供给isv。但是在开发和接入的过程中,不断地暴露出了问题,所以最终不得不切换了跑道,投入了Weex的怀抱。
接下来和大家谈一谈我们在React Native里面遇到的问题。第一个是性能问题,随着业务的逐渐复杂,Bundle包的大小将会成为比较大的问题,而且Bundle包会不断地进行膨胀,所以需要花费大力气去进行优化。更可怕的是Bundle包的大小会直接影响加载的速度,我们曾经做过一个对比试验:当业务足够复杂,Bundle包足够大的时候,首次加载的时间甚至要比一个Webview加载的时间还要长。另外就是React Native自身的问题,比如像是核心组件Listview中的cell复用的问题将会影响内存开销以及性能。当然今天看来可能这些问题都有了一些解决方案,但是在当时对于我们而言,的确是一个巨大的挑战。
React Native的另外的一个问题就是内存消耗,因为每个插件都是独立的环境,所以需要对于实例进行频繁地创建和销毁,所以对于内存的消耗将会是巨大的。
第三个问题就是端的差异性。React Native所强调的是Learn Once而不是Write Once,所以也就是说无法让isv去关心平台上的差异,消除端差异的工作只能交给平台自己去进行处理,但是这些问题都不是不可以逾越的。而最终让我们放弃React Native的是它的Breaking Change问题,因为React Native是一个发展非常迅猛并且更新频率非常高的框架,在我们接入的那个阶段,React Native基本上一到两周就会更新一次,而两次更新基本上就会出现一次Breaking Change问题,而这个问题将会给我们带来非常大的挑战。因为如果只是使用React Native开发自己的业务,可能不会出现太大的问题,但是使用React Native来构建平台供其他的isv使用,那么Breaking Change问题将会使得开发者非常头疼。如果每次变化都需要通知平台所有的isv进行版本更新或者升级,这个成本也将会是无法承受的。
因为以上的原因,Weex走进了我们的视野。Weex吸引我们的有以下几点,首先是版本升级的向前兼容,Weex团队承诺框架的向前兼容性。第二点就是Weex具有多端的一致性,这样就不需要我们自己再去处理多端的一致性问题了。Weex的第三个优点就是轻量,并且支持能力的可插拔,而且具有便利的拓展性,Weex提供了基础的能力,但是这些能力并不一定能够满足每个业务场景。而且对于像千牛这样的面向商业的业务场景而言,需要有更多的面向自身的业务上的考量,所以这个框架是否是能够非常方便地替代,是否能提供基础的能力以及拓展能力是我们非常看重的,而Weex在这块做的非常好。另外一方面就是Weex解决了React Native自身的一些性能问题,比如Bundle运行环境的共享,Listview的复用问题也解决的非常不错。最后一方面就是Weex能够灵活地支持多种前端框架,而且不需要切换前端框架,因为我们之前在React Native上做了大量的工作,如果不能实现平滑过渡的话,对我们而言也会造成很多麻烦,而Weex却能够帮助我们平滑地进行迁移。
至于我们借助Weex做了些什么工作,其实在QAP2.0时代,我们主要在三个方向上开展工作。第一是针对插件APP打开主链路进行了优化;第二个就是针对于新的开发方式提供了更丰富的并且更适合千牛的能力;第三个则是在引入了新的框架之后,希望帮助千牛平台上的isv能够以更加友好的方式进行迁移并且尽快熟悉新的框架。
于是产生了如下图所示的框架。客户端这边的基本分层是没有发生变化的,在容器层加上了Web容器,除此之外还做了一些容器打通的相关工作,比如使得在Weex容器里面可以无缝地使用原来为isv开放的Web容器,这样的web容器既可以被当做导航页面进行处理也可以作为组件进行处理。另外就是针对于新的开发方式提供了一些新的framework,比如包管理的相关机制,并且提供了更加native方式的储存机制以及数据采集、导航、管理以及插件生命周期的管理等。除此之外,我们还提供了一套完整的与开发相关的工程管理上面的工具,工程管理工具的目的主要是在安装、打包、发布这一系列流程中遵守千牛的包规范和格式,并且也为isv提供了一些开发调试的工具。
接下来回到插件APP打开主链路优化这个话题上来。我们把主链路优化分为三个环节,分别是:资源加载、数据加载以及页面渲染。
对于资源加载而言,在QAP1.0的时候,我们提供给isv的是使用本地资源链接网络请求的能力。当插件容器发起一个请求的时候,传统的方式是直接向isv的云端去请求资源,而在QAP1.0提供了离线包的机制,插件容器可以先去请求离线资源包,看看资源包里面是否有所需要的资源,然后可以将相关的资源通过千牛的云端下发到客户端,这就是QAP1.0的资源加载机制。QAP2.0版本则完全废除了插件容器直接向isv请求资源的这条链路,而只有在服务降级时,才会有可能使用这条链路。所以新的开发方式对于包的管理的要求是非常严格的,之前资源包还可能是可有可无的,因为实在不行还可以通过网络请求资源,但是QAP2.0将资源包升级成为了应用程序,也成了插件容器请求资源的主战场,相比之前资源包里面基本都是JS或者CSS这些静态资源而言,QAP2.0版本的资源包里面还有很多配置文件,可以将具体的能力或者安装包的动作配置到资源包里面。
大家可以看一下QAP2.0中包的结构究竟是什么样子的。包主要又两部分构成,第一部分就是资源目录,另外一部分就是配置文件。
资源目录里面存放什么内容呢,其实就是每个配置的Bundle包,以及静态的资源图片以及icon这样的资源。而对于配置文件来说,也分为了三个部分,首先是APP相关的配置,存储了APP配置基础的一些信息,比如插件的AppKey、容器的相关信息、引入的NUKEUI、QAPSDK版本以及默认的应用启动页等等;另一部分就是APP的能力宣称配置,其实在数字区域的背后都是对应着一个插件,我们将数字的对应行为进行了业务上的抽象,并且提炼出不同的能力交由isv进行实现。如果isv要实现某一个业务,那么需要告诉客户端当触发某个插件的能力时将会唤起这个插件来帮助客户端执行这个工作,也就是将APP的能力进行声明,并与外部的能力路由进行对接。第三个是资源配置,当程序包安装和下发到本地的时候,就会去读取资源配置中的配置表,将资源下发到正确并且适合的位置上去,这个位置对于开发者是透明的,但是会极大地方便平台的管理。
谈完了资源加载,我们接着聊一聊数据请求。谈到数据请求,先要谈一谈isv在之前开发过程中所遇到的问题。一个isv想要完成一个业务其实需要调用多个API才能拿到想要的结果,此外为了保证数据的及时性,需要经常进行拉取。由于不知道数据什么时候更新,所以每次都需要拉取,这样的方式导致缓存的利用率比较低。并且之前的架构中缓存需要考虑用户隔离,大小的限制等问题,所以导致使用会变得非常麻烦。
接下来分享一下在QAP上提供了什么样数据请求等待优化方案。QAP获取数据并不是通过自己去获取数据的,而是通过淘宝的top平台去获取数据,所以我们做了top请求的长连接代理,使用TCP的长连接解决了HTTP的多路复用等时间上的消耗过多的问题,除此之外还使用了web浏览器自身提供的存储能力。
在QAP2.0中,我们引入了批量的top请求,也就是把多个请求进行批量发送以及批量接收。另外就是对于数据推送方面,在TCP长连接中有一个数据推送通道,在QAP2.0将这个数据通道开放给了isv,isv可以将自己的重要数据实时地推送到客户端,这样就不用每次都要去拉取想要的数据了。而且对于多用户的数据实现了天然的隔离,并且提供了批量存储的接口。
在页面渲染部分,我们做的工作相对比较少了,主要是交给其他团队提供支持。在QAP1.0阶段使用了MSUI+Webview,在QAP2.0阶段使用了NukeUI+Weex。这里稍微谈一下NukeUI的优点,NukeUI有更加规范的UI交互,并且提供了千牛组件开发的规范,还具备主题换肤能力,也提供了更优秀的性能。
在做完这些事情之后,在将组件推出并将QAP进行打包的时候还是遇到了很多问题。其中一个就是包大小的问题。目前千牛平台上的插件功能多并且非常复杂,isv开发后的页面不像是运营页面那样,而是可以视作APP的由多个页面组成的程序,所以包大小难以控制。另外就是每个千牛的用户平均拥有20多款的插件,插件安装包非常多,如果包的大小没有得到很好的控制,对于用户的流量和空间都将会造成巨大的消耗。而Bundle包的大小在一定程度上也会影响插件的性能。Bundle包主要包括这样的几部分,rax、组件以及SDK,所以我们可以看到对于每个包而言,公共的部分是非常多的。虽然rax是按需打包的,但是随着业务越来越复杂,一个APP几乎能够覆盖组件库中的全部组件,所以这部分是可以作为公用部分抽取出来的。
所以我们的解决方案就是将公用部分抽取出来放入Main.js里面,Main.js是Weex的运行环境,我们将其内置到Weex的运行环境中去。将公用部分抽取出来之后,Bundle包里面就只剩下了isv的业务代码了,这样使得包的大小能够很好地控制。当然这样的做法所带来的比较大的挑战就是内置包的版本管理和更新升级问题。因为Weex容器的初始化时机是在程序APP加载的时候,这样的方案是没有办法在程序运行的期间做任何处理的。
我们目前正在努力的方向和希望解决的问题就是内置包的管理以及动态升级的问题,这个问题的解决需要Weex框架进行支持。Weex最近提出了Server的概念,允许在插件打开之前构建如图这样的一个环境,将内置包运行在这个环境里面,如果配置环境中配置信息与客户端的版本号一致的话,就去默认地构建环境,如果不一致就会动态地对于出现问题的包进行更新,这样就可以解决内置包的管理以及更新问题。
千牛还提供了更加丰富的能力。在通讯机制方面,这里的通讯机制主要是针对页面级别以及整个插件的生命周期的机制。首先划分的Page级别,也就是单个Bundle内部的事件通讯;其次是插件APP级别的事件通知,也就是插件APP下所有的Bundle之间进行的通讯;第三个就是千牛应用级别的事件通知,会把千牛主体应用发出的全局事件,比如前后端的切换,网络变更等的事件通知给开发者。另外我们还提出了黏性事件的概念,就是当事件没有被消费的时候不会丢失,避免在某些场景出现了事件发出了,但因为还没有注册导致接收不到的情况。
AppIndex主要是解决能力宣称和能力路由的问题,整个插件能提供哪些能力,怎样进行路由都由AppIndex进行管理的。首先对于一个插件而言,它可以将自己对应的能力录入到能力路由表中,并且注册到AppIndex客户端体系中,当进行能力路由的时候会通过权限管理以及能力路由找到实现这个能力的插件,并根据路由表找到具体的页面。其实在QAP1.0就有了这样的一套机制,但是当时只能够返回程序主入口的地址,而在QAP2.0就能精准地定位到每一个页面。
为了精确地寻址,在千牛的客户端中提供了通用的QAP的URI,所有的QAP页面都是通过下图中的这一套URI机制进行寻址的,通过这样的URI就能够定位到每一个页面。
最后为了降低isv的迁移成本我们也做了很多事情。我们实现了Weex+Web的混搭,也就是在前期推广的时候允许isv改造自己的部分页面,也就是将自己的插件的入口页面以及对性能有高要求的页面进行Weex更改,其他要求不高的页面还可以使用Web,这样就能够降低isv的开发成本;除此之外还为isv提供了业务开发实例和开发文档以及一站式的开发工具。
对于开发工具而言,我们提供了QAP的CLI,能够实现创建工程并且导入示例工程、进行调试、打包上传、安装真机测试以及获取最新千牛测试包等功能。但是在将CLI提供给isv的时候也发现了一些问题,比如往往会遇到的平台环境的问题以及依赖安装缓慢的问题。
考虑到以上的问题,我们目前正在做的事情就是将整套相关依赖做成IDE,这样开发者就可以在IDE里面进行一站式工作,可以很方便地找到入口、创建工程并且进行工程调试以及一键安装和上传。