跨平台方案 |Flutter |Flutter与RN简单对比

大佬文档

源码对应的git地址 platform_rn_flutter

跨平台方案

对比项RNflutter
当前版本v0.63.31.23.0-19.0.pre.20
start90.8K105K
issues8105K+(0.29天)
环境配置npm、node、react-native-cliflutter_sdk
运行环境JSCoreFlutter Engine
语言JavaScriptdart
编译器WebStorm/VSCode、AndroidStudio、XCodeAndroidStudio(VSCode)、XCode
android-gradle6.25.6.2
新建用时02:34.1700:21.22
启动用时(包拉取完成)30S14S
混合开发JavaModuleMethodChannel

环境搭建

共同需要

Android和ios开发环境

React Native

npm、node、react-native-cli等

Flutter

flutter-sdk、编译器插件

对比发现,flutter环境搭建较为简单,失败率低于RN

实现原理

  • RN 通过JS配置页面布局,最终解析渲染为原生控件
  • flutter 使用原生的画布,所有的控件都是自己绘制

开发

语言对比

1
2
3
4
5
6
7
8
9
10
11
12
var a = 1

async function doSomeThing() {
var result = await xxxx()
doAsync().then((res) => {
console.log("ffff")
})
}
function* _loadUserInfo () {
console.log("**********************");
yield put(UpdateUserAction(res.data));
}
1
2
3
4
5
6
7
8
9
10
11
12
var a = 1;

void doSomeThing() async {
var result = await xxxx();
doAsync().then((res) {
print('ffff');
});
}
_loadUserInfo() async* {
print("**********************");
yield UpdateUserAction(res.data);
}

动态语言和非动态语言都有各种的优缺点,比如 JS 开发便捷度明显会高于 Dart ,而 Dart 在类型安全和重构代码等方面又会比 JS 更稳健。

界面开发

最大的区别在于flutter的平台无关性,因为RN最终渲染的是原生UI,所有在不同的端上表现的是不同的效果;flutter渲染的结果是所有的端上,都是相同的,如果需要不同的效果,需要做单独处理。

状态管理

这一点两个平台是很接近的,甚至可以说是跟着RN走的

1
2
3
4
5
6
7
8
9
10
11
12
13
this.state = {
name: ""
};

···

this.setState({
name: "loading"
});

···

<Text>this.state.name</Text>
1
2
3
4
5
6
7
8
9
var name = "";

setState(() {
name = "loading";
});

···

Text(name)

性能比较

60fps

混合开发

flutter

1
var platform = const MethodChannel('samples.flutter.io/battery');

调用原生方法

1
2
3
void _incrementCounter() async {
await platform.invokeMethod('getBatteryLevel');
}

原生写法,对应act里面添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//通道名称----和flutter一致
private val CHANNEL = "samples.flutter.io/battery"

//这个地方和官方教程不一样,可能是版本问题
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine);

MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {//这个是对应的方法名,或者说tag,和flutter调用时一致
startActivity(Intent(this, HomeActivity::class.java))
result.success("")//回调,flutter可以拿到的返回
} else {
//没有对应方法
result.notImplemented()
}
}

}

原生方法调用

1
2
//CHANNEL 通道名称,和flutter一致,invokeMethod的两个方法分别是方法名(tag)和传递的参数
new MethodChannel(Objects.requireNonNull(getFlutterEngine()).getDartExecutor(), CHANNEL).invokeMethod("aaa", "c");

flutter触发调用

1
2
3
4
5
6
7
8
platform.setMethodCallHandler((handler) {
switch (handler.method) {
case "aaa"://这个就是上面Java的tag
print(handler);
break;
}
return null;
});

React Native

调用原生方法----官方示例

  • 新建ToastModule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ToastModule extends ReactContextBaseJavaModule {

private static ReactApplicationContext context;
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";

public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
context = reactContext;
}

@NonNull
@Override
public String getName() {
return "ToastExample";
}

@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
return constants;
}

@ReactMethod
public void show(String message, int duration, Callback callback) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
callback.invoke("success");//回调函数,异步回调
}
}
  • 新建CustomToastPackage—这个应该是可以复用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CustomToastPackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new ToastModule(reactContext));

return modules;
}

}
  • application中注册package

1
2
3
4
5
6
7
8
9
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new CustomToastPackage());
return packages;
}
  • 为了方便或者说规范,新建ToastExample.js

1
2
3
4
import { NativeModules } from 'react-native';
// 下一句中的ToastExample即对应上文
// public String getName()中返回的字符串
export default NativeModules.ToastExample;
  • 使用方式

1
2
3
4
5
6
import ToastExample from './ToastExample';

ToastExample.show('Awesome', ToastExample.SHORT,
(msg)=>{
console.log(msg);
});

界面搭建与简单封装

flutter—源码上传git

flutter 可以通过扩展函数做一些简单的封装,简化开发量,简单封装后的部分写法

  • 权限请求

    1
    2
    3
    4
    5
    runWithPermission(
    [Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE],
    () {
    //获取到该权限后需要执行的方法
    });
  • 网络请求

    1
    2
    3
    4
    5
    6
    7
    8
      
    HttpManager.getInstance().post(
    "user/login", {"username": "13812345678", "password": "654321"},
    (data) {
    // print(data);
    }, (error) {
    print(error);
    });
  • 全屏/展示状态栏

    1
    2
    3
    4

    import 'package:base_flutter/utils/libs_utils_common.dart';
    CommonHelper.fullscreen();
    CommonHelper.normal();
  • UI布局的问题:

    布局现有写法,嵌套层次太多,比较麻烦,可以使用扩展函数封装,实现链式调用。下面的例子是网络示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Test extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(title: Text('Demo'),),
    body: buildItem("amy")
    .addNeighbor(buildItem("billy"),)
    .intoListView()
    .intoOffstage(offstage: false)
    .intoContainer()
    );
    }

    Container buildItem(String name) {
    return Icon(Icons.phone)
    .addNeighbor(Text(name))
    .intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
    .intoContainer(color: Colors.white, padding: EdgeInsets.all(20),);
    }
    }