关于android:再谈移动端跨平台框架-Flutter-与-React-Native

9次阅读

共计 12087 个字符,预计需要花费 31 分钟才能阅读完成。

前言

这几年在大前端的开发畛域,抉择跨端计划的公司和部门越来越多,一方面是跨平台的前端框架越来越成熟,另一方面也是因原生开发者正逐年缩小。所以,在当下把握一门跨平台的技术栈还是很有必要的,无论从广度还是从深度都会有所帮忙。

那咱们应该抉择哪种技术计划呢?如果这个问题放在几年前,答案可能会有很多。不过当初看来,市面上仅剩两种支流计划,就是常常听到的 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.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 外围架构

1.3.1 Flutter

Flutter 的架构分为了三层,咱们大多状况只与 Flutter Framework 层交互,更多平台无关的的底层能力已被封装好。这也使得 Flutter Framework 十分的轻,如果你须要更多的原生能力,通常应用各类 Flutter Plugin 比方 Camera。

所以原生能力(轮子)依赖于官网和社区的产出速度

1.3.2 React

新旧架构比照

Old

三个线程各自负责运算,渲染,Native 交互,两头的交互应用 Bridge 与 JSON 信息格式进行传递。

New

新的架构次要有两点扭转

  1. JS Bundle 不再依赖于 JSC(Javascript Core)。换句话说,它能够编译和利用在任何 JS 引擎 (V8 等)。
  2. 引入 JSI 规范,基于 JSI 协定实现各自办法,使得 JS 能够间接援用 C++ 对象,反之亦然。与原生之间的交互不再用 Bridge 去做粘合。

渲染引擎仍是依赖原生的管道。猜想可能 FB 没有像 Google 那样,有这么多年的 Web 渲染引擎教训,轮子就不必再花工夫再造了

RN Bridge 上的变动

Old

能够看到 Bridge 十分的重

将原先较重的 Bridge 分拆成两个模块,Farbric 解决 UI,TurboModules 解决与原生交互

两个模块均是遵循 JSI 协定的 C++ 模块

2. 外围流程

2.1 数据获取

2.1.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 生态里的网络库都是实用的

2.1.2 JSON 模型化

Flutter

官网提供了 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

React Native

官网没有提供了 JSON Model 转化库,须要本人找轮子。

2.2 状态治理

Flutter

正如 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 Native

复用了 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 页面渲染

2.3.1 共性

在渲染形式上,实践根本一样,先是构建一颗平台无关性的虚构树 (Virtual Dom Tree),而后通过各自不同的实现自已渲染或交给原生进行渲染。

虚构树的益处能够实现 UI 节点的部分更新,而不会全量刷新,具备平台无关性

  • 两个框架都是 UI 响应式框架(React Framework)

    UI = f(state)
    UI 仅依赖于它的父类与本身的状态

    Flutter 在设计之初,也借鉴了很多 React 的设计思维。

  • 所有组件都可被组合成一颗虚构树虚构树 (VDom),在真正渲染前各个框架会把它们转化为各自的渲染对象 (RenderObj / VDom)。

2.3.2 差别

2.3.2.1 布局

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
  }
});

2.3.2.2 绘制

Flutter

正如下面提到的架构所示,Flutter 不须要和原生渲染引擎打交道,间接通过 Skia(2D 渲染引擎)进行绘制 (GPU),所以它的渲染管道十分简洁高效。

React Native

RN 是在通过 Yoga(布局引擎)计算好后地位后,通过不同平台的渲染管道进行渲染,所以这里在 Layout 计算与投递后果的过程中多了 Bridge 环节,效率可想而知。\

Flutter UI 所见即所得,在所有平台上体现统一。RN 依赖平台的原生控件款式,体现更趋于原生。

2.3.3 渲染过程

Flutter

如前所说,Flutter 在更新完 UI Tree 后间接通过 GPU 渲染

React Native

和 React Render 很相似,先是更新 VDom,而后再更新真正的组件,只是 RN 是 Native 组件

2.4 原生交互

2.4.1 混合开发 (Embed)

Flutter

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

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}} />;
}

Android 的嵌入形式相似

2.4.2 事件通信

Flutter

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()}
}

React Native

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. 开发环境

3.1 编码

Flutter

申明式的语言构造

每个 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],
);

React Native

RN 能够反对函数式编程 Hook 与 Class 形式编写。款式与组件代码拆散,不会有长长嵌套呈现。

3.2 调试

在 UI 调试上,两者都有对应的工具。成果上来看,RN 更加像 JS 的调试工具一样,上手比拟快。

react-devtools

Flutter Widget Inspector

但两个计划都有独特的一个问题,就是当须要 Native 与 RN/Flutter 联调时,比方在两侧都要打断点时,如同没有举荐的做法。

4. 保护老本

4.1 环境依赖

Flutter

  • Flutter SDK
  • XCode
  • Android toolchain

React Native

  • React Native SDK
  • XCode
  • Android toolchain
  • Node

4.2 工程化

Flutter

可应用线上代码治理,进行一站式代码提交,打包 Flutter 我的项目,不过目前还没有国内平台反对。

  • Codemagic
  • Bitrise
  • Appcircle

或者通过 fastlane 手动设置 pipeline.

React Native

官网没有提供最佳实际,不过因为 JS 在线打包很多平台都已反对,所以只有配置对应的 Native 工程环境即可。

4.3 产物

Flutter

通过 flutter 能够用命令行工具手动生成最终产物

iOS 生成的是两个 framework

flutter build ios-framework
  • App.framework (你的 Dart 代码产物) ~ 100 KB(模板空我的项目)
  • Flutter.framework(依赖的 Flutter 库)~ 100mb

Android 能够生成 aar 或 apk

flutter build apk
  • libapp.so (你的 Dart 代码产物) ~ 3.4 MB(模板空我的项目)
  • libflutter.so (flutter 工程产物) ~ 9 MB

React Native

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 左右

5. 性能

5.1 渲染性能

在大多数浏览器和手机设施上都是 60HZ 刷新频率,也就咱们只能在每帧 16ms 的工夫内解决完所有事件,包含渲染能力保障显示的平滑。

React Native

在渲染效率上,官网其实也提到了,咱们的大部分业务逻辑和事件处理都是在 JS 线程上的,因为架构的起因,在 JS 线程解决完数据之后,要扔给 UI 线程进行 Native 原生控件渲染,如果这个工夫等于 200ms 就会丢掉 12 帧。而呈现卡顿。如果任何状况下超过 100ms 就会被用户所感知。这种状况通常产生在新进一个页面时,要计算所有控件和布局进行渲染。

Flutter

其实 Flutter 因为少了原生控件的转化,少了一步桥接上的工夫耗费。但要留神的问题仍一样,业务逻辑的解决耗时,和 UI Tree 层级。

"configurations": [
  {
    "name": "Flutter",
    "request": "launch",
    "type": "dart",
    "flutterMode": "profile"
  }
]

可通过 VS Code 装置 Dart Extension, 而后点击状态栏下方 status bar,关上 DevTools 查看时时性能。

DevTools

6. 综合比拟

Flutter React-Native 备注
背地团队 Google Facebook
公布日期 2017.5 2015.3 React-Native 是一个更成熟的框架
编程语言 Dart Javascript
学习曲线 如果你曾经理解 JS,将会更快上手 RN.
热加载
热更新 RN 可下发 JS 实现。Flutter 产物已为二进制
开源
文档完整性
编程架构 State Manager Flux 都基于状态治理
自动化集成公布 官网文档 无可用的官网文档
插件数量 ~20k ~30k 如果算上 React 的话插件就有 200k 左右
仓库地址 Flutter React Native
Github Stars/Forks 132k/19k 99k/21k
产物 ~10MB (Android) ~100MB (iOS) ~ 70M (Android) ~ 40M (iOS) 模板空工程,多架构产物

什么时候抉择跨平台框架

  • 当你没有太多 UI 动效和简单的交互界面时
  • 如果你已有原生我的项目,想在局部模块晋升开发效率时
  • 当你新建一个我的项目,想疾速试错时

什么时候举荐应用 RN?

  • 已有我的项目,有较多场景想混合开发时
  • 已有前端页面,想尽快移植时
  • 有大量前端开发者,Native 人员不足时
  • 有真正跨多端场景时, iOS/Native/Web/Desktop

什么时候举荐应用 Flutter?

  • 全新我的项目,无太多混合开发的场景
  • 现存我的项目,没有太多 Native 与 Flutter 页面相互嵌套的状况
  • 在挪动设施上对于渲染性能及 UI 一致性有较高要求时

相干视频:

【2021 最新版】Android studio 装置教程 +Android(安卓)零基础教程视频(适宜 Android 0 根底,Android 初学入门)_哔哩哔哩_bilibili

Android 进阶零碎学习——高级 UI 卡顿性能优化_哔哩哔哩_bilibili

【Android 进阶教程】——Framework 面试必问的 Handler 源码解析_哔哩哔哩_bilibili

Android 进阶零碎学习——Gradle 入门与我的项目实战_哔哩哔哩_bilibili

正文完
 0