为什么Flutter获胜?

去年,我一直在为iOS和Android编写Flutter应用程序。在此之前,我在Xamarin拥有5年的经验。这已经是一个美好的五年。多亏了Xamarin和对这个框架的热爱,我从原则上转移到了开发人员的阵营,该工具帮助我赚了很多钱,知识并找到了出色的同事。那我为什么现在要写Flutter?简短的答案,因为Flutter涵盖了跨平台开发的所有需求。


一点历史


如果我错了,请指正我,但是2009年在很多方面是总体上移动开发,尤其是跨平台开发的关键。 2009年,发布了iPhone 3gs,使您可以从AppStore运行第三方应用程序。这种机会第一次出现在一年前的iPhone 3g中,但是3gs已经成为真正的大型,“受欢迎”的iPhone。还是在一年前的2008年9月,Android首次向公众推出。2009年,许多手机制造商开始尝试将Android用于其新手机型号。Nitobi 2009 年春季推出了PhoneGap,这是一个用于基于HTML5,CSS和JS创建跨平台应用程序的新框架。同年九月Ximian发布了MonoTouch,使您可以使用Mono和C#编写iOS应用程序。在2009年的同一年12月,Rovio Entertainment发行了一款适用于iOS的游戏,还有一刻是Maemo,它在许多方面标志着移动游戏产业的开始-愤怒的小鸟。这里的最后一个例子并非偶然。

可以将第一个跨平台框架“为人民”视为PhoneGap(Qt开发人员,不要扔石头)。这是一个很棒且非常明显的想法-将网络带入移动开发领域。到2009年,Web的功能已经开始扩展到浏览器之外(hello node.js),而用JS编写Web应用程序则非常简单。同样重要的第二点是UI的呈现。呈现的方式取决于浏览器引擎,所有这些引擎或多或少都遵循W3C针对HTML,CSS和DOM的标准。组成网站的任何Web开发人员都希望他的网站看起来几乎在任何平台上的任何浏览器中都相同。我认为,这是Web作为开放平台的最重要方面。如果长期以来存在为不同浏览器建模UI的标准,为什么还要学习一种新的语言/框架来为每个平台绘制UI。

之后,Cordova从PhoneGap和Ionic剥离出来。看来这是一个理想的框架,但是有两点:性能和OS集成。在跨平台解决方案上编写的主要目标之一,或者,如果需要,还可以作为应用程序基准测试之一,是它们的“本机性”。那些。理想情况下,100%的用户应考虑您的跨平台应用程序是本机的。这意味着它应该看起来像本地的,可以像本地的一样工作,并且可以与操作系统进行所有可能的集成。最初,PhoneGap的所有这些点都是无法实现的,十年前智能手机的容量不足以实现60 fps的UI渲染,与操作系统的集成极少。现在,Ionic上有许多应用程序很难与本机应用程序区分开,但是模仿本机应用程序仍然是一项任务。并非如此。让我们总结一下。编写Web应用程序,或者在iOS和Android上混合应用程序是可能且方便的。 UI呈现机制完全位于WebView平台上,再加上已经受过训练的程序员精通网络,因此非常方便。但是,在混合应用程序中,性能和OS集成可能很差。

与PhoneGap同时,MonoTouch于2009年推出,后来更名为Xamarin.iOS。同样,在同一年,Titanium也发布了,它也允许使用JavaScript编写iOS应用程序。最初,Titanium的工作原理与PhoneGap完全相同-依靠WebView。但是后来他们采用了Xamarin方法。这是什么方法?可以将其视为中间的东西。 Xamarin / Titanium / React.Native的方法是,该框架无需尝试创建/迁移您现有的UI渲染,而是将其与现有的本机集成在一起。

Xamarin无需为此以HTML形式绘制表单,而是为此调用了本机UI元素(UITextField,TextEdit等)。确实,为什么要重新发明轮子?所有必需的UI元素都存在于本地SDK和运行时中,您只需要学习如何从VM(mono,v8等)与它们进行通信即可。同时,正如您已经猜到的那样,您可以使用自己喜欢的C#,JS,TS,F#,Kotlin等,并且同时不直接与UI交互的代码是100%跨平台的。您可以走得更远。相同的UITextField和TextEdit在概念上是相同的实体,它们具有许多相似的属性和交互接口,因此,您可以创建一个抽象Entry(您好Xamarin.Forms)并仅使用它,因为这种情况很少(不是很)异常,直到平台UI元素。我没有提到,如果您的vm可以原生使用UI,那么您的vm很可能可以调用任何平台API。这似乎是完美的选择。本机用户界面,本机性能(hi.bridges in React.Native),100%操作系统集成。这真的很完美吗?最有可能-否,问题是实际上这些解决方案不能解决单个UI的跨平台开发问题。他们掩饰她。我想写一次,到处跑。对于所有类型的程序和问题,这都不是最好的座右铭,但是它非常适合UI。无论平台如何,我都希望为所有人编写相同的UI。为什么Web开发人员可以允许他自己使用HTML和CSS编写网站,然后在iOS的Safari浏览器和Android的Chrome浏览器中以相同的方式显示网站,但没有本机开发人员?

实际上,程序员长期以来一直在为iOS和Android编写具有通用代码库的高性能UI。这些程序员称为游戏开发人员。 《愤怒的小鸟》是在Cocos2d-x引擎上编写的,《团结》中的Cuphead和虚幻引擎上的《堡垒之夜》都是写的。如果游戏引擎能够在手机上显示令人惊叹的场景,那么带有平滑动画的按钮和列表肯定可以。那么,为什么没有人以此来使用它们呢?答案是简单而平庸的,它们并非为此目的。当您打开游戏时,完全取决于手电筒的UI看起来像原生界面,您几乎不需要与地理位置,按钮,摄像机等进行交互。在玩游戏时,您可以在UIViewController / Activity中通过Canvas渲染的小世界中过着不同的生活。因此游戏引擎与OS的集成相对较差,因此没有(或者我没有看到)模仿本机UI顶级引擎。

小计


对于理想的跨平台框架,我们需要:

  • 本机UI映射
  • 本机UI性能
  • 100%的能力,可以调用任何OS API,就好像它是本机应用程序一样

您现在认为在Flutter之下我会失败,但是我已经听到愤怒的评论:“ Qt在哪里!?他可以做到所有这些!” 确实,Qt在某种程度上符合这些标准。尽管我强烈怀疑第一个。但是Qt的主要问题不是编写本机UI的困难,主要问题是C ++。然后,我已经从加油站上的人工编码器吐口水了。优点是合成代谢类固醇的瑞士刀,在优点上您可以做的一切。但是,作为前端开发人员,我不需要所有这些。我需要一种可以与UI和I / O一起使用的简单易懂的语言。因此,在上述三点中添加了:

  • 易学且表达能力强的语言
  • 非常适合前端开发范例的Rantime

好了,既然我们已经重点介绍了用于开发移动应用程序的优秀跨平台工具的一些指标,我们可以逐一介绍一下它们,看看如何在Flutter中实现它。

本机UI映射



正如我们先前所发现的,在跨平台框架中使用UI有两种相反的方法。这是在每个平台上使用WebView或本机UI元素调用的UI渲染。每种方法都有优点和缺点。但是它们并不能满足开发人员的全部需求:看上去与本机UI +本机性能没有区别。Flutter可以满足所有这些需求。Flutter团队花费了一定的资源在框架本身中创建“本机”元素。Flutter中的所有小部件都分为三大类:


如果转到cupertino部分,您将看到这些小部件与本机iOS元素没有区别。作为使用Flutter已有一段时间的开发人员,我可以确认它们之间没有区别。例如,如果您使用CupertinoDatePicker,则在滚动时,您会感觉到与iPhone上的Taptic / Haptic引擎完全相同的反馈,就好像它是本机应用程序的本机元素一样。我会说更多,我会定期在iPhone上打开realtor.com网站的应用程序,直到最近我还不知道它是用Flutter编写的(或非本地语言)。

Flutter不仅允许您在2个平台上使用“本机”窗口小部件,而且还可以创建自己的窗口小部件,这非常容易!整个范例是一切都是小部件起作用。您可以在短时间内创建极其复杂的UI元素和动画。魅力和做法与最近中描述了在扑UI工作的智慧,这个文章哈卜尔,我建议你阅读。因为所有这些工作都在一个单独的图形引擎上进行,该引擎直接为每个平台呈现所有这些内容(我们将在后面讨论),您可以确保所有内容都将按计划显示。

另一个令人惊奇的观点。Flutter支持从iOS 8和Android API v16开始的平台。从UI渲染的角度来看,Flutter并没有关系到特定平台上可用的API。他将有机会与Canvas合作,并与图形子系统进行某种形式的交互。这意味着我们可以从AndroidX绘制最新的UI元素,例如,在8岁的手机上。当然,在支持最早的平台上,这种方法的性能存在问题,但这是另一个问题。

本机UI性能



如您所见,Flutter的UI渲染方法更接近于Ionic等混合应用程序。我们有一个用于在所有平台上呈现UI的引擎,这就是Skia图形库。Google于2005年购买了Skia作为产品,并将其转变为一个开源项目。这至少表明这是一个相当成熟的产品。Skia的一些性能特点:

  • 图形元素和其他数据类型的写时复制
  • 尽可能使用堆栈存储器以减少碎片
  • 线程安全,更好的并行化

与类似的库(请参阅Cairo相比,我还没有发现令人信服的Skia性能测试,但是某些测试显示,除​​某些特定情况外,平均性能提高了50%。是的,这并不是特别重要,因为这些测试是基于在桌面上使用OpenGL的,并且...

Skia可以与许多GPU后端进行交互。自最近以来在iOS上,从版本11开始,Flutter默认使用Metal作为后端GPU。在Android上,从API 24-Vulkan开始。对于以下版本-OpenGL。所有这些使我们明显提高了生产率。据我了解,在其他“硬件”平台上,Skia / Flutter使用OpenGL,从原则上讲,这不会阻止我们编写具有足够图形性能的应用程序。

网络脱颖而出。目前,整个UI渲染仍位于Canvas / HTML包中。因此,斯基亚丝毫不参与此过程。另外,Dart VM不会直接与DOM交互。首先是到js的转换。所有这些都不会对生产率产生最好的影响,并且肉眼可以直接看到。然而,工作正在进行落实CanvasKit在Flutter中,这又将允许Skia通过WebGL在浏览器中使用。

最终,C#程序员一直在使用SkiaSharp相对较长的时间-覆盖Skia for Mono / .Net x。Xamarin社区使用此库来绘制自定义UI元素,这是一个非常受欢迎的库。如果这不是胜利,那么我不知道那是什么。

100%可以调用任何API操作系统


在Flutter中,与“外部”世界互动有2条原则:


平台通道允许您通过消息传递系统与本机运行时/ API进行交互。从体系结构的角度来看,这可以看作如下。在视觉上,Flutter只是Canvas,在本机应用程序的唯一Activity / UIViewController中可以拉伸到全屏。这与我使用游戏开发人员(游戏引擎)的方法完全相同。那些。您可以打开应用程序的iOS / Android项目,并将其他功能添加到Swift / Kotlin / etc等。问题在于,本机运行时和Dart VM之间将一无所知(除了本机运行时将知道应用程序具有Canvas并在其中显示内容的事实之外)。此外,例如,如果您打开Android项目的MainActivity.kt文件,您将看到类似以下内容:

class MainActivity: FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
  }
}

您是否注意到您的活动是从FlutterActivity继承的?这使我们有机会配置将消息直接发送到Flutter / DartVM的机制。为此,我们需要重写configureFlutterEngine方法它将确定被调用方法的名称以及用于发送异步消息的通道的名称。所有。这样就可以为我们编写任何本机代码并调用任何本机API!同时,已经有大量的插件(程序包)使您不必编写本机代码,只能使用Dart。这真是太好了!您可以分别为任何平台编写UI,一次可以使用DartVM与UI,I / O一起使用,并且仅作为计算组件使用,可以使用实现本机功能且覆盖所有功能的99%的插件。如果这还不够,您可以本地编写并通过消息机制进行通信。故事。

第二种机制是外来功能接口或FFI。这是iterope机制与其他语言(主要是C)的相当普遍的术语。在.Net世界中,此机制称为P / Invoke,对于JVM,它是JNI。简而言之,这是与以C / C ++ / etc编写的库进行交互的能力。例如,在.Net Framework时代,还没有用C#编写的软件,并且绝大多数软件是用C / C ++编写的,因此需要一种机制来使用这些库。命名为JVM的Python同样如此。 FFI是所有跨平台移动框架中使用的一种或另一种方法。最近,DartVM还开始支持FFI与C和JavaScript互操作!虽然此功能处于beta分支中,但已可供使用(风险和风险自负)。

如您所见,Flutter和DartVM涵盖了本机平台上100%的可能性,甚至更多。

易学且表达能力强的语言


老实说,我承认,达特对我来说仍然不是世界上最好的语言。没有严格的类型系统,没有功能包子,例如模式匹配或不变性功能(例如它们将很快交付)等。关于类型系统,Dart最初被认为是一种“没有典型的”语言,例如ala JS,但是对于AOT编译的常规支持,我仍然要对类型系统进行更为严格的规范,尽管这种规范并不完全。使用方法签名(即参数)仍然使我很烦。由于种种@required原因,所有这些括号令人发指。但是dart是一种非常易于学习的语言。在语法上,这对我来说是Java和JS之间的交叉。 Dart很宽容,例如JS。一般来说,这是一种相当容易学习的语言,我没有遇到任何重大问题。

非常适合前端开发范例的Rantime


现在让我们谈谈Dart VM。通常,Dart VM包括很多东西,从GC到Profiler和Observatory。在这里,我只想谈谈GC和条件运行时。您可以在此处熟悉运行时的工作原理及其组成我不是该领域的专家,但是对我自己来说,我注意到了Dart VM的一些优点,我将尝试描述这些优点。在此之前,我想指出Dart和相应的VM最初是作为JS的替代产品开发的,这暗示了对前端开发的关注。

隔离物

Dart VM具有隔离概念。隔离是直接在Dart代码上运行的一个主线程和隔离堆的组合,隔离堆实际上是从Dart代码分配对象的。这是简化的结构。 Isolate还具有辅助/系统线程,有些OS线程可以进入和退出Isolate等。堆栈也存在于隔离中,但是作为用户,您不能对其进行操作。这里需要重点强调的是,如果您查看一个隔离,那么这是一个单线程环境。默认情况下,Flutter使用一个默认的隔离。看起来不一样吗?是的,这是JS环境。就像在JS中一样,Dart程序员无法使用多线程。有人可能会认为这是对真实开发人员权利的混乱,简化和侵犯,但是我认为在使用UI时,当您使用条件DOM(并且无需在屏幕上绘制多边形)进行操作时,则不需要这样做,使用多个线程进行操作很危险。

当然,在这里,我确实很狡猾,如果您确实想要的话,则可以使用单独启动的Isolate执行并行任务(您好,WebWorkers)。在这里,您可以详细了解如何在Flutter中使用其他Isolate。通常,隔离,顾名思义,彼此之间什么都不知道,彼此之间不保持链接,也不通过消息系统进行通信。

在我看来,除了单线程方法以外,为每个隔离分配一个单独的堆而又不能操纵该线程的堆栈的事实是一个很好的方法。例如,如果您正在编写一个处理大量行的服务器应用程序,并且这些行存储在堆中,它们将以极快的速度出现和消失,同时会分散内存并添加GC作业,可以通过任何方式传输这些行,或者至少从其中转移一部分堆栈上的堆将节省资源并提高性能。例子一般,但是您理解我。但是,当使用UI时,可能有足够数量的UI元素(例如动画)寿命很短,但是只有一个客户端,与服务器应用程序相比,处理的数据量可以忽略不计,直接使用堆栈的能力根本没有必要。我不是在说装箱/拆箱,在这种情况下可能是毫无意义的。应当注意,Dart VM中的对象分配非常频繁。即使从Dart方法输出双倍金额,VM也会在堆上单独分配一块。 GC如何处理此负载?让我们来看看。

幼小空间清除器(和并行标记扫描)

首先,与所有GC一样,Dart VM中的GC世代相传。此外,Dart VM中的GC可以根据工作原理分为两个组件:Young Space Scavenger和Parallel Mark Sweep。我不会讲最后一个原则,这是一个相当流行的内存清理原则,几乎在所有地方都实现了这一原则,并没有赋予Flutter特别的优势。我们对第一个感兴趣。下图很好地说明了Young Space Scavenger的工作原理:


它清楚地证明了这种方法的优势。 Young Space Scavenger适用于内存中最新的对象,可以说是针对第一代/零代对象。通常,这是Flutter / Dart VM的特征,大多数新对象的寿命很短。在您分配许多不能长期使用的对象的情况下,内存可能会非常分散。在这种情况下,您将不得不花费内存或处理器时间来解决问题(尽管您不应该使用这种方法来解决问题)。 Young Space Scavenger解决了这个问题。如果您看上面的图片,那么实际上没有6步,您不需要清除第一个内存块,默认情况下,我们认为在将对象复制到第二个内存块后,该内存块为空。好吧,当将尚存的对象复制到第二个块中时,我们自然会一一对应地设置它们,而不会产生碎片。所有这些使VM可以以相当低的价格分配许多新对象。

空闲时间GC

如您所知,Flutter和Dart VM团队密切合作,可以将这种合作的结果视为空闲时间GC。顾名思义,这是什么也没有发生的时候的垃圾回收。在Flutter的上下文中,当应用程序在视觉上没有任何改变时。没有动画,滚动或用户交互。此时,Flutter将消息发送到Dart VM,从原则上讲,现在是开始垃圾收集的好时机。接下来,垃圾收集器决定他是否应该开始工作。当然,这方面的垃圾回收发生在通过并行标记扫描管理的较旧对象上,这本身是一个相当昂贵的过程,而空闲时间GC在这方面是一个非常有用的机制。

还有其他类似的东西滑动压实压缩指针首先是运行并行标记扫描后的内存碎片整理机制。这也是一个昂贵的过程,并且只有在有空闲时间时才起作用。第二种方法是压缩指针,将64位指针压缩为32位,从而节省了内存(我认为这在服务器环境中比在移动环境中有用得多)。

摘要


如果您读了这句话,那么首先,恭喜您,其次,我不得不说我没有写文章的经验,因此我不太理解我是否能够阐明我的观点。而且想法很简单,当您使用Flutter编写移动应用程序时,结果就是本机的。并以奖金的形式获得了非常不错的应用程序开发速度。热重装/重启只是Frontend开发中必不可少的事情。您能想象某个排字员需要为每个浏览器构建/编译整个项目吗,例如,每个按钮的颜色都有变化?当然不是。通常,“热重装/重新启动”值得单独撰写。但是我分心了。

我在Flutter的经验告诉我,该框架将在不久的将来成为主流。我会定期接受Flutter开发人员职位的面试,在一半的情况下,正在寻找Flutter开发人员的公司实际上都有一批移动原生开发人员。他们只是在室内/附属项目上尝试了Flutter,感到满意/高兴,并逐渐转向Flutter。在我看来,这是一次真正的胜利。不幸的是,关于Xamarin不能说什么。通常,选择Xamarin的决定仅是由于堆栈的其余部分都是用.Net编写的事实,这是一个很滑的斜率。

总而言之,我想说的是,如果您在考虑开发新的移动应用程序时应该考虑哪方面,请查看Flutter。

All Articles