# 40 | 动态化实践,如何选择适合自己的方案? 在专栏第36期[《跨平台开发的现状与应用》](https://time.geekbang.org/column/article/88161)中,我分享了H5、React Native/Weex、小程序这几种常见的跨平台开发方式。站在开发的角度,虽然跨平台的开发效率要比Native开发更高,但是这并不是大前端在国内盛行的最主要原因。 相比跨平台能力,国内对大前端的动态化能力更加偏执。这是为什么呢?移动互联网已经发展十年了,随着业务成熟和功能的相对稳定,整体重心开始偏向运营,强烈的运营需求对客户端架构和发布模式都提出了更高的要求。如果每个修改都需要经历开发、上线、版本覆盖等漫长的过程,根本无法达到快速响应的要求。 所以H5、React Native/Weex、小程序在国内的流行,可以说是动态化能力远比跨平台能力重要。那我们应该选择哪一种动态化方式呢?正如我在跨平台开发所说的,目前这几种方案或多或少都还存在一些性能问题,如果一定要使用Native开发方式,又有哪些动态化方案?今天我们一起来学习应该如何选择适合自己的动态化方案。 ## 动态化实践的背景 前几天在朋友圈看到说淘宝iOS客户端上一个版本的更新,已经是两个多月前的事情了。淘宝作为一个业务异常庞大且复杂的电商平台,这样的发布节奏在过去是很难想象的。 ![](https://static001.geekbang.org/resource/image/bb/7d/bb60313024c59b2ed4b4430396e25f7d.png) 而现在即使不通过发布新版本,我们也能实现各式各样的运营活动和个性化推荐。依赖客户端的动态化能力,我们不需要等待应用商店审核,也无须依赖用户的主动更新,产品在快速迭代的同时,也有着非常强大的试错能力。 **1\. 常见的动态化方案** 移动端动态化方案在最近几年一直是大家关注的重点,虽然它已经发展了很多年,但是每年都会有新的变化,这里我们先来看看各大公司有哪些已知的动态化方案。 ![](https://static001.geekbang.org/resource/image/27/73/27f332588dae2d5f4d1a210e3e77dc73.jpg) 在2018年北京QCon大会上,美团工程师分享了他们在动态化的实践[《美团客户端动态化实践》](https://github.com/AndroidAdvanceWithGeektime/Chapter37/blob/master/Qcon%E5%8C%97%E4%BA%AC2018--%E3%80%8A%E7%BE%8E%E5%9B%A2%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%8A%A8%E6%80%81%E5%8C%96%E5%AE%9E%E8%B7%B5%E3%80%8B--%E6%96%B9%E9%94%A6%E6%B6%9B.pdf)。美团作为一个强运营的应用,对动态化有非常强烈的诉求,也有着非常丰富的实践经验,他们将动态化方案分为下面四种类型。 ![](https://static001.geekbang.org/resource/image/5e/e0/5e851695fbc022bc80a9ce634b5b34e0.png) * **Web容器增强**。基于H5实现,但是还有离线包等各种优化手段加持,代表方案有PWA、腾讯的VasSonic、淘宝的zCache以及大部分的小程序方案。 * **虚拟运行环境**。使用独立的虚拟机运行,但最终使用原生控件渲染,代表方案有React Native、Weex、快应用等。 * **业务插件化**。基于Native的组件化开发,这种方式在淘宝、支付宝、美团、滴滴、360等航母应用上十分常见。代表方案有阿里的Atlas、360的RePlugin、滴滴的VirtualAPK等。除此之外,我认为各个热修复框架应该也属于业务插件化的一种类型,例如微信的Tinker、美团的Robust、阿里的AndFix。 * **布局动态化**。插件化或者热修复虽然可以做到页面布局和数据的动态修改,但是代价巨大,而且也不容易实现个性化运营。为了实现“千人千面”,淘宝和美团的首页结构都可以通过动态配置更新。代表的方案有阿里的Tangram、Facebook的Yoga。 **2\. 动态化方案的选择** 四大动态化方案哪家强,我们又应该如何选择?在回答这个问题之前,我们先来看看它们的差别。 ![](https://static001.geekbang.org/resource/image/2b/67/2b7dcceb9c990918e09e2f0ddd05a967.jpg) 目前我们还无法找到一种“十全十美”的动态化方案,每种方案都有自己的优缺点和对应的使用场景。比如Web容器增强方案在动态化能力、开发效率上有着非常大的优势,但稳定性和流畅度差强人意。恰恰相反,布局动态化方案在性能上面有非常不错的表现,但是在动态化能力和开发效率上面却受到不少限制。 所以说动态方案的选择,我们需要考虑下面这些因素。 * **业务类型**。主要考虑业务的重要性、交互是否复杂、对性能的要求、是否长期迭代等因素。 * **团队技术栈和代码的历史包袱**。在选择方案的时候,也需要结合团队的技术栈现状以及代码的历史包袱综合考虑。以微信为例,作为一个强交互的IM应用,团队基本以Native开发为主,而且微信基本没有太多运营上的需求,所以当时在动态化方案上只使用了Tinker。当然团队的技术栈并不是永恒不变,有了微信小程序之后,内部的一些业务也尝试使用小程序来改造。 ![](https://static001.geekbang.org/resource/image/d9/70/d985b8e7426ae89d9ccabced38422c70.jpg) 最终无论我们选择哪种动态化类型,我都建议公司内部同一种动态化类型都使用同一个方案,这样在统一技术栈的同时,也可以实现代码在不同业务之间的迁移。比如阿里内部的虚拟运行环境统一使用Weex,一个业务在手淘的效果不错,也可以快速迁移到飞猪、天猫等其他应用中,实现应用的流量矩阵。 同样对于运营活动也是如此,阿里内部有一个叫[PopLayer](https://yq.aliyun.com/articles/59050)的组件,它可以在任意Native页面(这个页面甚至可以是Browser)弹出H5的部署容器,可以在无需发版的情况下对已有的Native界面上浮出透明浮层,并且可以不影响Native页面本身的交互。这样做活动我们不需要在客户端提前一两个月开发代码,而且同一个活动也可以快速在公司内部的各个应用中上线。 ## Native动态化方案 Web容器增强和虚拟运行环境方案通过独立的Runtime和JS-SDK来桥接Native模块,而业务插件化则通过插件化框架和接口能力直接调用。相比之下前者更加抽象而不易造成代码混乱,这也是目前各大公司逐渐开始“去插件化”的原因。 ![](https://static001.geekbang.org/resource/image/3b/24/3bfe93916ac78f7190474cdd6c36ad24.png) 最近两年,大前端开发越演越烈,传统的Native动态化方案是否还存在价值,它又该何去何从?热修复、插件化这些方案的未来又将如何演进呢? **1\. 热修复和插件化** 2016年在开源Tinker的时候有两件事情是超出我预料的,一个是热修复在国内竟然有那么大的反响,另外一个就是它竟然如此的“坑坑不息”。 从[《Tinker:技术的初心与坚持》](https://mp.weixin.qq.com/s/tlDy6kx8qVHQOZjNpG514w)一文中,你可以看到过去我们踩过的一小部分坑,但非常不幸的是,填坑之路至今依然没有结束。每次Android新版本发布,我们就像迎来期末考试一样步步惊心。 ![](https://static001.geekbang.org/resource/image/85/fa/85a4abdd15b978a0f2ec2dec8d9298fa.png) 曾经微信希望使用Tinker来代替版本发布,在热修复的基础上实现四大组件的代理。但是Android P私有API限制的出现,基本打消了这个念头。热修复不能代替版本发布,但是我们可以通过它来实现一些应用商店不支持的功能,例如精准的灰度人数控制、渠道和用户属性选择、整包的A/B测试等。 另一方面,热修复给国内的Android生态也带来一些不太好的影响,比如增加用户ROM体积占用、App启动变慢15%、OTA首次卡顿等。特别是Android Q之后,动态加载的Dex都只使用解释模式执行,会加剧对启动性能的影响。因为性能的问题,目前大公司基本暂停了全量用户的热修复,只使用热修复用于灰度和测试。 热修复如此,插件化也是如此。笨重的插件化框架不仅影响应用的启动速度,而且多团队协作的时候并没有想象得那么和谐,接口混乱、仓库不好管理、编译速度慢这些问题都会存在。插件化回归模块化和组件化,这也是目前各大公司都在逐步推进的事情。 前一阵子,徐川在[《移动开发的罗曼蒂克消亡史 》](https://mp.weixin.qq.com/s/2xBnlmESZjq7UTtcfzqhcA)一文中回顾了热修复和插件化的前世今生。时间一转三年过去了,对于曾经参与这个浪潮的一份子来说,我可以做的只是顺应潮流的变化。 ![](https://static001.geekbang.org/resource/image/38/0b/388999a6bcca0adeb91635d3f0eca80b.png) **热修复的未来** Tinker设计之初参考了Instant Run的编译方案,但是正如专栏第26期[《关于编译,你需要了解什么?》](https://time.geekbang.org/column/article/82468)中所说的,Google在Android Studio 3.5之后,对于Android 8.0以上的设备将会使用[Apply Changes](https://medium.com/androiddevelopers/android-studio-project-marble-apply-changes-e3048662e8cd)替代之前的Instant Run方案。 ![](https://static001.geekbang.org/resource/image/e6/69/e6396e5202b17edc691e7b934f6e6b69.png) Apply Changes不再使用插入PathClassloader的方式,而是使用我们已经多次讨论过的JVM TI。在Android 8.0之后,JVM TI开始逐渐支持ClassTransform和ClassRedefine这两个接口,它们可以允许虚拟机在运行时动态修改类,实现运行时的动态字节码编织。事实上这个技术在JVM就已经非常成熟,Java服务端利用这两个接口实现了类似热部署、远程调试、动态追踪等能力,具体你可以参考[《Java动态追踪技术探究》](https://tech.meituan.com/2019/02/28/java-dynamic-trace.html)。 那热修复的未来将要走向何方?本来我对热修复的未来是非常悲观的,但是Android Q给了我一个很大的惊喜。我们知道,Android P在中国有非常多的应用出现了兼容性问题,其中大部分是热修复、插件化以及加固等原因造成的(Google提供的数据是43%的兼容性问题由这三个问题造成)。 为了解决这个问题,并且减少我们对私有API的调用,Google在Android P新增了[AppComponentFactory](https://developer.android.com/reference/android/app/AppComponentFactory) API,并且在Android Q增加了替换Classloader的接口instantiateClassloader。在Android Q以后,我们可以实现在运行时替换已经存在ClassLoader和四大组件。中国热修复的先驱们用自己的“牺牲”,总算换来了Google官方的支持。我们使用Google官方API就可以实现热修复,这样以后Android版本再升级也不用担惊受怕了。移动开发的罗曼蒂克并没有消亡,Native的热修复再次迎来了春天。 ![](https://static001.geekbang.org/resource/image/2c/d3/2c57e70df59f3db4ee33e96819c3ced3.png) **插件化的未来** 对于插件化的未来,我们需要思考如何“回归官道”。Google在2018年推出了[Android App Bundles](https://developer.android.com/guide/app-bundle),它可以实现模块的动态下载,但是与插件化不同的是,它并不支持四大组件代理的能力。 ![](https://static001.geekbang.org/resource/image/d7/10/d7162ca4f725a4b3671702a87ad65c10.png) 但是Android App Bundles方案依赖Play Service,在国内我们根本无法使用。爱奇艺的[Qigsaw](https://zhuanlan.zhihu.com/p/40035587?utm_source=wechat_timeline&utm_medium=social&utm_oi=748276889193299968&from=timeline&isappinstalled=0)可能对我们有所启发,它基于Android App Bundles实现(支持动态更新,但是不支持四大组件代理),同时完全仿照AAB提供的Play Core Library接口加载插件,如果有国际化需求的公司可以在国内版和国际版上无缝切换。这种方案不仅可以使用Google提供的编译工具链,也支持国际国内双轨,相当于Google为我们维护整个组件化框架,在国内只需要实现自己的“Play Service”即可。 当然和热修复一样,如果使用[AppComponentFactory](https://developer.android.com/reference/android/app/AppComponentFactory) API,我们也可以实现插件化的四大组件代理。但是具体实现上依然需要在AndroidManifest中预先注册四大组件,然后具体的替换规则可以在我们自定义的AppComponentFactory实现类中埋好。 ![](https://static001.geekbang.org/resource/image/b0/c2/b095bdb7c73b481904c384a296eb4ec2.png) 以Activity替换为例,我们可以将某些类名的Activity替换成其他的Activity,新的Activity可以在补丁中,也可以在其他插件中。 ![](https://static001.geekbang.org/resource/image/9d/43/9d7c201af7f57909e7d8af4da1010c43.png) **热修复和插件化作为Native动态化方案,它们有一定的局限性。随着移动技术的发展,部分功能可能会被替换成小程序等其他动态化方案。但是从目前来看,它们依然有非常大的存在价值和使用场景。** **2\. 布局动态化** 正如上文所说,像淘宝、美团首页这些场景,我们对性能要求非常高,这里只能使用Native实现。但是首页也是流量的聚集地,“提增长、提留存、提转化”都要求我们有强大的运营能力。最近两年,淘宝、天猫一直推行“千人千面”,每个用户看到的主页布局、内容可能都不太一样。 布局动态化正是在这个背景之下应运而生,在我看来,布局动态化需要具备下面三个能力。 ![](https://static001.geekbang.org/resource/image/17/a8/1791818514edda690fe9b455f32083a8.jpg) * **UI容器化**。能够动态地新增、调整UI界面而无需发版。 * **能力接口化**。点击、跳转等通用能力可以通过路由协议对外提供,满足UI容器化后的调用需求。 * **数据通道化**。数据上报也可以通过字段配置,实现客户端根据配置自动上报。 在具体的实践上,天猫开源的[Tangram](https://github.com/alibaba/Tangram-Android/blob/master/README-ch.md)是一个不错的选择。但是Tangram的整体方案会相对复杂一些,我们也可以基于底层的[VirtualView](https://github.com/alibaba/Virtualview-Android/blob/master/README-ch.md)做二次开发。 ![](https://static001.geekbang.org/resource/image/59/d8/59e307f779fdd94f43403d8b9baaabd8.jpg) 总的来说,布局动态化相比虚拟运行环境来说,它不仅实现了UI的动态新增和修改,也有着良好的体验和性能,同时接入和学习成本也比较低。 ## 总结 “路漫漫其修远兮,吾将上下而求索”,我们对动态化实践的探索一直没有停止。今年,Flutter也强势地杀入了这个“战场”,那Flutter在跨平台和动态化方面表现如何,我们将在专栏下一期中揭晓。 动态化如今在国内是炙手可热的研究方向,虽然每个公司都强行造了自己的轮子,但是动态化方案目前还有很多没有解决的问题。所以在我们解决这些问题的过程中,也还会不断演变出其他的各种新方案。 现在各种类型的动态化方案,目前都能找到自己的应用场景。移动技术在快速地发展,我们无法准确预料到未来,比如说在Android P我们正准备放弃热修复的时候,Android Q又使它重新焕发了青春。但是我们坚信,无论未来采用何种方案,都是为了给用户更好的体验,同时让业务可以更快地迭代,并在不断地尝试中,给用户提供更好的产品。 ## 课后作业 对于动态化实践,你有什么看法?在你的应用中,使用了哪种动态化方式?欢迎留言跟我和其他同学一起讨论。 跨平台和动态化可以说是大前端时代最大的两个特点,也是每年技术大会的重点。今天的课后作业是仔细阅读下面大会的分享内容,学习各大公司在大前端的实践经验,并留言写写你的心得体会。 * 2018年北京QCon:美团[《美团客户端动态化实践》](https://github.com/AndroidAdvanceWithGeektime/Chapter37/blob/master/Qcon%E5%8C%97%E4%BA%AC2018--%E3%80%8A%E7%BE%8E%E5%9B%A2%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%8A%A8%E6%80%81%E5%8C%96%E5%AE%9E%E8%B7%B5%E3%80%8B--%E6%96%B9%E9%94%A6%E6%B6%9B.pdf)。 * 2018年GMTC:闲鱼[《基于Google+Flutter的移动端跨平台应用实践》](https://github.com/AndroidAdvanceWithGeektime/Chapter37/blob/master/GMTC2018%EF%BC%8D%E3%80%8A%E5%9F%BA%E4%BA%8EGoogle%2BFlutter%E7%9A%84%E7%A7%BB%E5%8A%A8%E7%AB%AF%E8%B7%A8%E5%B9%B3%E5%8F%B0%E5%BA%94%E7%94%A8%E5%AE%9E%E8%B7%B5%E3%80%8B-%E7%8E%8B%E6%A0%91%E5%BD%AC.pdf)。 * 2018年GMTC:京东[《当插件化遇上Android+P》](https://github.com/AndroidAdvanceWithGeektime/Chapter37/blob/master/GMTC2018-%E3%80%8A%E5%BD%93%E6%8F%92%E4%BB%B6%E5%8C%96%E9%81%87%E4%B8%8AAndroid%2BP%E3%80%8B-%E5%BC%A0%E5%BF%97%E5%BC%BA.pdf)。 * 2018年GMTC:小米[《快应用开发与实现指南》](https://github.com/AndroidAdvanceWithGeektime/Chapter37/blob/master/GMTC2018-%E3%80%8A%E5%BF%AB%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E4%B8%8E%E5%AE%9E%E7%8E%B0%E6%8C%87%E5%8D%97%E3%80%8B-%E8%91%A3%E6%B0%B8%E6%B8%85.pdf)。 * 2018年GMTC:绿色守护[《Android+研发的昨天、今天与明天》](https://github.com/AndroidAdvanceWithGeektime/Chapter37/blob/master/GMTC2018-%E3%80%8AAndroid%2B%E7%A0%94%E5%8F%91%E7%9A%84%E6%98%A8%E5%A4%A9%E3%80%81%E4%BB%8A%E5%A4%A9%E4%B8%8E%E6%98%8E%E5%A4%A9%E3%80%8B-%E5%86%AF%E6%A3%AE%E6%9E%97.pdf)。 欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。我也为认真思考、积极分享的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。