移动客户端跨平台开发方案探索

2018-02-12  本文已影响0人  cowboy3000

本文是 Piasy 原创,发表于 https://blog.piasy.com,请阅读原文支持原创 https://blog.piasy.com/2017/12/16/Mobile-Client-Cross-Platform-Development/

跨平台开发想必很多朋友都听说过,甚至实践过,这里我就不过多介绍相关的背景了,Java 的 Slogan 完美诠释了这一愿景:Write once, run anywhere!

提到这个话题,大家首先想到的可能是 React Native,不过本文并不是 RN 教程。本文旨在探索我关注到的几种比较靠谱的移动客户端跨平台开发方案,当然 RN 是其中必不可少的一部分。

完美的跨平台开发方案自然是一行平台相关的代码都没有,但这个要求稍显苛刻,很多时候退一步海阔天空,在移动客户端跨平台开发这个场景下也是这样。所以我们的目标应该是尽可能地减少平台相关代码的开发,最大程度复用核心逻辑代码。

此外,客户端的开发可以分为两部分:业务逻辑与界面交互。由于不同的平台(Android、iOS、Web)GUI 系统差异较大,通常来说界面交互共享代码更加困难。这对于 APP 开发确实是个不小的问题,但对于 SDK 开发来说却影响较小。

可选方案

接下来我们先看看有哪些可选方案:

其实如果要把小程序列入其中,也未尝不可,毕竟能在不同平台的微信和支付宝里运行,也堪称跨平台了。

注:上述各项技术的基本概念本文不会过多介绍,请大家自行查阅官方文档

2017.12.22 更新:之前误以为 Weex 是和小程序一样的技术,但它是和 RN 对标的呀,实在汗颜,日后再做补充……

React Native

RN 由 Facebook 开源,Facebook 内部很多项目在用,工业界也有不少公司和团队都在使用。这样的项目,坑肯定有,但一定不会没人填。

RN 的主要优势有两大部分,一是 React 本身的优势,二是将「React」「Native 化」之后跨平台的优势。这里我主要总结下 React 本身的优势(或者说特性):

我觉得下面这幅图形象地表达了 React 的设计精髓:building large applications with data that changes over time

image

内部原理:

总结:RN 利用 JavascriptCore VM 在 iOS/Android 系统上执行 JS 代码,实现了业务逻辑跨平台;通过 JS 和 native 互调完成 UI native 化,实现了界面交互跨平台。虽然 JS VM 解释执行、跨边界调用存在一定的性能开销,但大部分情况下它们都不会成为瓶颈,如果开发任务大多集中在界面交互、非计算密集的业务逻辑上,RN 是不错的选择。

Flutter

Flutter 由 Google 开源,项目年龄比 RN 短大半年,目前仍处于 alpha 状态,没有正式发布版(RN 虽然更新频繁,而且还处于 0.x 版本,但好歹也算发布了)。

优势:

内部原理:

image

总结:Flutter 利用 Dart 代码可以预编译为机器码这一特性,实现了业务逻辑的跨平台;通过自己实现一套跨平台的 GUI 系统,实现了界面交互的跨平台。由于最终执行的代码都是机器码(C++,Dart),而且跨平台的实现基本不涉及 Dart 代码与 native 代码互相调用,所以 Flutter 的效率肯定是有保障的。在涉及到动画、手势等强交互 UI 特性时,Flutter 的性能应该比 RN 要高不少。Flutter 还自带了一套 Material Design 的跨平台控件库,简直良心。

Web 端呢?Dart 可是能运行在浏览器里的呢!

Dart runs fast in every modern browser, on the command line, on servers, and on mobile.

不过遗憾的是,虽然 Dart 代码可以在浏览器运行,但却无法和 Flutter APP 共用 UI,Flutter 团队也不打算开发 Web 支持

J2ObjC + GWT

J2ObjC 和 GWT 都由 Google 开源,分别用来把 Java 代码编译为 ObjC 代码和 JS 代码,用来在 Android、iOS、Web 端共用业务逻辑代码,界面交互代码则需要单独开发。

这个特性简直就是 SDK 开发者的福音,事实上这也正是我在最近两个月在公司项目中实践的方案。这套跨平台方案没有什么炫酷的技术,直接把代码编译为各个平台开发语言的代码,简单粗暴但行之有效。

这里我简单总结下我在实践中感受到的一些要点:

Djinni + WebAssembly

Djinni 由 Dropbox 开源,它的核心思想是用 C++ 开发业务逻辑,利用 JNI、Objective-C++ 实现 C++ 和 Java、ObjC 的互相调用。

那这是 JNI 和 Objective-C++ 实现的跨平台呀,和 Djinni 有啥关系呢?做过 NDK 开发的朋友肯定知道,要实现 C++ 调用 Java 非常麻烦,boilerplate code 很多,Djinni 的发力点就在于减少 boilerplate code,声明跨边界的数据结构和接口,自动生成 C++、Java、ObjC 跨边界调用相关的代码,让我们可以聚焦于真正的业务逻辑开发中。

Djinni 生成代码的思路和 J2ObjC 编译代码的思路异曲同工,也都是简单粗暴的办法。接触到 Djinni 是半年前,最近趁着总结这篇文章的机会,简单实践了一把,还谈不上有太深的感悟,唯一一点是 Djinni 生成的 record 在跨边界时,是 immutable 的,因为很难保证跨边界状态维护,设计接口时需要考虑这一点。

这个方案是在 Dropbox 内部大量实践后再开源的,而且生成的代码并不多,稳定性不存在太多的风险,靠谱程度还是很高的。如果 SDK 的业务逻辑想在 C/C++ 代码中实现,那这套方案就是良策了。

理论上来说,我们的 C++ 代码也是可以编译为可以被 WebAssembly 加载的代码的,那在 Web 端使用应该也是可行的,当然,一层 JS wrapper 必不可少。

Demo time

光说不练假把式,demo 自然是需要的。但逼近年关,公司项目太忙,只来得及做完后两种方案实现 Android iOS 跨平台的 demo,剩下的部分,只能来年再补了。

毕竟「done is better then perfect」

Caveat time

绿色守护的开发者冯森林老师曾在一次 MDCC 上说:如果一个项目只说自己如何如何牛,却对自己的限制、坑闭口不提,那这个项目一定不靠谱。

坑肯定是少不了的,这里记录下我踩过的那些坑。

React Native

ReactNative 某个标签对应的 native view,如果修改 child view(增减、调整大小),都不会触发这个 native view 自身的 layout,所以需要自己手动触发。参考:GitHub issue

J2ObjC

包装类型使用注意:对 List<Long> 调用 .contains(long),判断将失效,生成的 ObjC 代码在比较指针。

参考文章

React Native:

Flutter:

上一篇 下一篇

猜你喜欢

热点阅读