聊一聊跨端那些事

326 阅读6分钟

核心观点:

  1. ReactNative使用系统组件,端差异带来的问题往往会超出想象。
  2. Google使用Dart这样几乎没有生态的语言作为Flutter的代码执行层是一次赌博。
  3. JS的性能最大缺陷:无字节码标准。

主流跨端方案

说到跨端,现在流行的主要是3种:

  • webview UI渲染使用标准的webview渲染,代码执行使用和webview绑定的JS引擎。
  • ReactNative UI渲染使用系统组件,代码执行使用JSCore或者v8等JS引擎。
  • Flutter UI渲染使用基于skia的自定义渲染引擎,代码执行使用使用dart VM。

可以看出上面几个主流的跨端方案,主要分为UI渲染和代码执行的选型。

UI渲染

可以选择的是:

  1. 使用Flutter基于skia构建的自定义渲染引擎。
  2. 使用webview的浏览器渲染。
  3. 使用Android和iOS原生系统渲染。

代码执行

可以选择的是:

  1. JSCore或v8这样的JS引擎
  2. Dart VM这样比较小众的虚拟机

先来聊一聊UI渲染

当我们进行技术选型时需要考虑的点有渲染性能,稳定性, 端差异性。

这里分别解释下

  • 渲染性能: 作为渲染引擎,渲染的流畅度是1个非常重要的指标。
  • 稳定性: 使用该渲染方案是否会造成较多的Crash或者ANR。
  • 端差异性: 因为使用了跨端方案,coder 在写代码的时候只会写1份代码,这个时候如果Android和iOS表现不一致的情况很多,那么效率则会大大降低。

从渲染性能看,原生的Android和iOS配合系统有大量优化,性能最好。flutter基于skia的渲染引擎构建的自定义渲染性能也不错,webview性能较差。

从稳定性看, 由于三种方案都是较为主流的方案所以稳定性都不错,但其中webview由于大量内存占用以及native交互,稳定性相对偏差一些。

从端差异性看,webview和Flutter的自定义渲染引擎从底层就是跨平台的,所以端差异性交好,而RN使用了端上的UI组件,iOS和Android端组件天然就有很大差异,所以端差异性较大。

这里需要重点强调的是ReactNative使用上层抹平端组件差异的方案其成本远远大于大部分人的想象,可能80%的适配可以用20%的时间完成,但剩余20%不止是用80%的时间完成,甚至是永远都无法抹平。

再来聊一聊代码执行

其实选择逻辑执行的方案的核心就是到底选择哪一门语言。我们需要考虑的是语言的性能以及语言生态。

  • 语言的性能,语言的执行速度肯定是影响语言选择的一大原因。
  • 语言的生态,语言的生态决定我们使用语言写代码的效率,是否能够找到足够多的lib来支撑。
  1. JS引擎 这里可以具体聊一聊JS引擎的特殊之处,JS引擎由于在浏览器场景被大规模应用,而在一开始并未制定标准的JS字节码标准,这也导致了主流的JS引擎都是在运行期编译,可想而知启动运行一份JS代码肯定会很慢。但JS的强大之处在于其生态非常成熟,几乎需要的功能都可以找到对应的lib,还有react这样已经成为前端事实标准的框架,开发效率非常高。

  2. 类似Dart VM这样的小众虚拟机 使用Dart VM的好处是历史包袱很小,比如Dart支持的AOT就为Dart执行带来很大的性能提升。但其致命缺陷就是在Flutter之前的Dart几乎没有生态,这就导致了开发Dart时很多lib需要自建。

就语言生态这一评判标准,至今我依然认为Google使用Dart这样几乎没有生态的语言作为Flutter的逻辑执行层是一场赌博,目前看这次赌博还没有完全输,因为的确Dart的生态在逐步完善,一些新APP启动也使用Flutter但离赢还非常遥远。

再看主流跨端方案

webview

webview的技术方案,是古老而成熟的,其优势是无论webview的渲染还是JS的执行都是非常标准化的,所以开发效率很高,但核心就是慢,无论是webview的渲染以及JS的执行都很慢,国内很多公司都推出了自己的webview内核,比较著名的是X5 U4E,很大的一个原因就是为了提升打开速度。

ReactNative

在渲染层面上RN使用了原生渲染,高性能渲染的同时带来了一个致命问题,那就是Android和iOS本身差异的抹平工作量,比如列表,比如手势分发,这也是RN被诟病的一点。

使用了JSCore和V8作为执行引擎,也存在2个问题 启动慢和跨线程通信:

对于启动慢的问题,国内外各自采用了不同的方案,比如facebook就推出了自己的JS引擎hermes,其支持直接运行字节码,这样可以避免编译发生在运行期,加速JS引擎的启动速度。还有一种方案是既然JS启动慢,那就初始化渲染时,脱离JS引擎,这样不会影响首屏渲染,其理念有些和H5的SSR有些类似,但这种方式也给开发灵活性带来很大局限。

跨线程通信的问题,是指RN使用的原生组件渲染,但无论Android还是iOS渲染调用都必须在主线程,但JS引擎有着自己的执行线程,这就使得代码执行和UI渲染需要跨线程通信,造成额外性能开销,虽然像weex使用binding的方法进行优化在特定场景减少了跨线程通信,但依旧无法解决所有场景的问题。

Flutter

从UI渲染角度看Flutter的自定义渲染引擎性能还不错,而且没有严重的端差异问题。

从代码执行层面看Dart VM性能不错,关键在于后面Dart的生态建设。国内也有一些公司将Flutter代码执行层从Dart替换为JS, 也是为了解决生态问题, 但同时也带来了性能的下降。另外Dart的线上模式默认不支持动态更新也是需要考量的​。

最后

对于跨端方案,还是要结合具体场景进行合适的选择, 比如在一些非核心的页面可以使用webview的方案这样能够最大提升开发效率,在一个较大的成熟应用中使用RN的方案应该可以和现有的端组件进行有效结合。新启动APP时可以尝试使用Flutter(个人依旧不是很推荐,但其他场景Flutter更加不适合)。