哈喽,我是老刘
前几天看到一个新的跨平台框架Skip,它的底层技术方案是转译。
感觉还挺巧的,因为不久前刚刚做了一个内部分享,就讲到了虚拟化的转译方案。
其实跨平台开发的技术和虚拟化的技术有很多相通的地方。
正好本文就把跨平台开发的三种技术方案盘点对比一下。
话说6年前我们刚开始选择Flutter作为跨平台框架的时候,还没有Compose、Skip这些选项。
所以当时做出选择并不难,如果放到今天可能选择困难症就要犯了。
跨平台的三种技术方案
我们知道,跨平台开发的目标就是一套代码多个平台使用。
但是不同系统平台提供的编程语言、SDK等都是截然不同的,于是为了让同一套代码在不同平台能跑起来并且能绘制UI界面,就产生了不同的技术方案。
1、套壳
这里的套壳可不是操作系统的套壳,这里说的其实是一种中间层方案。
这种方案最典型的代表就是RN。
从上面架构图中可以看出来,RN使用JS开发页面和逻辑。
然后把页面相关的信息传递给原生的RN模块。
原生的RN模块把页面的内容转换为原生的各种组件,然后交给原生SDK进行渲染。
中间层是软件工程中最常用的一种解决方案,其最大的好处就是灵活、省事。
有了RN Android和RN iOS两个中间层,就可以在其上随意搭建自己的页面框架了。
这样基本不需要考虑底层系统带来的影响,理论上一切差异都可以在中间层搞定。
当然在实际的实践中做不到这么完美,不过也足以满足工程应用。
当然中间层也有它固有的缺陷。
最大的问题就是性能的损失,从RN来看性能的损失来源于以下几个方面。
中间层本身的消耗
中间层肩负着上下层转换的工作,这本身就会带来内存和CPU等运行期资源的消耗。
相对于原生开发来说,这种损耗是额外增加的。
在客户端这种对流畅性非常敏感的应用场景下,也很容易造成用户体验下降。
上下层通信造成的延迟
要知道,页面不是一次渲染就放在那不动了。
随着用户的操作,页面要随时进行更新。
这就涉及到了上下层的通信。
比如用户在页面上点击了按钮,需要把点击事件从原生SDK传递给JS页面。
JS页面响应点击后也需要把页面的变化传递给原生SDK。
这种跨编程语言运行时的通信,比原生组件和原生SDK间的通信要慢很多。
如果碰到用户拖动一个页面元素移动的场景,比如把阅读App中书架上一本书拖动到另一个位置。
这时用户手指每移动一个像素,就需要进行一次通信。
RN这类框架在面对这类场景时就容易出现比较明显的卡顿。
正式因为中间层方案的这些缺陷,因此才有了后面那些更复杂的解决方案。
2、自定义绘制引擎
Flutter可以说是这种方案的典型代表。
从上面架构图中可以看出来,Flutter重写了一套SDK用以代替原生的SDK。
其实图中没有画出来的是在SDK和Skia引擎中间还应该有一个Flutter 引擎层。
通常SDK只是给开发者提供一个接口。
基于SDK开发出来的页面需要经过Flutter引擎的处理才能传递给Skia进行绘制。
所以简单来说就是Flutter重建了一整套的UI体系。
这样做最大的好处就是Flutter可以做到和原生完全一致的架构。
自然也就没有了中间层方案的性能损耗。
这也是为什么Flutter可以做到媲美原生的性能的根本原因。
而随着Impeller等新一代渲染引擎的投入使用,Flutter在性能上超越原生也是非常有可能的。
但是要记住,每个技术框架都是一把双刃剑,它在解决一些问题的同时,也带来了新的问题。
自带引擎虽然能带来众多的好处,但是也有其代价:
开发成本
庞大的Flutter引擎需要投入大量的人力和资源进行开发和维护。
来看下面这张图,这是2021年的时候Flutter项目的截图
可以看到关闭了5万多个issue,还有1万多个没有关闭。
所以也就不难理解,为什么开发者圈子里对Flutter最大的担心是被官方放弃了。
除了成本问题,自定义引擎还有一个硬伤
无法调用系统功能
前面说的中间层方案,因为最终调用的还是系统级的SDK,所以可以很方便的调用系统的相关功能,只是需要考虑不同系统的差异性问题。
但是以Flutter为代表的自定义引擎方案,就没有这种优势了。
不同系统上只能通过系统的SDK调用系统功能。
比如Android上只能基于Android SDK调用手机的定位、相机等功能。
Flutter的整套体系都是自定义的,而且Dart语言也无法直接调用Java体系的SDK。
这就造成了Flutter本身只能是一个UI系统,当我们的App需要用到系统的各种功能时,Flutter只能通过一些外部的手段解决。
当然,Flutter通过channel机制已经可以比较方便的调用原生系统的功能了。
但是如果是碰到摄像头实时预览这类数据量很大的功能,仍然会有性能方面的损耗。
自定义引擎的方案的优缺点说完了,这里要提一下Compose。
本质上Compose也属于自定义引擎方案,它的架构如下:
对比Flutter,它的主要差异是为不同的系统平台定义了针对性的UI框架。
这样做的好处是比如在Android平台,他就直接算是原生开发了。
但是这样的代价就是不能做到真正意义的一套代码多个平台运行。
因为你至少要为每个平台写一套UI。
如果你想要做到业务逻辑代码的跨平台,还需要在架构上进行比较精细化的设计。
那有没有一种方案,既能保留中间层可以灵活兼容不同系统差异的优势,又能拥有自定义引擎的高性能呢?
也是有的,那就是接下来要说的第三种解决方案。
3、转译
这里要说的就是Skip了。
它的架构简单来说就是用Swift UI写APP。
在iOS上就是原生项目,直接可以运行。
在Android上会有一个转译工具,工具会把Swift UI项目转译成Kotlin Compose项目,然后就变成了Android的原生项目了。
架构如下:
这种方案本质上就是写一套iOS代码,然后通过工具翻译成不同平台的原生代码。
所以你最终得到的是多套不同平台的原生代码。
这样的好处是显而易见的:性能无缺,功能完备。
但是我并不是太看好这个框架,或者说我对转译这条路是有一定的疑虑的。
我觉得主要有三个问题需要解决:
编程语言层面的映射问题
记得早年做网络安全的时候,我们就有工具能把exe文件转译成c代码。
看起来很强大很美好是吧?
但其实转译出来的c代码和你一开始写的c代码是有很大差异的。
要明白,转译工具本身也是一个程序,它不可能像人一样先理解被转译代码的逻辑,然后用新的代码再实现一遍。
它的工作原理简单来说就是按照固定的映射关系去进行转换。
那转化后的代码逻辑能保证和原来的完全一样吗?
其实很难,因为不同语言的设计思想可能差异很大,你如果不能理解代码的逻辑,只是简单的映射其实很多功能是很难实现的。
举个例子,如果把函数式语言中的闭包转换为一个不支持函数式编程语言的代码,要如何转化呢?
SDK框架的映射问题
语言的转译只是其中一个问题,还有一个问题是功能组件的转译。
比如iOS中的.onAppear和.onDisappear,再比如iOS中的众多动画效果。
这些效果单独拿出来在compose中都能实现相同的效果,但是把整个框架映射过去就是另一回事了。
更不要说随着iOS和Android系统和sdk的升级,带来的不同版本间对应关系的问题。
三方库的管理
我们开发一个稍微大型一些的项目都需要用到三方库。
即便系统SDK的映射能完美的解决,但是用到的三方库肯定是没办法映射过去的。
这时候就需要把一些代码拆分出来分别管理了。
这也是一个项目层面需要考虑的难点。
这部分就不展开细说了,有项目经验的同学应该能很容易理解。
好了,前面说了我觉得这个解决方案的主要问题和难点。
接下来说说我认为这种方案未来的发展方向。
转译方案的未来
对比一下前面介绍的三种跨平台技术方案。
中间层方案因为其架构上固有的缺点,已经逐步被自定义引擎的方案取代其主流地位。
以Flutter为代表的自定义引擎方案目前被工程界广泛接受。
转译这种技术本身其实已经出现很多年了,但是由于转译工具本身的能力限制,一直没有成为主流。
不过当下AI的发展给转译这条路带来了新的希望。
转译工具在功能上一直无法突破的一个底层原因是它无法去理解被转译代码的逻辑,所以只能按照固定套路去转译。
但是当前AI已经能在很大程度上理解代码的功能逻辑了。
(注意当前的大语言模型并不是真正的建立逻辑思维,和一个程序员去理解代码逻辑还是有区别的)
基于AI的,建立在理解代码逻辑基础上的转译工具,可能是转译方案未来的发展趋势。
我们展望一下,如果我们能基于大语言模型,训练一个把Swift UI转换成Compose或者反过来的AI工具。
那么这种转化其实本质上就相当于一个程序员把Swift UI的代码用Compose重新实现了一份。
转译这种技术方案的优势就能彻底发挥出来,相当于一次写了两套甚至多套原生代码。
这样的话转译的技术方案就能和自定义引擎的方案分庭抗礼并驾齐驱了。
更进一步,最初始的那套Swift UI代码也交给AI完成。
好吧,我们程序员差不多可以失业了。
总结
前面我们盘点了目前跨平台开发框架的主流技术方案。
其中转译方案目前虽然不是主流,但是随着AI的发展,未来潜力还是很大的。
立足当下,我仍然认为Flutter是当前跨平台的最佳选择。
即便未来转译方案真的能有突破性的进展,在端一致性等方面Flutter这样的独立引擎方案仍然具备优势。
另外,如果转译方案能有突破,那把Flutter代码翻译成原生代码似乎也是不错的选择。
好了,如果看到这里的同学有学习Flutter的兴趣,欢迎联系老刘,我们互相学习。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》https://mp.weixin.qq.com/s?__biz=MzkxMDMzNTM0Mw==&mid=2247483665&idx=1&sn=56aec9504da3ffad5797e703c12c51f6&chksm=c12c4d11f65bc40767956e534bd4b6fa71cbc2b8f8980294b6db7582672809c966e13cbbed25#rd