1.如何用 Flutter 实现混合开发?闲鱼公开源代码实例
2.什么是源码跨终端
3.如何使用Fluttify开发一个新的Flutter插件
4.Flutter探索(1)快速预览
5.flutter åç¼è¯
如何用 Flutter 实现混合开发?闲鱼公开源代码实例
阿里妹导读:具有一定规模的 App 通常有一套成熟通用的基础库,尤其是源码阿里系 App,一般需要依赖很多体系内的源码基础库。那么使用 Flutter 重新从头开发 App 的源码成本和风险都较高。所以在 Native App 进行渐进式迁移是源码 Flutter 技术在现有 Native App 进行应用的稳健型方式。
今天我们来看看,源码开源码要钱吗闲鱼团队如何在这个实践过程中沉淀出一套独具特色的源码混合技术方案。
现状及思考
闲鱼目前采用的源码混合方案是共享同一个引擎的方案。这个方案基于这样一个事实:任何时候我们最多只能看到一个页面,源码当然有些特定的源码场景你可以看到多个 ViewController ,但是源码这些特殊场景我们这里不讨论。
我们可以这样简单去理解这个方案:我们把共享的源码 Flutter View 当成一个画布,然后用一个 Native 的源码容器作为逻辑的页面。每次在打开一个容器的源码时候我们通过通信机制通知 Flutter View 绘制成当前的逻辑页面,然后将 Flutter View 放到当前容器里面。源码
这个方案无法支持同时存在多个平级逻辑页面的情况,因为你在页面切换的时候必须从栈顶去操作,无法再保持状态的同时进行平级切换。举个例子:有两个页面A,B,当前B在栈顶。切换到A需要把B从栈顶 Pop 出去,此时B的状态丢失,如果想切回B,我们只能重新打开B之前页面的状态无法维持住。
如在 pop 的过程当中,可能会把 Flutter 官方的 Dialog 进行误杀。而且基于栈的操作我们依赖对 Flutter 框架的一个属性修改,这让这个方案具有了侵入性的特点。
新一代混合技术方案 FlutterBoost
重构计划
在闲鱼推进 Flutter 化过程当中,更加复杂的页面场景逐渐暴露了老方案的局限性和一些问题。所以我们启动了代号 FlutterBoost(向C++ Boost库致敬)的新混合技术方案。这次新的混合方案我们的主要目标有:
跟老方案类似,新的方案还是采用共享引擎的模式实现。主要思路是由 Native 容器 Container 通过消息驱动 Flutter 页面容器 Container,从而达到 Native Container与 Flutter Container 的同步目的。我们希望做到 Flutter 渲染的内容是由 Naitve 容器去驱动的。
简单的理解,我们想做到把 Flutter 容器做成浏览器的感觉。填写一个页面地址,然后由容器去管理页面的绘制。在 Native 侧我们只需要关心如果初始化容器,然后设置容器对应的页面标志即可。
主要概念
Native 层概念
Dart 层概念
关于页面的理解
在 Native 和 Flutter 表示页面的对象和概念是不一致的。在 Native,我们对于页面的概念一般是 ViewController,Activity。而对于 Flutter 我们对于页面的概念是 Widget。我们希望可统一页面的概念,或者说弱化抽象掉 Flutter 本身的 Widget 对应的页面概念。换句话说,当一个 Native 的页面容器存在的时候, FlutteBoost 保证一定会有一个 Widget 作为容器的内容。所以我们在理解和进行路由操作的时候都应该以 Native 的容器为准, Flutter Widget 依赖于 Native 页面容器的状态。
那么在 FlutterBoost 的概念里说到页面的时候,我们指的是 Native 容器和它所附属的 Widget。所有页面路由操作,打开或者关闭页面,实际上都是订阅本站源码对 Native 页面容器的直接操作。无论路由请求来自何方,最终都会转发给 Native 去实现路由操作。这也是接入 FlutterBoost 的时候需要实现 Platform 协议的原因。
另一方面,我们无法控制业务代码通过 Flutter 本身的 Navigator 去 push 新的 Widget。对于业务不通过 FlutterBoost 而直接使用 Navigator 操作 Widget 的情况,包括 Dialog 这种非全屏 Widget,我们建议是业务自己负责管理其状态。这种类型 Widget 不属于 FlutterBoost 所定义的页面概念。
理解这里的页面概念,对于理解和使用 FlutterBoost 至关重要。
与老方案主要差别
前面我们提到老方案在 Dart 层维护单个 Navigator 栈结构用于 Widget 的切换。而新的方案则是在 Dart 侧引入了 Container 的概念,不再用栈的结构去维护现有的页面,而是通过扁平化 key-value 映射的形式去维护当前所有的页面,每个页面拥有一个唯一的 id。这种结构很自然的支持了页面的查找和切换,不再受制于栈顶操作的问题,之前的一些由于 pop 导致的问题迎刃而解。也不需要依赖修改 Flutter 源码的形式去进行页面栈操作,去掉了实现的侵入性。
实际上我们引入的 Container 就是 Navigator 的,也就是说一个 Native 的容器对应了一个 Navigator。那这是如何做到的呢?
多 Navigator 的实现
Flutter 在底层提供了让你自定义 Navigator 的接口,我们自己实现了一个管理多个 Navigator 的对象。当前最多只会有一个可见的 Flutter Navigator,这个 Navigator 所包含的页面也就是我们当前可见容器所对应的页面。
Native 容器与 Flutter 容器(Navigator)是一一对应的,生命周期也是同步的。当一个 Native 容器被创建的时候,Flutter 的一个容器也被创建,它们通过相同的 id 关联起来。当 Native 的容器被销毁的时候,Flutter 的容器也被销毁。Flutter 容器的状态是跟随 Native 容器,这也就是我们说的 Native 驱动。由 Manager 统一管理切换当前在屏幕上展示的容器。
我们用一个简单的例子描述一个新页面创建的过程:
这就是一个新页面创建的主要逻辑,销毁和进入后台等操作也类似有 Native 容器事件去进行驱动。
官方提出的混合方案
基本原理
Flutter 技术链主要由 C++ 实现的 Flutter Engine 和 Dart 实现的 Framework 组成(其配套的编译和构建工具我们这里不参与讨论)。Flutter Engine 负责线程管理,Dart VM 状态管理和 Dart 代码加载等工作。而 Dart 代码所实现的 Framework 则是业务接触到的主要 API,诸如 Widget 等概念就是在 Dart 层面 Framework 内容。
一个进程里面最多只会初始化一个 Dart VM。然而一个进程可以有多个 Flutter Engine,多个 Engine 实例共享同一个 Dart VM。
我们来看具体实现,在 iOS 上面每初始化一个 FlutterViewController 就会有一个引擎随之初始化,也就意味着会有新的线程(理论上线程可以复用)去跑 Dart 代码。Android 类似的 Activity 也会有类似的效果。如果你启动多个引擎实例,注意此时Dart VM 依然是共享的,只是不同 Engine 实例加载的代码跑在各自独立的 Isolate。
官方建议
引擎深度共享
在混合方案方面,我们跟 Google 讨论了可能的一些方案。Flutter 官方给出的建议是从长期来看,我们应该支持在同一个引擎支持多窗口绘制的能力,至少在逻辑上做到 FlutterViewController 是uvc驱动源码共享同一个引擎的资源的。换句话说,我们希望所有绘制窗口共享同一个主 Isolate。
但官方给出的长期建议目前来说没有很好的支持。
多引擎模式
我们在混合方案中解决的主要问题是如何去处理交替出现的 Flutter 和 Native 页面。Google 工程师给出了一个 Keep It Simple 的方案:对于连续的 Flutter 页面(Widget)只需要在当前 FlutterViewController 打开即可,对于间隔的 Flutter 页面我们初始化新的引擎。
例如,我们进行下面一组导航操作:
我们只需要在 Flutter Page1 和 Flutter Page3 创建不同的 Flutter 实例即可。
这个方案的好处就是简单易懂,逻辑清晰,但是也有潜在的问题。如果一个 Native 页面一个 Flutter 页面一直交替进行的话,Flutter Engine 的数量会线性增加,而 Flutter Engine 本身是一个比较重的对象。
多引擎模式的问题
因此,综合多方面考虑,我们没有采用多引擎混合方案。
总结
目前 FlutterBoost 已经在生产环境支撑着在闲鱼客户端中所有的基于 Flutter 开发业务,为更加负复杂的混合场景提供了支持,稳定为亿级用户提供服务。
我们在项目启动之初就希望 FlutterBoost 能够解决 Native App 混合模式接入 Flutter 这个通用问题。所以我们把它做成了一个可复用的 Flutter 插件,希望吸引更多感兴趣的朋友参与到 Flutter 社区的建设。在有限篇幅中,我们分享了闲鱼在 Flutter 混合技术方案中积累的经验和代码。欢迎兴趣的同学能够积极与我们一起交流学习。
扩展补充
在两个 Flutter 页面进行切换的时候,因为我们只有一个 Flutter View 所以需要对上一个页面进行截图保存,如果 Flutter 页面多截图会占用大量内存。这里我们采用文件内存二级缓存策略,在内存中最多只保存 2-3 个截图,其余的写入文件按需加载。这样我们可以在保证用户体验的同时在内存方面也保持一个较为稳定的水平。
页面渲染性能方面,Flutter 的 AOT 优势展露无遗。在页面快速切换的时候,Flutter 能够很灵敏的响应页面的切换,在逻辑上创造出一种 Flutter 多个页面的感觉。
项目开始的时候我们基于闲鱼目前使用的 Flutter 版本进行开发,而后进行了 Release 1.0 兼容升级测试目前没有发现问题。
只要是集成了 Flutter 的项目都可以用官方依赖的方式非常方便的以插件形式引入 FlutterBoost,只需要对工程进行少量代码接入即可完成接入。详细接入文档,请参阅 GitHub 主页官方项目文档。
什么是跨终端
链接:/post/
鉴于很多人对跨端技术感觉很神秘,虽然我实际上还没有写过一个从0到1的跨端框架,但是我曾经用Yoga(布局引擎Yoga(React-Native)做过一些简单的跨端的事情,后来用了Weex。研究跨端有一段时间了,想科普一下。
科普之前,首先你要知道,为什么需要跨端技术?我们通常会把Weex和React-Native(本文统称为RN)说成是跨端技术吗(Flutter没有单独提到)?
其实不是,好像Android/iOS本来是两个人的,但最终变成了一个人。我的人力减少了一半!
但前提是这个人力需要懂Android,iOS,JavaScript,vb缩图源码更懂,不然出了问题,怎么修?
所以在中国的互联网环境下,很难招到这样的人。大家都在研究PPT架构技术,职场生存理论,岁如何解脱财富。我们如何有时间扩展我们的技术堆栈?
端上开发很惨,总有崩溃(使用崩溃,闪退)而且没有办法远程修复。只能等下一个版本给使用市场推一个修复bug的新版本。
但如果推送新版本,用户可能不会升级。因此,许多公司研究了各种热修复框架,尤其是在Android平台上。有很多热修复框架,主要是由DexClassLoader来完成。
但是,最早的时候,WebView有一个很大的问题,尤其是Android。而且加载网页肯定要花时间,过程中屏幕会一片空白等等。所以很多人围绕这些做了很多优化。我个人觉得最有用的其实是线下套餐。同时,每一代WebView也在更新升级。然后一些有实力的公司开发了自己的所谓浏览器内核,各种黑科技,如何提速,支持各种特性等等。但是好像没有开源:dog:
不算。这只是跨安卓和iOS,不把我的PC当目的?
其实浏览器是跨端的,每个平台都可以用Chrome(其他浏览器主要是想做不做)!但是它也有自己的问题,因为各家都有自己的浏览器,内核不同,划分越来越大。chrome(Blink)/Safari(WebKit)/Firefox(Gecko?)等等,尤其是对css的支持。
Developer.mozilla.org/zh-CN/docs/.这个网站可以检查一些浏览器的兼容性。例如,边框宽度的兼容性如下:
其实也不是不可以,但是这样做相当于直接为OpenGL或者其他图形引擎编程,而且要自下而上的搭建一套渲染机制,打包各种基础UI组件给开发者使用,或者留下很多漏洞让开发者自定义自己的UI,非常复杂。但其实Flutter就是这么做的,所以Flutter2.0又开始向桌面端发展了,而且不局限于Android/iOS,但不知道能走多远。还有的是搞React-Native-Skia的,所以用js代码直接调Skia(2D图形渲染引擎)?(具体没看过)
你写的JavaScript代码为什么能运行?这取决于JavaScript引擎。
扔给它一段js代码(实际上是一个文本字符串),它就能帮你计算结果,处理逻辑。多级导航源码
常见的Weex、RN、Hippy也依赖于此(MLN使用Lua)进行逻辑处理。
这个时候会有很多概念。
有些人喜欢把JavaScript引擎称为JavaScriptCore(不知道为什么,可能是因为iOS开发者才是研究这些比较深入的人,因为苹果的JavaScript引擎叫JavaScriptCore。苹果的这个JavaScriptCore呢?很多人喜欢称之为JSCore或者JSC)。所以,后来看到这些名词,我总是把它们带入语境中去感受他想说的是JavaScript引擎还是苹果的JavaScript引擎 JavaScript Core (JSCore/JSC)。
先说JavaScript引擎。
是的,有这么多!当然还有JavaScriptCore(不在图中)。
最后一行是跑分,越多越好。有JIT的V8在3w挂所有东西。其中QuikJS极小,得分很高。估计很多人会用QuikJS做跨端JavaScript引擎吧?赫尔墨斯是由脸书创造的。看来Android目前在RN中使用的JavaScript引擎已经取代了之前使用的JavaScriptCore。RN为什么一直不用V8?这个我也不知道.
但是很多人都在搞Android的V8项目,Github上也有一些开源项目。其次,iOS不支持JIT,有自己的JavaScriptCore,没有JIT改V8似乎意义不大。
一个正常的跨端框架最简单的情况如下(后面会讨论问题,逐步丰富):
用一个
简单的例子看
假设我的 js 文件中就是要 展示一个红色的 div 方块 。那么首先,端会把这个文本传给 JavaScript Runtime,它解析完后形成一个约定的格式,比如如下的 JSON 格式(里面的值用来描述是一个*红色方块,我随便定义的)
{ "name":"div", "width":"", "height":"", "background":"red"}
通过 JavaScript Runtime 和 端(Android/iOS) 通信,把这个消息传回去。
端拿到了消息,发现要创建一个 * 的叫做 div 的东西,没有 div 啊!这就需要端上提前埋好代码,比如 Android 里有 FrameLayout,那么就有类似的注册代码
// 伪代码register("div", FrameLayout.class);
然后端就知道了,oh!我需要创建一个长宽的正方形。
首先,这是框架设计提前思考好的,究竟要支持哪些基础组件,比如 image 、text 等等。而且一般这里都会开个口子,让开发者可以自己扩展组件,比如你需要一个横滑列表,没提供怎么办?看看 div 怎么注册的,按照它的过程注册一个列表就好了。这也可以 PPT 吹成: 扩展跨端框架 ,其实 门槛比自定义 View 还要低 。
前面说了 JavaScript Engine,这里咋又来了个 Runtime?
JavaScript Engine 能做什么?
什么都做不了,只能解析执行 js 代码
那么问题来了,我怎么去 描述 我写的 js 代码代表的 视图 呢?其实不用描述,js 代码只要在 内存中 维护好一个树形结构就好了,就是一个 Object,因为实体在具体的端上,怎么理解呢?
左边只要在内存中维护好这样一个树形结构就好了,传递给客户端时,转为
{ "name":"div", "children":[ { "name":"image" }, { "name":"div", "children":[] } // 等等 ]}
端上拿到消息,创建视图为右图中的结构即可。
如何维护好这个模型呢?调用什么 js 的方法发送消息呢?怎么给这些个 div 加上 css 来描述它的大小形状呢?等等更复杂的一系列的前端问题,都需要 写代码 来实现。
所以一般都会有个 core.js 或者 framework.js 类似的一堆 js 代码,就是用来处理这些事情,而这些代码同样依靠 JavaScript Engine 来执行。
从而所谓的 JavaScript Runtime ,我觉得可以单纯的理解为 JavaScript Engine 自身的代码跑起来后的环境,也可以理解为 core.js 等被跨端框架所需要的、包含了各种逻辑的前端代码被加载运行后的环境。
当你用这些跨端框架的时候,你会发现他们只支持 css 子集 ,而且布局方式基本都是 flexbox(一种布局模型) 。
那么比如你写了一个横着容纳了三个小方块的大方块,你的前端 css 代码肯定要写成, flex-diretion:row ,那么抛给端上的消息可能如下:
{ "name":"div", "attribute":{ // 使用布局 "flex-diretion":"row" }, "children":[ { "name":"div" }, { "name":"div" }, { "name":"div" } ]}
端上拿到这个消息,都不知道 flex-direction 是什么。当然,你可以自己写一个解析库来解,但是 Yoga 帮你做了这件事!
所以 RN 使用的是 Yoga 布局引擎(支持 flexbox,也是 Facebook 搞的)。
Weex 似乎一开始是用的 Yoga,后来自己写了一套?
这个地方就出现了一个名词 Layout Engine ,它就是帮我们处理各种布局参数的,然后帮我们算好每个视图的坐标,然后端上拿到坐标后设置对应的视图的坐标,一个井井有条的视图便展示了出来。如果你觉得你写的布局解析算法超越了 Yoga 等等,那么你完全可以自己写一套。
比如从 JavaScript Runtime 处理完各种属性了,要渲染视图了!传了一段 JSON 给端。
端上手指点了一下这个视图,那也要封装成一个消息传递给 JavaScript Runtime,然后触发你之前写的 js 的监听代码,比如点击后弹一个弹窗,那就又要封装一个调用弹窗方法的消息给端。
就是这样来来回回。
所以两边都有自己的消息队列。
而且当你做动画还想监听动画过程的时候,肯定在短时间内发送了大量消息,这些过程肯定是 需要优化 的。
并且!据我个人用 Weex 的经验,有的 flexbox 属性两端都不统一(可能是 Weex 的 Bug,毕竟 KPI 项目,都不维护了)
我记得当时还开玩笑说,用了 Weex 终于领悟了跨端的真谛:
if(platform === 'Andoird') { // 差异化逻辑} else if(platform === 'iOS') { // 差异化逻辑}
跨端的代价就是,你 本以为 真的可以一套代码两端跑,后来发现真的有点做梦了(连 H5 有时候 Andoird/iOS 都不一致,因为用的内核都不是一个),代码里有不少的 if-else。
所以经过上面的一系列科普,一个跨端框架成了这样:
这其中一般是需要一个客户端、一个前端、一个懂 JavaScript Engine 会 C/C 的来分别开发。
我虽然没开发过,但是感觉会有很多问题。
比如 JavaScript Runtime 在另一个进程的话,跨进程通信?
比如消息通信过于频繁是不是就会有各种连锁反应,掉帧啊、事件响应不及时、动画不流畅啊,怎么优化?
其实我本身一直自诩喜欢研究原理,但是直至今日我也没真的一行行看过跨端框架的源码,我知道的这些也未必是对的,只是之前做过 Weex 的一些工作稍微研究了一下,还是挺惭愧的。
既然你自称喜欢研究原理,为什么不看呢?
链接:/post/
相关问答:相关问答:手机端和电脑端各是什么?
电脑端和手机端,实际上说的就是平台问题。
当我们使用电脑的时候,电脑基本使用的操作平台是windows,或者苹果等常用操作系统。
而手机上用的平台,如安卓,苹果的IOS,当年诺基亚的塞班,黑莓的系统,都叫做手机端。
那么怎么定义手机端和电脑端呢,我们可以这么理解,如果用电脑操作系统的设备,即便是平板电脑,你也可以理解成是电脑端。
如微软平板电脑surface,他的定位是平板也是电脑,
我们很多的平板,多数使用的是安卓系统,苹果的当然就是IOS,但是平板使用基本使用的移动平台,也就可以看成是手机端。
但是,如果这个移动设备的平台使用的是电脑的操作系统的时候,他所使用的平台,也就成了电脑端。
如何使用Fluttify开发一个新的Flutter插件
注:目前Fluttify本身并不对外开放,但是内测阶段可以免费为你生成插件,只要提供android端的jar/aar和ios端的framework/.h+.a,或者maven坐标和cocoapods名称即可
系列文章:
使用Fluttify生成插件
从原生SDK生成Fluttify产物,本质上也是一个构建的过程,所以Fluttify采用了Gradle插件的形式。和android构建类似,构建Fluttify也需要提供一个build.gradle,输入必须的参数后运行gradle fluttify,便能够生成出一个原生SDK对应的Flutter插件。 这篇文章会以 极光统计SDK作为示例。
快速开始
以JAnalytics SDK的初始化方法为例:
可以看到在Fluttify的加持下,编写插件变成了一件非常轻松的事情,想要写方法的时候就是查官方文档,然后写对应的dart代码,完全屏蔽了原生接口。你不用再一次次的打开android studio和xcode,一次次地在原生和dart之间跳来跳去,再也不用为了在dart和原生之间传对象写那一大坨的字段,跟原生有关的,Fluttify都(尽力)为你提供好dart接口,这就是Fluttify的目标。
接下来大致介绍下生成的插件的结构以及怎么调用生成的接口。
Fluttify产物工程结构
Fluttify的产物是一个标准的Flutter的插件工程,所以lib文件夹之上的结构都和普通插件一样。lib文件夹下会分成android和ios文件夹,分别放置各平台SDK中的类(枚举/接口等)对应的Dart类(枚举/接口等)。android/ios文件夹下还会各自生成:
习惯上会在lib文件夹下再加一个dart文件夹,放置对各平台进行抽象的代码,并且最后对外export的时候,只export这个文件夹下的文件。
lib文件夹结构概览:
基本操作创建对象
Fluttify中创建对象主要是使用create方法。这个方法在dart端以静态的异步方法XX.create__xx的形式提供,其中xx是构造器的参数。形如:
如果构造器没有参数,那么就直接XX.create__,后面跟两个下划线是为了不与SDK中的create方法冲突。
调用方法
Fluttify中的所有方法都是异步的,所以你想要看起来像同步代码的话,就要对每个调用都加await。其他的就没什么好说的,因为Fluttify的目的就是为了让原生调用起来跟dart调用起来一模一样,原生怎么调用,dart这边就怎么调用。
支持哪些操作
Fluttify会生成SDK中的公开类,公开方法,以及常量。
基础设施
所有Fluttify生成的工程都会依赖一个基础设施插件,也就是foundation_fluttify。
这个插件提供了系统类的dart接口,并且是手写的。曾经我也尝试过直接对android.jar和ios的系统framework进行生成,虽然理论上能够实现,但是效果并不好,生成出来的文件非常多,光这一个插件编译进app的话就要增大好几Mb,所以放弃了,还是碰到需要的再去手动编写一下就ok了。
另外foundation_fluttify还提供了一些便利方法,比如platform方法简化多平台的调用,kNativeObjectPool存放原生对象的dart引用,等等。
原生对象的生命周期
Fluttiy生成的dart代码在调用过程中产生的原生对象,都会被放入一个叫HEAP的全局键值对类型中,android端为HashMap,ios端为NSDictionary。由于存放在全局变量中,如果不手动释放的话,那么这些对象会一直被强引用,无法释放。foundation_fluttify中的Ref类(dart)有一个release方法,调用它会把对应的原生对象充HEAP中删除,从而解除强引用。
结语
以上就是使用Fluttify开发一个插件的介绍。 janalytics_fluttify已经上传到 fluttify-project组织下,如果有老铁对这个插件感兴趣的话可以联系我(@qq.com),我可以把你设置为维护者,当然有空闲时间时,我也会进一步开发这个插件。
如果有想要生成插件的老铁也可以联系我,目前Fluttify还处于内测阶段,不会收取任何费用,有任何反馈都可以往 fluttify-feedback提issue,欢迎各位的反馈。
Flutter探索(1)快速预览
Flutter是Google推出的一款用于在iOS和Android平台开发高质量原生UI的移动SDK,只需维护一套代码即可在iOS和Android构建出性能不亚于原生应用的漂亮应用。本文将快速概览Flutter的核心概念和框架。
Flutter包括一个现代的响应式框架、一个2D渲染引擎、现成的Widget组件和开发工具。这些组件能帮助开发者快速设计、构建、测试和调试应用。
Flutter中的Widget构建UI,借鉴了React的灵感,采用Widget描述布局和交互方式。当Widget状态发生变化时,Widget会重绘UI,Flutter通过比较前后状态的差异,确定渲染树所需最小更改,类似于React的虚拟DOM diff算法。
下面用简单的"Hello,world"代码对比Flutter与React。
React的代码看起来如下:
Flutter与React非常相似,只是将React的JSX转换为纯对象形式,将Component变为StatelessWidget。个人认为JSX的描述方式更直观,主要因素是习惯问题,使用对象形式嵌套时,末尾的“)”使代码段辨识度降低,但现在IDE可以自动注释解决。所以,习惯因素是我认为JSX更易阅读的原因。
StatelessWidget表示无状态组件,StatefulWidget则可以通过setState()方法改变状态。熟悉React的同学对此应当非常熟悉,这部分内容将在后续文章中详细讨论。
Flutter使用Dart语言,这与TypeScript相似,都是替代JavaScript的语言。Dart的目标是完全替代JavaScript,重新构建一门语言。尽管对于熟悉JavaScript或Java的开发者而言易于上手,但网上已提供了《为JavaScript开发人员准备的Dart参考教程》以快速上手。
Flutter整体架构分为三层:Framework、Engine与Embedder。
Flutter与React Native的竞争关系明显,两者都是优秀的跨平台移动应用框架。通过比较近5年的Google搜索热度,可以看出React Native在年开始稳步上升,而Flutter在年2月才开始有上升趋势。尽管Flutter热度较低,但与React Native非常接近。搜索热度图侧面反映了React Native成熟度较高,而Flutter在年开始变得热门。
Flutter通过Dart直接AOT编译为本地代码,控制Skia2d渲染引擎,理论上性能优于通过JavaScript在Chromium解释运行的React Native。Flutter调用方式与Android原生调用一致,直接由framework层调用到Skia,而ReactNative需通过中间层调度。
Flutter在跨端一致性和保留原生能力之间取得平衡。Skia底层画布控制使Flutter可以轻松生成动画图形、视频、文本和控件,实现跨端一致性。而React Native使用JavaScript控制原生控件,需要根据不同端差异性进行妥协,保留了原生能力。跨端一致性和保留原生能力在某种程度上存在矛盾,取决于个人的权衡。
React Native已相对成熟,解决了许多问题,如拍照、地图、视频、通知等。而Flutter仍在填坑过程中,虽然功能齐全,但不可避免地存在一些问题。因此,除非寻求新产品的尝试,不惧怕解决坑洞,否则推荐使用已成熟输出产品的React Native。
Flutter选择Dart语言,生态相比React Native较小。React Native发展多年,背靠JavaScript、React、原生Native,这些是Flutter难以追赶的。
无论是Flutter还是React Native都需要Native开发知识。虽然Dart容易上手,但理解语言和规避坑洞需要经验填充,因此上手难度比React Native略高。
总的来说,我更看重Flutter的底层渲染引擎,它抹平了平台差异,实现了正的跨平台一致性。如果Widget和Dart换成React和TypeScript,我将更加支持Flutter,因为这能显著减少程序员的学习成本。尽管如此,Flutter作为一个跨平台框架的设计理念是可靠的。
flutter åç¼è¯
>You are building a fat APK that includes binaries for android-arm, android-arm.
>If you are deploying the app to the Play Store, it's recommended to use app bundles or split the APK to reduce the APK size.
> To generate an app bundle, run:
> flutter build appbundle --target-platform android-arm,android-arm
> Learn more on: \connectivity-0.4.4\android\src\main\java\io\flutter\plugins\connectivity\ConnectivityPlugin.javaʹ�û����ѹ�ʱ�� API��
>ע: �й���ϸ��Ϣ, ��ʹ�� -Xlint:deprecation ���±��롣
>ע: E:\work_tool\flutter\.pub-cache\hosted\pub.flutter-io.cn\url_launcher-5.1.3\android\src\main\java\io\flutter\plugins\urllauncher\WebViewActivity.javaʹ
>�û����ѹ�ʱ�� API��
>ע: �й���ϸ��Ϣ, ��ʹ�� -Xlint:deprecation ���±��롣
>Calling mockable JAR artifact transform to create file: C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\android.jar\bafedfedcdfbe2\android.jar with input E:\work_tool\sdk\android-sdk-windows\platforms\android-\android.jar
>Calling mockable JAR artifact transform to create file: C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\android.jar\abbb3fbccbacdbca9\android.jar with input E:\work_tool\sdk\android-sdk-windows\platforms\android-\android.jar
>Request to incrementing alive workforce from 0. Current workforce (dead or alive) 0
>thread-pool size=4
>ProGuard, version 6.0.3
>Reading input...
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\flutter.jar\abcf4d2aaafaea6\jetified-flutter.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\app\intermediates\flutter\release\libs.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\localbroadcastmanager-1.0.0.aar\7afeadaa8eabac\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\work-runtime-2.1.0.aar\8e3a1e8a8ffadf6cceeb4f5\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\fragment-1.1.0.aar\4fa7d1becbc8af9b\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\room-runtime-2.1.0.aar\1bfafdefbeac\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\room-runtime-2.1.0.aar\1bfafdefbeac\jars\libs\room-common-java8-2.1.0.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\viewpager-1.0.0.aar\e7f6fbccdda5\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\loader-1.0.0.aar\ffad0ad2cbfc\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\activity-1.0.0.aar\adbfae1bfcaebf\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\customview-1.0.0.aar\fe8bf4aedb\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\core-1.1.0.aar\febbdd5a2c7be8ed\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\versionedparcelable-1.1.0.aar\c6fca4e4cf3ac8d7cb5ced\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.collection\collection\1.1.0\1fbde0da5de0e\collection-1.1.0.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\lifecycle-livedata-2.0.0.aar\deddb1dd0bfde1f\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\lifecycle-livedata-core-2.0.0.aar\edbeb6faaafd5acb4d9\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\core-runtime-2.0.1.aar\bb0cc2ceeaf0aabb1cc5c\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.room\room-common\2.1.0\bbdea0daca9dfa0\room-common-2.1.0.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\sqlite-framework-2.0.1.aar\9cbdfef7eade9\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\sqlite-2.0.1.aar\aafbdfb2da1a\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\lifecycle-service-2.0.0.aar\9debd4dd7b2cda\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\lifecycle-runtime-2.1.0.aar\cacaebcca4a9ba5\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\lifecycle-viewmodel-2.1.0.aar\e1ffadbbfa\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\transforms-1\files-1.1\savedstate-1.0.0.aar\8a2cafce5d9dbff\jars\classes.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.lifecycle\lifecycle-common\2.1.0\ced9cd6cb9db2ec4eddb7\lifecycle-common-2.1.0.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.arch.core\core-common\2.1.0\bfccbdecddcb6fe\core-common-2.1.0.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.annotation\annotation\1.1.0\e3a6fb2fe3ae6bba4ceea4c8\annotation-1.1.0.jar] (filtered)
>Reading program jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\com.google.guava\listenablefuture\1.0\caa6acbcdebbfb3cad\listenablefuture-1.0.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\flutter_downloader\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\shared_preferences\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\url_launcher\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\sqflite\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\connectivity\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\path_provider\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\package_info\intermediates\intermediate-jars\release\classes.jar] (filtered)
>Reading program directory [F:\dartSpace\flutter\jiddspace\nfc\build\app\intermediates\javac\release\compileReleaseJavaWithJavac\classes] (filtered)
>Reading program jar [F:\dartSpace\flutter\jiddspace\nfc\build\app\intermediates\transforms\mergeJavaRes\release\0.jar] (filtered)
>Reading library jar [C:\Users\Administrator\.gradle\caches\modules-2\files-2.1\androidx.annotation\annotation\1.0.0\f2cdacafa2a5c0cdd7cead\annotation-1.0.0.jar] (filtered)
>Reading library jar [E:\work_tool\sdk\android-sdk-windows\platforms\android-\android.jar]
>Reading library jar [E:\work_tool\sdk\android-sdk-windows\build-tools\.0.3\core-lambda-stubs.jar]
>Reading library jar [E:\work_tool\sdk\android-sdk-windows\platforms\android-\optional\org.apache.patibilities in this Flutter app.
>See https://goo.gl/CPwY for more information on the problem and how to fix it.
>