使用Flutter进行跨平台移动应用程序开发— Xamarin — React Native:以性能为中心的比较

上周四(2017年8月),我荣幸地在弗吉尼亚州麦克莱恩市的Capital One的iOS峰会上发表了演讲。 这是我第三次参加会议,也是我第二次有机会上台。 场地是一个令人印象深刻的礼堂,组织无可挑剔,听众充满活力和开朗。 我参加了许多有趣的演讲,与来自全国各地的热情软件工程师会面并交流了想法。

我没有演讲视频,但我将分享摘要和幻灯片。 开始了。

移动应用程序开发已经超出了我们最疯狂的梦想。 据估计,全球有20亿部智能手机,其中99%以上运行iOS或Android。 为了吸引更多用户,开发人员需要同时针对这两个平台。 不幸的是,出于多种原因,这样做远非易事。

开发人员必须学习两种语言(在iOS上以前是Objective-C,但现在是Swift;在Android上Kotlin变得比Java势头强劲)和两种平台。 通常,还需要两个平台团队。 这种方法既缓慢又昂贵。 很难调整iOS和Android功能的发布时间表。 至少,App Store审核过程使及时发布iOS更具挑战性。 很难在任何一个平台上提供令人愉悦的体验,甚至更难重复两次。 更糟糕的是,如果并且当需要支持网络时(不幸的是有时是事后的想法),难度将成倍增加。

以前,我们已经看到了跨平台的解决方案,但是它们要么在性能上折衷,要么达到设计期望。 Google,Facebook和Microsoft一直在努力工作,我认为他们现在拥有值得再次关注的解决方案。 我想探索三种流行的框架:Flutter,Xamarin和React Native,解释它们的含义,工作方式,并以性能为重点将它们相互比较。 我的希望是,阅读本文后,您会被启发去检查它们,并足够了解,以便在您的脑海中制定出哪种技术可能是满足您需求的正确选择。

让我们从Flutter开始。 这是由Google开发的开源移动应用SDK。 它为开发人员提供了一种简单而又高效的方法,可以在Android和iOS上构建和部署美观,高性能的移动应用程序。 它的独特之处在于它既不使用Web视图也不使用iOS / Android UI组件,但仍使用其自己的呈现引擎以本机方式绘制所有内容。

Flutter团队没有重新发明轮子,而是对Google内部的项目进行了广泛的研究,并在合理的范围内重复使用了许多现有代码。 他们从Chrome,WebKit,Android中获取了相关内容,并构建了一个苗条的C ++引擎。 他们选择使用Dart构建其余框架。 该框架实现动画,绘画,手势,渲染,并提供Material Design组件的实现以及稀疏的iOS主题组件库。

在iOS上,C ++引擎是使用LLVM编译的,而Dart代码是AOT(提前)编译成本地代码的。 在Android上,C ++引擎使用Android NDK(本机开发套件)进行编译,并且Dart代码在Dart VM(虚拟机)上运行,后者生成JIT(即时)编译的优化本机代码。 实施JIT编译的系统会连续分析正在执行的代码,识别出可能加速的部分,并将这些代码段转换为设备的CPU指令集。 转换后的代码称为本机代码,它直接在CPU上执行,而不是在VM上运行。

Flutter的特定于平台的API支持不依赖于代码生成,而是依赖于灵活的消息传递系统。 应用程序的Flutter部分(客户端)通过平台通道将消息发送到应用程序的iOS或Android部分(主机)。 主机在平台通道上侦听并接收这些消息。 然后,它使用本机编程语言调用任意数量的特定于平台的API,并将响应发送回客户端。

有关典型Flutter应用程序的内存,CPU和GPU利用率的性能讨论,请参考幻灯片。

有趣的是,我在运行这些测试时发现了内存泄漏问题。 我还没有机会向Flutter提交错误,但我将在接下来的几天内提交。

让我们进入列表中的下一个框架Xamarin。 Xamarin是Microsoft开发的一种开源技术,用于使用C#来构建iOS,Android和Windows Phone 8应用程序。 与Flutter相比,Xamarin可以访问本机iOS和Android UI组件。 微软的独特之处在于,它提供了一套称为“移动中心”的综合服务。 Xamarin应用程序可以在此处构建,分发和监视。 推送通知可以通过Mobile Center发送。 他们甚至在称为Xamarin测试云的付费服务中提供了真实的设备UI测试。

Xamarin应用程序是使用Visual Studio开发的。 Visual Studio反映了许多Xcode Interface Builder功能。 无需启动Xcode就可以构建完整的UI和导航模式。 但是,除非您购买了庞大的Enterprise订阅,否则您就无法分析代码,而且我发现,由于进行概要分析是我最想做的最重要的事情,这令人失望。 幸运的是,Instruments能够附加任何流程,而且我能够收集所需的指标。

Xamarin iOS应用程序在Mono Runtime上运行,并且C#代码已AOT编译为本机代码。 Mono运行时与Objective-C运行时并排运行,并通过绑定公开对iOS API的访问。 在Android上,将编译C#代码和绑定并将其链接到APK文件,该文件通过ART(Android运行时)上的JIT执行。 ART和Mono通信通过JNI(Java本机接口)进行。 JNI是一种机制,它使运行在JVM(Java虚拟机)中的Java代码能够被本机应用程序调用和调用。 在这种情况下,ART可以通过ACW(Android可调用包装器)桥来调用Mono上的方法,而Mono可以通过MCW(托管可调用包装器)桥来调用Android代码。

Xamarin支持本地UI组件的方式不错,但需要权衡取舍。 当有新的iOS或Android版本时,Xamarin将不会立即获得完全支持。 有一个历史趋势,即比Android SDK更快地更新iOS SDK。 我无法找到有关此类发布的周转时间的正式确定过程。

有关典型Xamarin应用程序的内存,CPU和GPU利用率的性能讨论,请参考幻灯片。

让我们稍微切换一下齿轮并谈论React Native。 React Native是基于React构建的,React是Facebook的JavaScript库,用于构建Web界面。 React之所以受欢迎是因为它提供了令人印象深刻的性能,尤其是对于快速更改的数据。

React组件通过将HTML嵌入JavaScript中来描述它们的外观,并且框架处理渲染。 同样,React Native组件通过嵌入XAML(可扩展应用程序标记语言)来描述其外观,而框架通过将这些组件映射到其本机iOS或Android对应组件来处理呈现。 例如,XAML标记“ ”映射到iOS上的UILabel和Android上的TextView。

React Native的核心是一个桥梁,该桥梁允许本机代码调用JavaScript,反之亦然。 使用基于JSON的自定义协议,将本机层的事件打包为函数ID,参数,进行序列化,然后通过C ++桥传递给JavaScript层。 JavaScript层通过调用许多本机函数来处理这些事件。 所有这些函数的结果将一起批处理,序列化,然后通过C ++桥异步发送回本机层。 然后,本机层反序列化和处理响应并相应地更新UI。

有两个更受欢迎的框架值得一提,但是我们没有时间深入研究。 我最喜欢的之一是AngularJS Material,它既是UI组件框架,又是Material Design规范的参考实现。 第一个演示中最右边的应用程序是使用AngularJS Material构建的,它的外观和感觉都是原生的,但采用的是Android方式。 我最喜欢的另一个是Ionic,它是一个基于Angular构建的框架,同时提供iOS和Android主题的UI组件。 可以使用Apache Cordova将Ionic应用程序部署在iOS和Android设备上。 NativeScript是一个类似于React Native的架构,并允许使用Angular,TypeScript或JavaScript开发跨平台应用程序。

请参考幻灯片,了解有关典型React Native应用程序的内存,CPU和GPU利用率的性能讨论。

我还认为查看一些针对Dart,C#和JavaScript的计算机语言基准测试很重要。

通常,询问哪种语言更快,因为编写的代码的效率决定了它将以多快的速度运行。 但是,我认为了解在虚拟机上运行的字节码与直接在CPU上执行的机器码可能带来的性能影响是一种好习惯。 有关详细信息,请参阅滑盖。

在演讲结束时,我根据6个标准并排比较了这三个框架:性能(CPU,GPU,内存利用率,二进制大小),可移植性,开发人员经验,生产准备情况,用户体验和寿命。 尽管其中一些是客观标准,但我的结论是,没有一种“一刀切”的解决方案。 每个产品都有不同的环境,每个组织都有不同的挑战。 关键是要提出正确的问题,用知识武装自己,并根据证据做出决定。

我希望您现在对这些技术有一个更好的主意,并且我能够激起您的兴趣去检查这些框架!

有关参考的完整列表,请参考滑板。

2017年10月13日更新:我只好在Flutter演示应用程序中提交有关内存泄漏的错误。 您可以在此处查看详细信息。

2017年11月30日更新:Flutter工程团队正在调查此问题,并发表了以下评论:

“内存使用量持续增加,因为我们将图像保存在 ImageCache中, 并且直到其超过最大大小(默认为1000)时才从缓存中逐出项目。 该应用程序可以设置imageCache.maximumSize来限制缓存大小(以重复获取和解码图像为代价)。

图像将以原尺寸缓存,而不是应用按比例缩小时所呈现的尺寸。 而且,替换图像小部件并执行GC不会影响图像内存消耗,因为图像缓存仍处于填充状态。”

我问是否可以通过忽略重复的图像来避免这种情况(假设确定重复是很重要的),所以我正在等待响应。