ReactNative与flutter混编(iOS篇)

3,252 阅读5分钟

最近由于公司产品进行优化,需要将之前RN项目中部分相对独立并且消耗性能的模块重写。经过讨论,最后选择了flutter重写该模块进行尝试,以便于后期使用flutter进一步开发公司项目。

当前的demo版本 flutter:^2.0.5,react-native:0.64.0,Xcode:12.5

混编方案

混编方案可参考 flutter官网文档,官网一共提到了三种集成方案:

  1. 使用 CocoaPods 和 Flutter SDK 集成
  2. 在 Xcode 中集成 frameworks
  3. 使用 CocoaPods 在 Xcode 和 Flutter 框架中内嵌应用和插件框架

我选择了第一种集成方案,主要在于第一种更适合项目中模块化集成和编辑调试。以下我将步骤详细列出:

(该文适合已经开发过rn和ios的开发者,所以rn和ios开发等相关环境搭建和工具的集成就不在此处说明)

步骤一:创建项目

1.创建rn项目: 由于公司的rn项目是使用ts编写的,所以我的demo也采用ts模板创建

npx react-native init RNAndFlutter --template react-native-template-typescript

2.创建flutter module

flutter create --template module my_flutter

步骤二:将flutter module集成到react-native项目中

按照官方文档给的例子是将ios项目和flutter module放在同级目录,如下所示:

QQ图片20210505142201.jpg

然后在Podfile中添加

flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end

用编辑器把创建的flutter module(my_flutter是我创建的flutter module,后面都以这个名字来代替)打开,然后在目录下执行flutter pub get,该步骤会给my_flutter生成一个完整的可运行的flutter项目,执行完之后再执行pod install,刚才生成的flutter项目中的podhelper.rb 脚本会把你的 plugins, Flutter.framework,和 App.framework 集成到ios项目中。执行完上述步骤之后,flutter就已经初步与rn工程混编在一起了。

由于该模块在公司的其他多个项目中也会用到,采用上面说的flutter module直接与项目关联会稍有不妥,之后的维护成本和难度会增加。所以我们的集成方式稍有不同,我们把生成的flutter module上传到公司的gitLab,然后在rn项目中添加node modules的方式添加进去(注意:因为flutter的项目实际挺大的,在.gitignore配置,不要把.ios和.android文件夹一并传,这些都可以在项目中运行生成)。同时,如果采用此方案,那上述的Podfile添加也需要稍作修改,例如:flutter_application_path = '../node_modules/my_flutter/my_flutter',执行这个之前切换到my_flutter目录下,执行flutter pub get


做完上述步骤我执行react-native run-ios,发现ios项目编译报错,Flipper-Folly版本有问题,试了其他解决方案,列如use_flipper!修改为use_flipper!({ 'Flipper-Folly' => '2.3.0' }),还是会有其他问题

错误:Typedef redefinition with different types ('uint8_t' (aka 'unsigned char') vs 'enum clockid_t')

解决方案:直接注销Flipper,然后删掉pod的相关文件,重新执行pod install。解决方案链接

步骤三:配置ios项目

下述步骤可参照官网来做,毕竟官网肯定比我说的详细,我简述一下

  1. 删掉ios项目中原来的plist,然后新建release和debug的plist文件
  2. 在debug.plist文件中添加
<key>NSBonjourServices</key>
<array>
    <string>_dartobservatory._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>flutter will use your local network</string>
  1. 在build settings中修改info.plist的文件路劲
  2. Build Settings > Build Phases > Copy Bundle如果有Info-Release.plist,则删掉
  3. Excluded Architectures debug中添加arm64

步骤四:RN或者Native跳转Flutter

关于导航跳转,官网给了比较详细的说明,照着做就行。但是,估计是文档没有更新,第二种方案中遵循FlutterPluginAppLifeCycleDelegate的[self.rootFlutterViewController handleStatusBarTouches:event];这一句会报错,我目前是是直接注释掉,运行似乎不受影响。(如果有更好的解决方案请留言或者私信告诉我,感激不尽)

由于项目之后可能会涉及更多混合跳转的问题,我目前就试试水,采用的阿里闲鱼的flutterBoost框架。接下来我简介一下集成flutterBoost

  1. 在pubspec.yaml添加并执行 flutter pub getpod install
flutter_boost:
    git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: 'v3.0-beta.6'
  1. 参照flutterBoost的说明,创建MyFlutterBoostDelegate文件,代码直接完全copy下来就行。建议直接下载demo然后把文件拖进去,简单快捷不出错。

  2. 在Appdelegate中添加

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"RNAndFlutter"
                                            initialProperties:nil];

  if (@available(iOS 13.0, *)) {
      rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
      rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  
  UIViewController *rootViewController = [UIViewController new];
  UINavigationController * nav =  [[UINavigationController alloc]initWithRootViewController:rootViewController];
  nav.navigationBarHidden = YES;
  rootViewController.view = rootView;
  self.window.rootViewController = nav;
  [self.window makeKeyAndVisible];
  
  //添加flutterboost相关的
  MyFlutterBoostDelegate* delegate=[[MyFlutterBoostDelegate alloc ] init];
  delegate.navigationController = nav;
  [[FlutterBoost instance] setup:application delegate:delegate callback:^(FlutterEngine *engine) {
  } ];
  
  return YES;
}
  1. 创建RN和Native的桥接文件
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>

NS_ASSUME_NONNULL_BEGIN

@interface RNAndNativeHelper : NSObject<RCTBridgeModule>

@end

NS_ASSUME_NONNULL_END
#import "RNAndNativeHelper.h"
#import <flutter_boost/FlutterBoost.h>

@implementation RNAndNativeHelper

RCT_EXPORT_MODULE();

/** RN跳转flutter
 *@param 界面跳转传参
 */
RCT_EXPORT_METHOD(pushToFlutter:(NSString *)param)
{
  [[FlutterBoost instance] open:@"/" arguments:@{@"animated":@(YES),@"params":param}];
}

@end
  1. 配置flutter的路由
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost_app.dart';
import 'HomePage.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
   static Map<String, FlutterBoostRouteFactory>
	   routerMap = {
    '/': (settings, uniqueId) {
      //跳转传参解析
      var paramTmp = settings.arguments;
      Map<String,dynamic> arguments = new Map<String,dynamic>.from(paramTmp);
      String paramTmpStr = arguments['params'];
      Map<String,dynamic> parmas = convert.jsonDecode(paramTmpStr);
      return PageRouteBuilder<dynamic>(
          settings: settings, pageBuilder: (_, __, ___)
          => MyHomePage(title: parmas['title'] ?? '...',));
    },
    'embedded': (settings, uniqueId) {
      return PageRouteBuilder<dynamic>(
          settings: settings,
          pageBuilder: (_, __, ___) =>
          MyHomePage());
    }};

    
   Route<dynamic> routeFactory(RouteSettings settings, String uniqueId) {
    FlutterBoostRouteFactory func =routerMap[settings.name];
    if (func == null) {
      return null;
    }
    return func(settings, uniqueId);
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FlutterBoostApp(
      routeFactory
    );
  }

}

  1. 由RN跳转到flutter,然后运行
const RNAndNativeHelper = NativeModules.RNAndNativeHelper;
const params = {title:'React-Native'}
RNAndNativeHelper.pushToFlutter(JSON.stringify(params));

运行起来,此时出现闪退,可删除plist中的NSAppTransportSecurity下面的NSExceptionDomains,再次运行就不会闪退了。如果出现只能用Xcode运行才能加载flutter代码,则运行flutter attach来连接设备再用其他编译器运行。

到此,整个集成过程就全部介绍完了,如果有啥问题留言,通常第二天会回复。

demo在这里,有需要的可以下载运行


参考链接:

  1. flutter官网文档
  2. flutterBoost的地址
  3. Flipper解决方案链接