前言
==
这几年在大前端的开发领域,选择跨端方案的公司和部门越来越多,一方面是跨平台的前端框架越来越成熟,另一方面也是因原生开发者正逐年减少。所以,在当下掌握一门跨平台的技术栈还是很有必要的,无论从广度还是从深度都会有所帮助。
那我们应该选择哪种技术方案呢?如果这个问题放在几年前,答案可能会有很多。不过现在看来,市面上仅剩两种主流方案,就是经常听到的 React Native 和 Flutter。一个出自 Facebook,一个出自 Google。
这两个方案的优劣已有很多点评,基本上形成了两种阵营。但在我看来,它们其实没有明显的差距。如果有,早就被市场所淘汰了。现在看来所谓的劣势,很快就会被那帮天才工程师们,想出解决方案而弥补上了。这也许是竟争帮助了整个生态的完善。反而是 Apple 一直没有跟上,可能还是源于闭源生态,没有另外两家那么的急于变革。
反观 Google 的野心其实是很大的,想通过跨平台方案(无论是 Flutter 还是 Kotlin),从社区和开发者入手一统语言,甚至操作系统(Fuchsia),从而扩展更大的版图。Facebook 则想利用自己多年在前端领域积累的丰富经验,通过 React 切入所有平台。这可能成为了两套框架的设计初衷。
Microsoft 到是另辟蹊径,在 IDE(VSCode)上花大力气,帮助大家建立更好的开发体验,统一了开发环境。
SDK 版本
Flutter: 2.5.x
React Native: 0.64.x
======
1.1 设计理念
在端上的开发,有前辈总结了一个很精辟的观点:端上的开发无外乎三件事,“数据获取”,“状态管理”,“页面渲染”。而在跨端领域的竟争,我理解是“虚拟机”,“渲染引擎”,“原生交互”,“开发环境”的竟争。而在这几点上,无论是 Flutter 还是 React Native (以下简称 RN) 都有非常棒的解决方案。
首先从 Flutter 来看,在虚拟机上使用了 Dart VM,Dart 支持 JIT 与 AOT 两种编译模式,也就是我们所说的动态编译与静态编译。在开发阶段使用 JIT 编译,实现热更新预览,动态加载等,而在发布阶段使用 AOT 模式编译为机器码,保证启动速度和跨端信息的传递效率。在渲染引擎上,Flutter 使用了 Skia 渲染引擎进行视图绘制,避开了不同平台上控件渲染差异。而且,少了这一层的交互,使得效率也得到提升。而在原生交互上,因为 Dart 本身跨平台的特性,底层 C++ 可以直接访问到原生的 API,加上信息使用机器码进行传递 (BinaryMessage),所以与原生交互的效率非常高。
然后再说 RN ,在早期的架构上虚拟机使用的是 JSC (Javascript Core) 执行运算,这样它可以充分复用 JS 生态,吸引大量前端开发者参与。而且由于 JS 天生跨平台的特点,跨端移值 App 也顺理成章。在渲染引擎上 RN 没有直接使用 WebKit 或其它 Web 引擎,因为之前 Web 在构建复杂页面时带来的计算消耗,远比不上纯原生引擎的渲染。所以它直接复用了原生的渲染通道,这样就可以带来与原生近乎一致的体验。
不过说到这儿,你可能发现虽然早期的 RN 架构充分利用了现有生态,但毕竟不像 Flutter 那样从头到尾都自己来,那么的撤底。带来的问题就是,在 JSC 到原生渲染这一层,用了非常多的 Bridge,并通过 JSON 序列化在多个线程里来回传递信息,这样的消耗在简单的交互过程中可能不明显,而在大量的交互与渲染上会有明显的卡顿,这也成为广为诟病的一点。不过在新的架构中, RN 也做出了新的方案去解决这些痛点,下面会有介绍。
但我们知道 Flutter 也不是完美的,虽然什么事情都自己造自己来,但因为缺少成熟的生态,很多问题都需要官方或社区提供足够的轮子才能解决,否则开发者会在遇到特定问题时,只能自己想办法。另外,Dart 发布阶段用了静态编译,虽然效率得到了提升,但也缺少了在线动态更新的灵活性。
1.2 核心架构
Flutter 的架构分为了三层,我们大多情况只与 Flutter Framework 层交互,更多平台无关的的底层能力已被封装好。这也使得 Flutter Framework 非常的轻,如果你需要更多的原生能力,通常使用各类 Flutter Plugin 比如 Camera。
所以原生能力(轮子)依赖于官方和社区的产出速度
Old
三个线程各自负责运算,渲染,Native 交互,中间的交互使用 Bridge 与 JSON 信息格式进行传递。
New
新的架构主要有两点改变
渲染引擎仍是依赖原生的管道。猜测可能 FB 没有像 Google 那样,有这么多年的 Web 渲染引擎经验,轮子就不用再花时间再造了
Old
可以看到 Bridge 非常的重
将原先较重的 Bridge 分拆成两个模块,Farbric 处理 UI,TurboModules 处理与原生交互
两个模块均是遵循 JSI 协议的 C++ 模块
========
2.1 数据获取
Flutter | React Native |
---|---|
http.dart 库C++ 实现 | 复用现有的 JS 库fetch, XMLHttpRequest, Axios |
Flutter
import 'package:http/http.dart' as http;
// 它返回一个 Flutter 的 Future 对象,类似 JS 的 Promise.
http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
RN
fetch('https://reactnative.dev/movies.json');
其它 JS 生态里的网络库都是适用的
官方提供了 json_serializable 库,让你可以先定义好模型与属性后,直接通过命令行生成对应的 JSON 转模型代码。
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$UserToJson(this);
}
运行脚本命令即可
flutter pub run build_runner build
官方没有提供了 JSON Model 转化库,需要自己找轮子。
2.2 状态管理
正如 Flutter 将所有控件都定义为了 Widget 一样,它也分成了两种 Widget,一种是 Stateful, 另一种是 Stateless。
Stateless
Stateless 是无状态的,不能通过 state 状态去更新控件
class MyScaffold extends StatelessWidget {
const MyScaffold({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
...
}
}
Stateful
Stateful 是有状态的,可以通过 state 变化去更新控件,但写法和 JS 有些不大一样,需要习惯
class FavoriteWidget extends StatefulWidget {
const FavoriteWidget({Key? key}) : super(key: key);
// 覆盖这个 createState 方法,实现状态管理
@override
_FavoriteWidgetState createState() => _FavoriteWidgetState();
}
// 状态设置通常写在私有方法这里
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
// ···
@override
Widget build(BuildContext context) {
...页面构建
}
void _toggleFavorite() {
// 用 setState 去进行状态的变更,以触发 Widget 的重新渲染
setState(() {
...
_isFavorited = false;
...
});
}
}
对于需要向上传递信息时,使用 InheritedWidget, Provider, FlutterHook 方式。
复用了 React 里的 State 模式,同时也支持现在流行的 Hook 方式使用 state,和 React 方式近乎类似。
// React Native Counter Example using Hooks!
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const App = () => {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text>You clicked {count} times</Text>
<Button
onPress={() => setCount(count + 1)}
title="Click me!"
/>
</View>
);
};
// React Native Styles
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
});
2.3 页面渲染
在渲染方式上,理论基本一样,先是构建一颗平台无关性的虚拟树 (Virtual Dom Tree),然后通过各自不同的实现自已渲染或交给原生进行渲染。
虚拟树的好处可以实现 UI 节点的局部更新,而不会全量刷新,具有平台无关性
`UI = f(state)`
UI 仅依赖于它的父类与自身的状态
Flutter 在设计之初,也借鉴了很多 React 的设计思想。
Flutter
在 Flutter 中,UI 组件称为 Widget,Flutter 将所有可能的控件都封装为 Widget ,而 RN 没有将所有控件封装,而是将样式与 Component 分离,进行自由组合。所以你不会在 RN 里看到长长的嵌套。
Flutter Widget 嵌套组合:
虽然看起来组合 UI 很合理,但对于处理复杂的 UI 场景,就拙荆见肘了,比如富文本。
在 RN 中,UI 组件称为 Component,布局沿用了 Component (类似 Web UI 元素) + Style (类似 CSS) 进行布局,没有像 Flutter Widget 一样先封装好各种"样式+组件",而是把选择权交给你自己进行组合。
import {View, Text, StyleSheet} from ‘react-native';
class HelloThere extends React.Component {
render() {
return (
<View style={styles.box}>
<Text>Hello World!</Text>
</View>
);
}
}
var styles = StyleSheet.create({
box: {
borderColor: 'red',
backgroundColor: '#fff',
borderWidth: 1,
padding: 10,
width: 100,
height: 100
}
});
Flutter
正如上面提到的架构所示,Flutter 不需要和原生渲染引擎打交道,直接通过 Skia (2D 渲染引擎)进行绘制 (GPU),所以它的渲染管道非常简洁高效。
React Native
RN 是在通过 Yoga (布局引擎)计算好后位置后,通过不同平台的渲染管道进行渲染,所以这里在 Layout 计算与投递结果的过程中多了 Bridge 环节,效率可想而知。\
Flutter UI 所见即所得,在所有平台上表现一致。RN 依赖平台的原生控件样式,表现更趋于原生。
如前所说,Flutter 在更新完 UI Tree 后直接通过 GPU 渲染
和 React Render 很类似,先是更新 VDom ,然后再更新真正的组件,只是 RN 是 Native 组件
2.4 原生交互
Flutter 内嵌入 Native 页面
Fluttter 提供了 AndroidView 与 UiKitView 来支持原生页面的嵌入,不过这类 Widget 在使用中还要注意布局,事件的回调等诸多问题,从官方的文档来看其实不太推荐这类场景。虽然架构上没有限制,但目前桌面端的 Widget 还不支持。
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the maps plugin');
Native 嵌入 Fluttter
如 Flutter Demo 所示一样,它可以被嵌入任何 Activity 或 ViewController 中。
官方建议最好是在应用初始化时将 Flutter 环境加载好,或者在向用户展示 Flutter 页面前加载好。因为 Flutter 初始化要做很多事情,如 加载 Flutter 库,初始化 Dart VM, 创建 Dart Isolate(内存与线程管理),UI 初始化等。预热的时间消耗大概是在 300ms 左右(参考官方数据)
React Native 与 Native 原生的控件互嵌相对比较容易。
Native 内嵌入 RN 页面
iOS
RCTRootView 我们可以认为是 RN 的一个容器,可以像处理普通 View 一样进行添加。但要注意 RN 里的 layout 要设置为 flex 布局,以便按容器的 size 去适配。
- (void)viewDidLoad {
...
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:appName
initialProperties:props];
rootView.frame = CGRectMake(0, 0, self.view.width, 200);
[self.view addSubview:rootView];
...
}
RN 内嵌入 Native 页面
iOS
继承 RCTViewManager 。然后和事件通信一样,通过 RCT_EXPORT_MODULE 暴露 Native 对应的类,然后实现 view 方法,返回 native 的 view 实例。
// RNTMapManager.m
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
@interface RNTMapManager : RCTViewManager
@end
@implementation RNTMapManager
RCT_EXPORT_MODULE(RNTMap)
- (UIView *)view
{
return [[MKMapView alloc] init];
}
@end
然后在 RN 里直接插入该 View 到对应的 UI 组件下。
import { requireNativeComponent } from 'react-native';
// requireNativeComponent automatically resolves 'RNTMap' to 'RNTMapManager'
module.exports = requireNativeComponent('RNTMap');
// MyApp.js
import MapView from './MapView.js';
...
render() {
return <MapView style={{ flex: 1 }} />;
}
Native <-> Shell (iOS /Android) <-> MethodChannel (Flutter Framework) <-> Dart Code
Message 会被不同的平台间进行类型转换,比如 Map -> HashMap/Dictionary
Dart
const channel = MethodChannel('foo');
final String greeting = await channel.invokeMethod('bar', 'world');
print(greeting);
iOS
let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView)
channel.setMethodCallHandler {
(call: FlutterMethodCall, result: FlutterResult) -> Void in
switch (call.method) {
case "bar": result("Hello, (call.arguments as! String)")
default: result(FlutterMethodNotImplemented)
}
}
Android
val channel = MethodChannel(flutterView, "foo")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"bar" -> result.success("Hello, ${call.arguments}")
else -> result.notImplemented()
}
}
Native
在 Native 侧只需实现对应的协议,即可将类或方法暴露给 RN
React 通常将要它们称为 Module
iOS
// RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
// 在对应的 Native Class 声明上加上 RCTBridgeModule 协议
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end
// RCTCalendarModule.m
#import "RCTCalendarModule.h"
@implementation RCTCalendarModule
// 将这个类暴露给 RN 掉用。如果不指定名称,默认以类的名字命名
RCT_EXPORT_MODULE();
// 暴露一个方法给 RN
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}
@end
Android
参照以下方式引入 RN 各个库,并继承 ReactContextBaseJavaModule ,加上 @ReactMethod 标识以暴露方法
package com.your-app-name; // replace com.your-app-name with your app’s name
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class CalendarModule extends ReactContextBaseJavaModule {
CalendarModule(ReactApplicationContext context) {
super(context);
}
}
// 暴露方法给 RN
@ReactMethod
public void createCalendarEvent(String name, String location) {
}
RN
这个时候就可以通过下面方法访问到 Native 的类
const { CalendarModule } = ReactNative.NativeModules;
========
3.1 编码
每个 Widget 可以继续嵌套 Widget ,有点类似俄罗斯套娃。
SwiftUI 也是声明式的,写法很类似
var container = Container( // grey box
child: Center(
child: Container( // red box
child: Text(
"Lorem ipsum",
style: bold24Roboto,
),
decoration: BoxDecoration(
color: Colors.red[400],
),
padding: EdgeInsets.all(16),
width: 240, //max-width is 240
),
),
width: 320,
height: 240,
color: Colors.grey[300],
);
RN 可以支持函数式编程 Hook 与 Class 方式编写。样式与组件代码分离,不会有长长嵌套出现。
3.2 调试
在 UI 调试上,两者都有对应的工具。效果上来看,RN 更加像 JS 的调试工具一样,上手比较快。
react-devtools
Flutter Widget Inspector
但两个方案都有共同的一个问题,就是当需要 Native 与 RN/Flutter 联调时,比如在两侧都要打断点时,好像没有推荐的做法。
========
4.1 环境依赖
4.2 工程化
可使用线上代码管理,进行一站式代码提交,打包 Flutter 项目,不过目前还没有国内平台支持。
或者通过 fastlane 手动设置 pipeline.
官方没有提供最佳实践,不过因为 JS 在线打包很多平台都已支持,所以只要配置对应的 Native 工程环境即可。
4.3 产物
通过 flutter 可以用命令行工具手动生成最终产物
iOS 生成的是两个 framework
flutter build ios-framework
Android 可以生成 aar 或 apk
flutter build apk
Metro
RN 通过 Metro(专为 React Native 设计)打包工具将所有 RN 代码打包成对应的 js.bundle 产物,双端产物大小差不多。
你也可以自己通过命令行生成离线包:
react-native bundle --entry-file index.js --bundle-output ./bundle/ios.bundle --platform ios --dev false
注意:--dev false
生成的非 dev 包和 dev 包,大小还是差很多的。
官方提供的一个初始化工程,生成的 bundle 大概是在 750 KB 左右
======
在大多数浏览器和手机设备上都是 60HZ 刷新频率,也就我们只能在每帧 16ms 的时间内处理完所有事情,包括渲染才能保证显示的平滑。
在渲染效率上,官方其实也提到了,我们的大部分业务逻辑和事件处理都是在 JS 线程上的,因为架构的原因,在 JS 线程处理完数据之后,要扔给 UI 线程进行 Native 原生控件渲染,如果这个时间等于 200ms 就会丢掉 12 帧。而出现卡顿。如果任何情况下超过 100ms 就会被用户所感知。这种情况通常发生在新进一个页面时,要计算所有控件和布局进行渲染。
其实 Flutter 因为少了原生控件的转化,少了一步桥接上的时间消耗。但要注意的问题仍一样,业务逻辑的处理耗时,和 UI Tree 层级。
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
}
]
可通过 VS Code 安装 Dart Extension, 然后点击状态栏下方 status bar ,打开 DevTools 查看时时性能。
DevTools
========
Flutter | React-Native | 备注 | |
---|---|---|---|
背后团队 | |||
发布日期 | 2017.5 | 2015.3 | React-Native 是一个更成熟的框架 |
编程语言 | Dart | Javascript | |
学习曲线 | 低 | 低 | 如果你已经了解 JS,将会更快上手 RN. |
热加载 | 是 | 是 | |
热更新 | 否 | 是 | RN 可下发 JS 实现。 Flutter 产物已为二进制 |
开源 | 是 | 是 | |
文档完整性 | 是 | 是 | |
编程架构 | State Manager | Flux | 都基于状态管理 |
自动化集成发布 | 官方文档 | 无可用的官方文档 | |
插件数量 | ~20k | ~30k | 如果算上 React 的话插件就有 200k 左右 |
仓库地址 | |||
Github Stars/Forks | 132k/19k | 99k/21k | |
产物 | ~10MB (Android) ~100MB (iOS) | ~ 70M (Android) ~ 40M (iOS) | 模板空工程,多架构产物 |
【2021最新版】Android studio安装教程+Android(安卓)零基础教程视频(适合Android 0基础,Android初学入门)_哔哩哔哩_bilibili
Android进阶系统学习——高级UI卡顿性能优化_哔哩哔哩_bilibili
【 Android进阶教程】——Framework面试必问的Handler源码解析_哔哩哔哩_bilibili
Android进阶系统学习——Gradle入门与项目实战_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/7030771637796470791,如有侵权,请联系删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。