React Native移动开发实战
上QQ阅读APP看书,第一时间看更新

1.2 React Native与其他跨平台技术的对比优势

曾经大部分开发者以为可以通过Web技术来实现跨平台移动开发,却因为性能限制或其他问题而放弃,最终,不得不针对多个平台开发多个版本,这违背了跨平台开发的初衷。而React Native的出现让跨平台移动端开发再次回到人们的视野中,而它提倡的“Learn once, write anywhere”也赢得了广大开发人员的青睐。相比传统的H5技术,React Native获得了更加接近原生应用的体验。

为了方便理解,笔者将跨平台技术分为四大流派。

· Web流:也被称为Hybrid技术,它基于Web相关技术来实现界面及功能。

· 代码转换流:将某个语言转成Objective-C、Java或C#,然后使用不同平台下的官方工具来开发。

· 编译流:将某个语言编译为二进制文件,生成动态库或打包成apk/ipa/xap文件。

· 虚拟机流:通过将某个语言的虚拟机移植到不同的平台上来运行。

1.2.1 Web流

Web流,如大家熟知的PhoneGap/Cordova等技术,它将原生的接口封装后暴露给JavaScript,然后通过系统自带的WebView运行,也可以使自己内嵌Chrome内核。

Web流缺点是性能差、渲染速度慢。说它Web性能差,主要说的是在Android下比较差,在iOS下已经很流畅了。

性能差的主要原因是,在Android和iOS的早期设备中,由于没有实现GPU加速,所以会造成每次重绘界面的卡顿。

而造成渲染慢的第二个原因是:CSS过于复杂。因为从实现原理上看,Chrome和Android View并没有本质上的差别,但过于复杂的CSS会加重GPU的负担。那是不是可以通过简化CSS来解决呢?实际上还真有人进行了这种尝试,比如著名的Famo.us,其最大的特色就是不使用CSS,只能使用固定的几种布局方法,完全依靠JavaScript来写界面,它能有效避免低效的CSS代码,从而提升机器性能。

造成绘制缓慢的第三个原因是,业务需求的复杂,比如超长的ListView商品展示。因为DOM是一个很上层的API,使得JavaScript无法做到像Native那样细粒度地控制内存及线程,所以难以进行优化,特别是在硬件较差的机器上。

上面三个问题现在都不好解决。其实除了性能之外,Web流更严重的问题是功能缺失。比如iOS 8就新增4000多个API,而Web标准需要漫长的编写和评审过程,而等到Web审核通过,即便是Cordova这样的优秀的框架,或者自己封装也是忙不过来的。所以为了更好地使用原生系统新功能,Native是最快的选择。

1.2.2 代码转换流

不同平台下的官方语言不一样,并且平台对官方语言的支持最好,这就导致对于同样的逻辑,我们需要写多套代码。比如Android平台用Java, iOS用Objective-C或者Swift。于是就有人想到了通过代码转换的方式来减少重复的工作量,这就是代码转换流。

这种方式虽然听起来不是很靠谱,但它的成本和风险都是最小的,因为代码转换后就可以用官方提供的各种工具了,和普通开发区别不大,而且转换后,利用原生的优势,可以减少兼容性问题。

目前存在以下几种代码转换方式。

将Java转成Objective-C

2objc是一款能将Java代码转成Objective-C的工具,据说Google内部就是使用它来降低跨平台开发成本的,比如Google Inbox项目就号称通过它共用了70%的代码,效果很显著。有了2objc,我们就可以先开发Android版本,然后再开发iOS版本。

将Objective-C转成Java

MyAPPConverter是一款将Objective-C代码转换成Java代码的工具,比起前面的2objc, MyAPPConverter还打算将UI部分也包含进来,从它已转换的列表中可以看到还有UIKit、CoreGraphics等组件,使得有些应用可以不改代码就能转换成功。

XMLVM

除了上面提到的源码到源码的转换,在代码转换流中,还有XMLVM这种与众不同的转换方式,它首先将字节码转成一种基于XML的中间格式,然后再通过XSL来生成不同语言,目前支持生成C、Objective-C、JavaScript、C#、Python和Java。

虽然基于中间字节码可以支持多语言,但是这种方式也有一些问题,例如生成代码不可读,因为很多语言中的语法会在字节码中被抹掉,并且是不可逆的,所以不利于代码的调试和发现问题。

综上所述,虽然代码转换这种方式风险小,但对于很多小APP来说其实共享不了多少代码,因为这类应用大多数围绕业务来开发的,大部分代码都和业务逻辑耦合,所以公共部分不多,其意义不大。

1.2.3 编译流

编译流比代码转换流的代码转换更进一步,它直接将某个语言编译为普通平台下能够识别的二进制文件。采用这种方式主要有以下特点。

优点

· 可以重用一些实现很复杂的代码(比如之前用C++实现的游戏引擎,重写一遍的成本太高)。

· 编译后的代码反编译困难,安全性更好。

缺点

· 转换过于复杂,并且后期定位和修改成本会很高。

· 编译后体积太大,尤其是支持ARMv8和x86等CPU架构的时候。

常用的编译流方案如下所示。

C++方案

因为目前Android、iOS和Windows Phone都提供了对C++开发的支持。特别是C++在实现非界面部分,性能是非常高的。而如果使用C++实现非界面部分,还是比较有挑战的。这主要是因为Android程序的界面绝大部分是Java编写的,而在iOS和Windows Phone平台下可以分别使用C++的超集Objective-C和C++/C#来开发。要解决使用C++开发Android应用程序界面的问题,目前主要有两种方案。

· 通过JNI调用系统提供的Java方法。

· 自己实现UI部分。

第一种方式虽然可行,但是代码冗余高,实现过于复杂。那第二种方式呢,比如JUCE和Qt就是用代码实现的。不过在Qt的方案中,Android 5版本或更高版本环境下,很多效果都没法实现,比如按钮没有涟漪效果。根本原因在于它是通过Qt QUIck Controls的自定义样式来模拟的,而不是使用系统UI组件,因此它享受不到系统升级自动带来的界面优化。

当然我们可以使用OpenGL来绘制界面,因为EGL+OpenGL本身就是跨平台的。并且目前大多数跨平台游戏底层都是这么做的。

既然可以基于OpenGL来开发跨平台游戏,那么,是否能用它来进行界面开发呢?当然是可行的,而且Android 4的界面就是基于OpenGL的,不过它并不是只用OpenGL的API,那样是不现实的,因为OpenGL API最初设计并不是为了实现2D界面,所以连画个圆形都没有直接的方法,因此Android 4中是通过Skia将路径转换为位置数组或纹理,然后再交给OpenGL从而完成界面渲染的。

然而直接使用OpenGL绘制界面,不仅实现的代价大,而且目前支持的平台少。因此对于大多数应用来说自己实现界面是很不划算的。

Xamarin

Xamarin是从Mono发展而来,它用C#来开发Android及iOS应用,因为相关工具及文档都挺健全,因而发展得还不错。在UI界面方面,它可以通过调用系统API来使用系统内置的界面组件,或者基于Xamarin.Forms开发定制要求不高的跨平台UI。

从实现的方式来讲,iOS下是以AOT的方式编译为二进制文件的;而在Android平台上是通过内嵌的Mono虚拟机来实现,所以Xamarin是跨平台开发的不错选择。

对于熟悉C#的团队来说,Xamarin是一个很不错的方案,但这种方案最大的问题就是相关资料不足,遇到问题很可能搜不到解决方案,并且当前第三方库太少,加之Xamarin本身有些bug,所以让我们静待Xamarin做得更好吧。

Go

Go做为后端服务开发语言,专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++程序的速度,而且更加安全、支持并行进程。Go从1.4版本开始支持开发Android应用(1.5版本支持iOS)。虽然能同时支持Android和iOS,但是目前可用的API很少,Go语言仍然专注于后端开发。

目前,Android的View层完全是基于Java写的,要想用Go来完成界面的开发不可避免要调用Java代码,而在这方面Go还没有简便的实现方式,目前Go调用外部代码只能使用Cgo,通过Cgo再调用jni,这就不可避免地需要写很多的中间件。而且Cgo的实现本身就对性能有损失,除了各种无关函数的调用,它还会锁定一个Go的系统线程,这会影响其他gorountine的运行,如果同时运行太多外部调用,甚至会导致所有go线程处于等待状态。

所以,目前使用Go开发跨平台移动端应用并不靠谱。

1.2.4 虚拟机流

编译流是将代码编译为不同平台下的二进制文件,而另一种更彻底的做法是:通过虚拟机来支持跨平台运行,比如JavaScript和Lua都是天生的内嵌语言。不过使用虚拟机进行跨平台开发最普遍的两个问题是:性能损耗;虚拟机本身也会占据不小的空间。

Java虚拟机

说到虚拟机,大家肯定首先想到的是Java,因为Java一开始就是为跨平台而设计的,Sun的J2ME早在1998年就有了,在iPhone手机出来之前,很多小游戏都是基于J2ME开发的。前几年,微软为了支持移动端项目的发展,提供了一套将Android和iOS代码快速转移到Windows Phone的工具,不过后来不了了之。

前面提到C#和Java在iOS端的方案都是通过AOT的方式实现的,目前还没见到有Java虚拟机相应的方案,主要原因是iOS方面的限制。

Titanium/Hyperloop

Titanium和PhoneGap几乎是同时期的著名跨平台方案,和PhoneGap最大的区别是:它的界面没有使用HTML/CSS,而是自己设计了一套基于XML的界面框架Alloy。Titanium的代码风格如下:

        APP/styles/index.tss
        ".container": {
        backgroundColor:"white"
        },
        // This is APPlied to all Labels in the view
        "Label": {
        width: Ti.UI.SIZE,
        height: Ti.UI.SIZE,
        color: "#000", // black
        transform: Alloy.Globals.rotateLeft // value is defined in the alloy.JS file
        },
        // This is only APPlied to an element with the id attribute assigned to "label"
        "#label": {
        color: "#999" /* gray */
        }

虽然学习成本低,但Titanium同样面临着其他跨平台框架都存在的挑战:缺乏第三方库支持、对外的API较少。Titanium也意识到了这个问题,所以目前在开发下一代的解决方案Hyperloop,它可以将JavaScript编译为原生代码,这样开发者可以方便地调用原生API。比如调用iOS的写法如下:

        @import("UIKit");
        @import("CoreGraphics");
        var view = new UIView();
        view.frame = CGRectMake(0, 0, 100, 100);

这个方案和之前讲到的Xamarin如出一辙,也是将JavaScript翻译为Objective-C,然后交由官方系统运行。不过这个项目已经开发了快三年了,但至今仍然是试验阶段,笔者不建议尝试。

React Native

React Native是由Facebook开源的基于JavaScript和React搭建的一套跨平台开发框架。在设计之初,React Native采用的方案就是在不同平台下使用平台自带的UI组件来完成界面的绘制,再加上它采用JavaScript和React等前端语言来开发,所以获得了不少前端程序员的青睐。

有人说,React Native采用JS等前端技术来开发移动APP是回归H5,但其实React Native和Web扯不上太多关系,React Native虽然借鉴CSS中的Flexbox、navigator、XMLHttpRequest等API的写法,但是大部分还是通过原生的组件或者自己封装的组件来开发的。就像Facebook的内部软件Facebook Groups, iOS版本很大一部分基于React Native开发,其中用到了不少内部通用组件。

React Native相比传统原生开发,学习成本还是比较低的,熟悉JavaScript的开发者可以迅速实现界面,而使用标签加CSS样式表方式绘制的界面,远比原生使用代码绘制的界面更加易读,并且一套界面同时满足Android和iOS平台,这对于讨厌绘制界面的开发者来说是多么的诱惑。再加上React Native师出名门,截至目前,React Native已更新到0.4.4版本,并且趋于稳定。由于其更加接近原生的体验,国内一些大厂纷纷加入,诸如阿里、腾讯、美团等纷纷开始使用React Native改造一些应用型APP。

所以,不管是对于个人还是团队,现在跨平台开发做得最好的就是React Native,并且随着开源力量的加入,React Native会发展得越来越好。