淘宝Native研发模式的演进与思考 | DX研发模式
寻弦2022-06-13

DX全称DinamicX,目前是在淘宝乃至整个阿里集团内广泛使用的Native动态化方案,核心优势是性能和稳定性。过去几年一直有其他淘宝/集团的外部文章中有涉及到DX,但DX一直没有对外做过完整介绍,对外界来说这两个字母颇有些神秘色彩。本系列文章《DX研发模式》我们就将拉下它神秘的面纱,看看过去两年 DX 在做什么。

DX的起源与发展

DX是从首页为解决业务两端一致性和动态性的问题孵化而来,后经过若干次升级和迭代,逐步完成了从首页动态化方案——基础链路动态化方案——集团共建动态化方案——集团移动小组标准Native研发模式的多级跳跃。随着接入的业务和开发者越来越多、使用场景越来越复杂和多样,技术范畴也从端侧SDK延展到动态化技术体系,再到Native研发模式。

淘宝的技术选型

这可能是当前这个星球上最复杂App的淘宝技术选型标准,我们把淘宝所有的业务分为三个大类:核心域、导购域和开放域。不同场景对于性能体验、稳定性、交付效率都有不同的诉求,这里没有“技术银弹”,只有适合目标场景的最佳方案。

过去两年我们面临的挑战是什么

作为核心域标准技术选型的DX,过去几年的挑战是什么呢?

首先来看一下大的业务背景:

  1. 业务竞对加剧,集团从航母战略升级为App矩阵战略,对交付量和效都有了更高的要求;
  2. 随着直播/短视频/AR/3D技术的发展与普及,移动端迈入沉浸式/富交互体验,Native技术栈的优势更加明显;
  3. 淘宝的购物效率越来越高,用户访问时长/频率降低,大部分购买决策发生在淘外,淘宝逐渐沦为下单/比价工具,而当内容场掌握了大量入口流量和用户时长后,开始下场自己闭环做电商成交,曾经的引流渠道变成直接竞争对手,淘宝也将面临“无源之水、无本之木”的困境,所以淘系近年来一直在打造自己的内容/社区,让更多的消费购前决策发生在淘系。

这三大业务背景深刻地影响着接入DX的每个业务以及潜在的新业务,所以过去两年,我们的工作主要围绕研发模式升级和体验升级来进行。

首先来说研发模式升级,原先主要有两个问题:

  1. DX自身技术体系不完整,在19年之前,DX只能说是一个客户端动态化方案,包含一个端侧SDK和一个组件平台,但随着接入的新业务越来越多,DX自身技术体系不完整的瓶颈愈发明显,大量新业务需要重复建设端侧容器、搭建平台等基础设施,导致接入成本高、收益低,而各业务自建的容器和平台,又难以沉淀和复用,进一步加剧了后续技术拉通的难度。
  2. 研发模式过于单一,随着沉浸式体验和富交互体验的铺开,很多业务告别了过去重展示轻交互的模式,往中度交互开始进行体验升级,尤其以微淘拆分出的逛逛和订阅为代表的类内容场,需要以更沉浸式的浏览/互动体验来吸引用户更多的停留和内容消费,而我们过去基于搭建的方式并不能很好地对此类场景进行研发支撑。

再来是体验升级,我们虽然并不能决定具体业务场景的视觉交互和体验设计,但面向优秀的体验,我们可以做几个事情:

  1. 提供开箱即用的体验优秀的标准化组件,如日历/富文本/复杂嵌套容器/Page Indicator等组件,极大降低体验精细化带来的适配成本,如Dark Mode/银发版字体自动放大适配等;
  2. 在富媒体/端智能的快速发展,以及下沉市场中低端设备的增加的背景下,持续优化DX渲染管线的性能水位,提升广大业务场景的基础浏览和交互体验;
  3. 提供更完善的性能工具,帮助开发者在面对相对“黑盒”的模版性能问题时,可以做到早发现、可排查、可优化。

关于ProCode和LowCode的思考

在我们承接新订阅和逛逛的新场景时发现,在这种内容型交互类场景,有着丰富的交互、联动与转场,DX过去动态逻辑表达力不足和基于模块搭建的LowCode研发模式无法支撑其进行高效研发,核心矛盾是动态逻辑、复杂容器及内部联动、多人并行开发与模块引用,于是经过一系列的讨论和思考后,有了下面这张图:

基于在新场景下面临的问题,我们决定把研发模式进行分层,定制性强/动态逻辑要求高的业务场景,用ProCode模式来开发,通过强大专业的DX IDE+Git来进行工程化多人协作开发,可以进行UI和动态逻辑的调试(事件链),而定制性弱/复用性高的业务场景,用LowCode模式来承接,通过可视化搭建平台来拖拽生成视图和布局,配置相应数据源和规则。同时,不断抽象沉淀组件库/能力库,使越来越多的交互/功能/布局可以通过可视化方式来拖拽生成。

我们畅想的未来是,当业务同学有一个新的业务想法时,可以通过LowCode模式自行搭建与配置,在线上圈选小部分用户进行灰度验证,在验证产品方向正确后,再投入更多的专业技术人员来做进一步的产品定制和升级,来产生更大的业务效果。

ProCode的“三驾马车”

要让 ProCode 模式成型,必须要解决的是动态逻辑、复杂容器和相应的研发支撑问题,所以就有了ProCode的“三驾马车”:IDE、列表容器、事件链

IDE

第一是IDE,这里补充些背景信息,在做IDE之前,DX有个模版平台,主要来承载模版的开发/调试/发布工作,那我们为什么要再做一个IDE呢?

首先,严格来说,原来DX平台上的不叫IDE,只能说是Editor,从关系上来看,IDE包含Editor,借用一张VSCode的图来说明两者的差异:

其次,原来DX平台上的Web Editor(Monaco)虽然也支持大部分LSP API,但没有工程/项目体系、文件系统、代码上下文理解、调试等能力,且受限于浏览器环境,而IDE运行于本地环境,有丰富的扩展能力和插件,可以提供完整的研发生命周期支持。

所以,在ProCode模式下,我们的技术选型基于Katian IDE(兼容VSCode API),来得到更多的视图扩展能力,未来还能通过灵活的部署方式(远程开发、云端部署),实现更高效/快速的研发与协作。

目前DX IDE已经接入了包括淘宝在内的集团多个重要业务,这些开发者使用IDE后研发效率有了大幅的提升,尤其是多人协作与调试效率有了量级的提升。关于DX IDE更详细的介绍,可以期待和阅读本系列文章的第二篇。

列表容器

第二是列表容器,过去基于搭建的容器有个特征,容器/页面布局由数据协议来描述,即数据布局一体,这在简单页面结构(如单列表页面)场景下运转良好,配合搭建能较快地配置出楼层,通过协议来驱动端侧渲染,服务端也不需要过多感知业务结构(默认就是单列表布局)。但在复杂页面结构(如多层多Tab嵌套容器),尤其还有复杂交互的情况下,就会让整个数据结构变得过于复杂,同时也增加数据协议的paylaod,而页面布局的变化频率远小于业务数据。

在这种场景下,把两者耦合在一起,明显是不合适的,于是,出于职责单一和“动静分离”的原则,我们进行了重新的思考和设计,在模版XML中统一描述布局(包括容器/页面布局),让数据回归纯粹的数据结构,与此同时,我们也在DX中内置了新的功能强大的列表容器,写起来就像下面这样:

<RecyclerLayout
            userId="recycler"
            width="match_parent"
            height="match_parent"
            dataSource="@data{itemList}"
            backgroundColor="@triple{@getEngineStorage{backgroundColor}, @getEngineStorage{backgroundColor}, '#ffffff'}"
            columnCount="2"
            columnGap="4"
            isOpenPullToRefresh="True"
            refreshPullText="下拉即可刷新..."
            refreshReleaseText="释放即可刷新..."
            refreshLoadingText="加载中..."
            loadMoreFailText="加载失败"
            loadMoreLoadingText="加载中..."
            loadMoreNoMoreDataText="到底了"
            onPullToRefresh="@dxEventHandler{'refreshTest'}"
            onEndReached="@dxEventHandler{'loadMoreTest'}"
        >
            <Template
                if="@subdata{span}"
                sticky="True"
                colspan="2"
                name="aa"
            >
                <TextView
                    width="match_parent"
                    height="100"
                    text="@data{text}"
                    textSize="15"
                    backgroundColor="#FFFF55"
                    borderColor="#FF9911"
                    borderWidth="2"
                />

            </Template>
            <Template
                if="@subdata{recyclerInsertItems}"
                name="recycler_sub_template1"
                version="2"
            />
</RecyclerLayout>

目前列表容器已覆盖了多个淘宝和集团内重要业务场景,支持了嵌套容器/联动框架/动态Template/Slot等能力,下一年我们也会推广到更多的业务。

事件链

第三是事件链,过去DX只支持UI的动态化,有限的逻辑控制只能通过表达式来实现,其他的逻辑变更都依赖发版,而随着业务对发布上线的时效要求越来越高,逻辑动态化的诉求也愈发强烈。于是,面对这个诉求,我们经过反复的讨论和思考,综合考虑了苹果合规、性能、稳定性、开发效率,提供了事件链的方案。

在事件链方案中,开发者在XML对应的eventchain.json中使用原子能力和表达式进行逻辑组装,在研发期IDE提供了事件链JSON的智能感知能力(包括补全/提示/跳转/格式化/折叠/高亮等),可以通过事件链回放功能,来图形化显示事件链执行的流程以及每个原子能力的出入参和上下文,来便捷问题的排查。开发完成后,在编译阶段,DX编译服务会把该JSON编译成二进制,与模版二进制一起下发,确保性能和一致性,在端侧有事件链引擎来负责二进制的解析,以及原子能力的调度。

想了解列表容器和事件链更多的技术细节,可以期待和阅读接下去的文章。

关于性能优化的思考和实践

过去两年,DX的能力越来越强大,功能越来越丰富(动画动态化、事件链、复杂嵌套容器、直播/视频组件、3D组件等),那DX一直以来的性能优势,会不会有所影响或下降?我们又是如何在不断加入新功能的同时,做到性能不退反升呢?面对越来越复杂的模版,如何帮助开发者在面临性能问题时,有排查和优化的抓手,而不是一无所措呢?

这就要说下DX的渲染管线,如果说Flutter是接管整个系统渲染的革命派,那DX就是基于原生系统渲染的改良派。DX的主要设计思路是基于Platform First来改良系统原生渲染管线/机制中的性能瓶颈和问题,并做到两端统一。

从DX管线各阶段的耗时分布来看,主要大头在load和render,所以我们主要通过异步化管线来解决load的耗时问题,通过异步绘制和高性能组件来解决render的耗时问题,通过计算资源管控来尽量保障资源的合理分配。

异步化管线和异步绘制,主要是让更多工作能够在子线程执行,或者并行调度,降低主线程的负担,合理利用多核计算资源,在DX列表容器中,我们内置了该能力,可以做到开箱即用,而在业务容器中,需要业务方按照我们建议的时机/业务认为合适的时机来调用相应API,来获得异步化的调度。

高性能组件方面,我们主要优化了图片组件和自研了富文本组件,尤其是富文本组件,我们基于系统TextKit/TextLayout实现了自测自绘,提供了TextSpan/ImageSpan/展开收起等能力,相比原生组件,内存降低(iOS 70% Android 8%),性能提升(iOS 20% Android 10%)。

计算资源管控方面,我们主要设计和实现了离屏计算资源管控框架,用来实现视频/直播/动图的播控问题,且提供了规则配置能力,在多视频情况下,iOS CPU占用率降低66%/Android降低25%,目前在淘宝的一些重要信息流场景已上线。

性能工具方面,过去我们主要是在运行时进行各种底层优化和编译期的性能预警提示,在遇到模版性能问题时,通常需要业务方和DX同学分别打Log来看时间的消耗和定位问题的所在,双方都耗时耗力。会出现这种情况,一部分原因是DX与Native的结合度较高,一次渲染过程中有大量的DX内部代码和业务方自定义代码交织在一起,导致无法快速精准定位问题所在;另一部分原因是DX的渲染过程对业务方来说相对黑盒,业务方也没有进行排查和优化的有效抓手。

为了摆脱这种场景,我们要让整个模版渲染过程在开发阶段进行一定的白盒化,以可视化图形方式展现模版各阶段的渲染耗时及其归属(DX内部还是业务方自定义部分,具体到是哪个Widget的什么阶段/DataParser/EventHandler),同时通过不断累积的常见性能问题,进行模版自动分析并提供一定的优化建议。

关于性能优化的思考和实践,请继续期待和阅读后面的文章。

技术大图

总结一下,这是目前DX技术体系的大图,研发支撑层,我们通过IDE和平台来满足ProCode和LowCode两种生产模式的诉求,核心运行时层,我们不断增强容器/组件/布局等视图能力的同时,引入一定的动态逻辑能力,通过共建不断沉淀原子能力库,业务框架层,我们也与淘宝内团队合作,把复杂状态管理、流程编排等能力引入DX技术生态中,业务可按需接入。

未来展望

从技术演进上,我们认为DX的下一阶段,要实现三化:

  1. 标准化,目前DX的DSL偏向私有标准,开发门槛较高,不利于扩大开发者群体,接下来我们要向客户端行业标准靠齐,吸引更多的开发者;
  2. 现代化,目前DX通过命令式来描述UI,而行业已经向声明式迈进,从技术演进路线上,我们也要拥抱声明式,进一步提升开发体验和效率,此外,研发支撑方面,我们这个财年从0到1打造了DX IDE,在本地进行安装和运行,已经能较大程度提升复杂页面/模版的研发效率,但我们不会停下研发提效的脚步,接下来还要尝试通过云端部署的方式,达到免安装/开箱即用/工作空间共享等,进一步提升研发和协作效率;
  3. 一体化,目前我们已经通过DX IDE实现了部分的工程一体化,未来为了进一步提升前后端协作效率,也为了把Serverless打造成客户端的基础能力之一,我们要实现端云一体,在一个集成研发环境中,以同一个技术栈,同时开发端侧和云侧代码,简化端云远程调度开发成本,发布期通过编译技术部署到不同的目标,提升前后端协作效率和端侧技术方案自主性。

当然要实现这些,还有大量的技术挑战在前方等待着我们,欢迎有兴趣的同学和我们进行技术交流。