导语
DU的框架产生
要实现快速迭代,就要避开APP发版环节,采用动态部署的方式,将功能动态下发到用户手机内。市场上解决此类问题的方案也多种多样,常见方案大致存在以下几种:
A、Web / Hybird 方案;
B、JS + Native 方案;
C、Android插件化方案。
三种方案各有优缺点,具体优缺点对比以及技术选型不是本文重点,这里不再展开讨论,感兴趣的同学可自行查阅相关文章。我们选取的是第二种方案:JS+Native解决方案。
JS+Native动态部署方案,其原理是:通过JS与native交互协议,用JS代码操作原生系统的UI组件,代替DOM元素来渲染。由于最终渲染是由Native完成,所以即实现了跨平台动态更新的特性,也保证了流畅的用户体验。市面上较流行的解决方案有:
React Native:是由Facebook开源的跨平台移动应用开发框架。支持用开源的JavaScript库React.js来开发iOS和Android原生App。其主张“Learn once, write everywhere”。
WEEX:WEEX是一款基于通用跨平台的 Web 开发语言和开发经验,来构建 Android、iOS 和 Web 应用的开发框架。目前 前端框架Vue 和 Rax被广泛应用于 Weex 页面开发。其宣称“Write Once, Run Everywhere”。
DU也是基于JS+Native原理。它更加轻量,是面向Android研发人员设计的一款动态开发框架。它的设计思路遵循招才猫动态部署框架整体的思路,使Android/iOS Native开发的小伙伴上手机开发成本低,甚至可忽略。下图是用DU框架开发的页面,大家可以看出,与我们Android开发规范相似度非常高。
DU框架原理
上一章已提到,DU的技术方案为JS+Native。具体方案如下图:
协议通信层:DU框架,选取了Google的JavaScript引擎V8,实现JavaScript和Android的相互调用。在JS端以及Native分别对协议进行了封装与解析。DU所有的数据交互,都是以此为基础来扩展的。
JS端SDK:DU围绕协议层,封装了对上层业务支持的各个功能模块,并提供DynamicUpdateAPI,供JS业务层调用。如页面组件、类转换器、资源管理、配置管理、扩展模块、崩溃收集等,这些模块的封装,会通过协议层传输到Native端,具体的实现,都由Native端完成。
Native端:则是对JS端定义的模块具体实现。如网络扩展模块,会接受JS端传递的网络协议数据,然后调用Native网络框架(如OKHttp)去执行真正的网络请求,并将结果通过协议层,会传JS端。
JS业务层:业务逻辑具体实现层,通过调用JS端提供的DynamicUpdateAPI,完成需求功能的开发工作。
DU框架完整的运转流程如下图:
DU渲染原理
DU的图片,放到了固定的图片文件夹中,在布局文件中使用的时候,只要定义$drawable/xxx即可加载对应的图片文件;
DU框架里面封装了加载DU页面的DUActivity以及DUFragment。在启动DU页面时,将布局文件与页面进行关联。在Activity/Fragment创建的时候,会自动创建资源解析器与构建器。
资源解析器会将布局文件以及布局中用到的颜色、尺寸、图片等信息,进行解析转换,并交给Views生成器,生成一个个带有完整描述的组件,这些组件会形成ViewsTree。
转换器将生成的ViewsTree交给构建器。构建模块会根据各个控件的描述,创建对应的组件,并添加到DecorView上,并为有交互的控件添加事件监听。
至此,DU完成了页面的渲染。
DU将Android的生命周期,映射到了JS页面的生命周期,使JS可以在生命周期内,处理自己的业务逻辑。具体方式如下图:
DU同样支持带有启动模式的Activity。由于Android的Activity需要在AndroidManifest.xml注册,DU采用了占坑的方式,提前在AndroidManifest.xml注册了带有启动模式的Activity。
3、UI扩展
DU已经对构建页面的基本组件做了封装。如常用的Layout、Button、ListView、CheckBox等。但是业务错综复杂,交互设计千变万化。显然一个框架是不能覆盖全部页面元素的。这里DU提供了UI扩展功能,只要按照DU的扩展规范,即可轻松创建属于你的UI界面。具体方法如下:
UIComponents组件,就是现有APP自己创建的控件,也可以是系统封装的控件,如Button。
Proxy,是控件代理层。代理层有两个作用:
A、封装属性代理。通过代理里面封装的Property,为不同控件设置不同的属性,并且Property是可以继承的,相应的也就是控件的属性可以继承。如DUViewGroupProxy.Property 继承自DUViewProxy.Property,所以DUViewGroup不但拥有了自己的属性,同时也拥有了DUView的属性。这样可以避免重复定义控件属性,增加属性复用性。
B、将调用与实现隔离。如Button控件,在招才猫端对应的就是招才猫封装的ZCMButton,但是对应其他APP,就会是xxxButton。有了代理层, 在集成DU框架时,可以实现控件的快速切换,而不是再封装一套。
view_config配置文件,此文件主要作用是将View与Viewproxy做映射关系。
layout.xml,为布局文件。
以上为Native端的实现。
SDK里面已经封装了交互协议以及View生成器。在SDK初始化的时候,已经根据view_config.xml的配置信息,将View与ViewProxy做了一一对应。在渲染页面时,view生成器会根据layout创建相应控件,并通过ViewProxy,调用view的setxxx方法,为view属性赋值。
DU模块扩展
jSExtendsible封装了扩展模块的方法调用;
NativeExtengdsible,是具体扩展功能的实际执行方;
extendsible_config.xml将jSExtendsible与NativeExtengdsible做了映射关系;
SDK内部,通过ModuleManager,将jSExtendsible调用协议解析、并交给NativeExtengdsible执行,最后将执行结果返回。
DU类转换器
例如:JS端调用textView.setHeight(12)设置高度,协议传输过程中,12是字符串,调用的是Android端TextView的setHeight(int pixels) 方法。这时需要将字符串”12“转换为int类型。类转换器处理的就是类似场景。
目前DU已经封装了30几种类转换器,基本可以覆盖常规开发需要。接入方也可以根据实际情况,扩展自己的类转换器。具体原理以及实现过程和UI扩展、模块扩展一致,如下图。这里不再赘述。
DU踩的坑
1、JS引擎的选择
在DU行程框架诞生之前,招才猫使用了以WebView为引擎,作为协议中转核心,通过JS,创建Native页面并执行相关逻辑,实现了发布页面的动态化。
在后续优化中,在发布页面基础上,提取了通用协议,并做了更加丰富的封装,产出了第一版DU,同样,第一版DU框架的核心引擎也是WebView。WebView在这里的作用仅仅是执行JS,并负责与Native通讯。可以想见,WebView页面绘制等功能并没有使用,但是我们仍会进行初始化,造成了资源浪费。经过对比与调研,我们最终引入了V8,作为DU框架的JS解析执行以及通讯引擎,减少了DU在运行时的资源消耗。
2、复杂动画的处理
在需求开发中,必然会用到各种动画效果,DU框架已支持常用的动效实现,如Animation、Transition等,DU已做了封装,可以直接使用。但在我们使用过程中发现,简单动效还好,DU完全能够胜任。一旦在DU中实现比较复杂的动效时,页面就会出现卡顿现象。经过试验与分析,我们发现在动效执行过程中,JS与Native会进行频繁交互,在协议传输、解析过程中,由于数据传输过于频繁,造成通讯模块处理延迟,导致页面卡顿。
在解决动画问题上,我们针对动效交互的协议做了大量优化,但收益不佳。在后续优化中,我们放弃了在动效交互协议上做文章,而是考虑其他解决方案。得益于我们UI扩展能力,在开发实践中,我们将复杂动效封装成UI组件,将动效完全交由Native实现,JS端仅仅作为调用方。这样,就不存在动效执行中,协议交互频繁的问题了。此方案的坏处就是如果动效组件在之前版本中没有实现过,还是需要跟随版本上线。不过在实践中,需要特别复杂动效的情况,少之又少。DU完全能够胜任目前的需求。
结语
1、https://reactnative.cn/
2、https://weex.apache.org/zh/guide/introduction.html
3、https://nodejs.org/en/
4、https://github.com/eclipsesource/J2V8
5、https://developer.android.com/docs
作者简介
END
阅读推荐
租房业务APP后端服务重构之路
58同城宝实时数仓建设实践
Android字节码优化工具redex初探
本文分享自微信公众号 - 58技术(architects_58)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。