美文网首页Flutter圈子
Flutter混合开发-iOS

Flutter混合开发-iOS

作者: 繁星mind | 来源:发表于2020-04-16 19:15 被阅读0次

    本文主要针对现有iOS项目想接入flutter,怎么接入flutter,如何进行项目管理,以及Native和flutter之间如何调用,如何调试来讲解的。

    一、创建Flutter Module

    执行下面的命令创建Flutter Moudle

    cd some/path/
    flutter create --template module my_flutter
    

    some/path/是你要存放工程的目录,然后创建flutter Module,这一步要注意,不要创建成flutter project项目了,执行命令后,控制台会打印:

    Creating project my_flutter... androidx: true
      my_flutter/test/widget_test.dart (created)
      my_flutter/my_flutter.iml (created)
      my_flutter/.gitignore (created)
      my_flutter/.metadata (created)
      my_flutter/pubspec.yaml (created)
      my_flutter/README.md (created)
      my_flutter/lib/main.dart (created)
      my_flutter/my_flutter_android.iml (created)
      my_flutter/.idea/libraries/Flutter_for_Android.xml (created)
      my_flutter/.idea/libraries/Dart_SDK.xml (created)
      my_flutter/.idea/modules.xml (created)
      my_flutter/.idea/workspace.xml (created)
    Running "flutter pub get" in my_flutter...                          1.8s
    Wrote 12 files.
    
    All done!
    Your module code is in my_flutter/lib/main.dart.
    

    创建完成以后my_flutter文件结构如下:

    my_flutter/
    ├── .ios/
    │   ├── Runner.xcworkspace
    │   └── Flutter/podhelper.rb
    ├── lib/
    │   └── main.dart
    ├── test/
    └── pubspec.yaml
    
    

    接下来可以在lib中添加代码逻辑,在pubspec.yaml中,添加依赖的packages和plugins。

    二、集成方式

    1.使用CocoaPods和Flutter SDK集成

    这个方案是针对高于Flutter 1.8.4-pre.21版本的SDK的混编方案,如果使用之前的SDK,请查看Upgrading Flutter added to existing iOS Xcode projectAdd Flutter to existing apps

    1.1 Podfile 中添加下面配置

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

    ../my_flutter是你flutter Moudle存放的目录,这里my_flutter存放在profile的上一级目录,所以这么写。

    1.2 Podfile target 中添加install_all_flutter_pods(flutter_application_path)

    target 'MyApp' do
      install_all_flutter_pods(flutter_application_path)
    end
    

    这里的MyApp就是对应iOS项目的名称,存放到自己项目对应target中就好了。

    1.3 pod install

    在Podfile所在目录,执行pod install,如果没问题,会在你的项目中增加以下依赖:

    Installing Flutter (1.0.0)
    Installing FlutterPluginRegistrant (0.0.1)
    Installing my_flutter (0.0.1)
    

    在执行pod install以后,如果没有增加上面👆的依赖,那么可能是工程有问题。

    问题1.Profile中路径添加错误或者my_flutter是Flutter project,不是Flutter Moudle
    提示错误如下:

    [!] Invalid `Podfile` file: cannot load such file -- ./my_flutter/.ios/Flutter/podhelper.rb.
    
     #  from /Users/Example/Podfile:10
     #  -------------------------------------------
     #
     >  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
     #
     #  -------------------------------------------
    

    这时候要检查下flutter_application_path是否正确,如果正确,查看下my_flutter是否是Flutter project,可以查看下my_flutter是否包含,.iOS这个文件,注意这是一个隐藏文件,先要在电脑中设置成显示隐藏你文件,再进行查看确认,如果包含则是Moudle,不包含则是project。

    问题2.项目签名不对

    在my_flutter目录下运行如下命令:

    open -a Simulator
    flutter build ios
    

    查看能否正确运行,如果提示以下错误,则是证书问题:

    It appears that your application still contains the default signing identifier.
    Try replacing 'com.example' with your signing id in Xcode:
      open ios/Runner.xcworkspace
    Encountered error while building for device.
    

    怎么解决这个问题?

    方法一:

    • 1.找到my_flutter/.ios,打开Runner.xcworkspace文件
    • 2.找到Signing & Capabilities,将Signing证书配置正确就可以了(这里要配置Generic iOS Deveice)

    方法二:
    使用如下命令,忽略签名:

    flutter build ios --release --no-codesign
    

    配置成功之后,再次运行flutter build ios,会打印如下信息:

    Automatically signing iOS for device deployment using specified development team in Xcode project:
    56XB5ELH9A
    Running Xcode build...
     ├─Building Dart code...                                    78.1s
     ├─Generating dSYM file...                                   0.1s
     ├─Stripping debug symbols...                                0.0s
     ├─Assembling Flutter resources...                           0.9s
     └─Compiling, linking and signing...                         2.9s
    Xcode build done.                                           84.0s
    Built
    

    然后再次pod install应该就可以成功了。

    1.4 方案优缺点

    优点:

    • 1.功能配置简单,方便管理。
    • 2.使用CocoaPods便于集成。

    缺点:

    • 1.团队成员都必须配置flutter环境,否则编译不过
    • 2.Native代码和Flutter代码存放在一起,会变得复杂。

    2.framework方式接入

    2.1生成FrameWork

    首先切换到my_flutter所在目录,执行下列命令,生成framework

    flutter build ios-framework --output=../Flutter/
    

    命令执行成功后,会在my_flutter同一级目录下,产生Flutter的文件,文件结构如下:

    Flutter/
    ├── Debug/
    │   ├── Flutter.framework
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework (only if you have plugins with iOS platform code)
    │   └── example_plugin.framework (each plugin is a separate framework)
    ├── Profile/
    │   ├── Flutter.framework
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework
    └── Release/
        ├── Flutter.framework
        ├── App.framework
        ├── FlutterPluginRegistrant.framework
        └── example_plugin.framework
    

    2.2配置frameWork路径

    在项目中找到这个路径build settings > Build Phases > Link Binary With Libraries
    添加$(PROJECT_DIR)/Flutter/Release/Framework Search Paths

    配置

    2.3嵌入frameWork

    在项目中找到这个路径General > Frameworks,Libraries and Embedded Content
    app.FrameworkFlutter.FrameWork添加到项目中,就可以使用了。

    问题1.Failed to find assets path for "flutter_assets"

    Failed to find assets path for "flutter_assets"
    [VERBOSE-2:engine.cc(114)] Engine run configuration was invalid.
    

    如果报上面的错误,则在my_flutter中运行以下命令:

    flutter clean
    flutter build ios
    

    问题2.dyld: Library not loaded: @rpath/Flutter.framework/Flutter

    这个问题是说明嵌入frameWork有问题,可以检查一下,Embed Framework和Link Binary With Libraries

    Embed Framework

    2.4 方案优缺点

    优点:

    • 1.团队成员不依赖flutter环境

    缺点:

    • 1.打包配置,比较麻烦,都需要手动操作。

    3.使用Flutter framework和CocoaPods集成(本地)

    3.1生成frameWork

    在Flutter v1.13.6之后版本,支持--cocoapods参数,可以使用下面命令。

    flutter build ios-framework --cocoapods --output=../Flutter/
    

    生成如下文件结构:

    Flutter/
    ├── Debug/
    │   ├── Flutter.podspec
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework (each plugin with iOS platform code is a separate framework)
    ├── Profile/
    │   ├── Flutter.podspec
    │   ├── App.framework
    │   ├── FlutterPluginRegistrant.framework
    │   └── example_plugin.framework
    └── Release/
        ├── Flutter.podspec
        ├── App.framework
        ├── FlutterPluginRegistrant.framework
        └── example_plugin.framework
    

    3.2配置profile文件

    pod 'Flutter', :podspec => '../Flutter/{build_mode}/Flutter.podspec'
    
    

    3.3 方案优缺点

    优点:

    • 1.团队成员不依赖flutter环境
    • 2.可以使用cocoapods集成管理。

    缺点:

    • 1.Flutter版本有限制
    • 2.每次需要自己打frmaework

    4.使用Flutter framework和CocoaPods集成(远程)

    4.1创建一个CocoaPods私有库

    在my_flutter同级目录下,创建CocoaPods私有库

    $ pod lib create MyFlutterFramework
    

    终端执行代码:

     xingkunkun:FlutterForFW admin$ pod lib create MyFlutterFramework
     Cloning `https://github.com/CocoaPods/pod-template.git` into `MyFlutterFramework `.
    Configuring MyFlutter template.
    ------------------------------
    To get you started we need to ask a few questions, this should only take a minute.
    
    What platform do you want to use?? [ iOS / macOS ]
     > ios
    What language do you want to use?? [ Swift / ObjC ]
     > objc
    Would you like to include a demo application with your library? [ Yes / No ]
     > no
    Which testing frameworks will you use? [ Specta / Kiwi / None ]
     > none
    Would you like to do view based testing? [ Yes / No ]
     > no
    What is your class prefix?
     >
    
    Running pod install on your new library.
    

    4.2创建一个Flutter Module

    • 1.创建Flutter Module步骤,
    flutter create --template module my_flutter
    
    • 2.构建framework
    $ flutter build ios --debug 
     或者 
    flutter build ios --release --no-codesign(选择不需要证书)
    
    • 3.检查.ios目录下
      • 是否有Flutter-->App.framework
      • 是否有Flutter-->engine-->Flutter.framework
    .ios目录下
    Flutter-->App.framework
    Flutter-->engine-->Flutter.framework
    

    4.3将CocoaPods私有库集成到Native项目中

    在MyFlutterFramework中创建ios_frameworks文件夹,并将App.frameworkFlutter.framework拷贝进去。

    在MyFlutterFramework的podspec文件中,添加以下配置:

      s.static_framework = true
      arr = Array.new
      arr.push('ios_frameworks/*.framework')
      s.ios.vendored_frameworks = arr
    

    之后在MyFlutterFramework的podfile同级目录中执行

    $ pod install
    

    在MyApp工程下的podfile文件中添加

    platform :ios, '8.0'
    
    target 'MyApp' do
      # Comment the next line if you don't want to use dynamic frameworks
      use_frameworks!
    
      # Pods for MyApp
       pod 'MyFlutterFramework', :path => '../MyFlutterFramework'
    
    end
    

    之后在MyApp的podfile同级目录中执行

    $ pod install
    

    这时在MyApp中,就可以找到App.frameworkFlutter.framework

    4.4将MyFlutterFramework和my_flutter推送到远程仓库

    • 1.MyFlutterFramework和my_flutter推送到远程仓库
    • 2.修改MyApp工程下的podfile,将pod 'MyFlutterFramework'依赖修改为MyFlutterFramework远程连接。
    platform :ios, '8.0'
    
    target 'MyApp' do
      # Comment the next line if you don't want to use dynamic frameworks
      use_frameworks!
    
      # Pods for MyApp
       pod 'MyFlutterFramework', :git=>'https://gitlab.com/MyFlutterFramework.git'
    
    end
    
      1. 如果MyFlutterFramework中的ios_frameworks不详推送到远程仓库,可以在gitignore文件中添加一下
    # 忽略ios_frameworks中文件
    ios_frameworks
    
    

    4.5 方案优缺点

    优点:

    • 1.团队成员不依赖flutter环境
    • 2.可以使用cocoapods集成管理。
    • 3.可以使用远程仓库共享和管理项目代码

    缺点:

    • 1.每次重新构建,需要移动framework位置,比较繁琐,可以使用脚本解决。

    三、Flutter与Native交互

    Flutter 官方提供了一种 Platform Channel 的方案,用于 Dart 和平台之间相互通信。

    核心原理:

    • Flutter应用通过Platform Channel将传递的数据编码成消息的形式,跨线程发送到该应用所在的宿主(Android或iOS);
    • 宿主接收到Platform Channel的消息后,调用相应平台的API,也就是原生编程语言来执行相应方法;
    • 执行完成后将结果数据通过同样方式原路返回给应用程序的Flutter部分。

    Flutter提供了三种不同的Channel:

    • BasicMessageChannel(主要是传递字符串和一些半结构体的数据)
    • MethodChannel(用于传递方法调用)
    • EventChannel(数据流的通信)

    下面是使用Platform Channel进行通信的示例:
    示例代码

    1.Native app主动与Flutter交互

    交互主要分为三步:

    • 1.flutter注册MethodChannel
    • 2.flutter MethodChannel监听native消息
    • 3.native通过MethodChannel发送消息

    Dart代码

    class HomePage extends StatefulWidget {
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
    
      String title = 'Flutter to Native';
      Color backGroundColor = Colors.red;
    
    // 注册一个通知
      static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get');
    
      _HomePageState(){
        //Native调用Dart方法
        methodChannel.setMethodCallHandler((MethodCall call){
          if(call.method  == "NativeToFlutter"){
            setState(() {
              title = call.arguments;
              backGroundColor = Colors.yellow;
            });
          }
          return Future<dynamic>.value();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: backGroundColor,
          body: Center(
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              child: new Text(title),
              onTap: (){
                _iOSPushToVC();
              },
            ),
          ),
        );
      }
    }
    

    Native代码

    #import "ViewController.h"
    #import <Flutter/Flutter.h>
    
    #define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
    
    @interface ViewController ()
    
    @property (nonatomic, strong) FlutterMethodChannel *messageChannel;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button addTarget:self action:@selector(pressOn) forControlEvents:UIControlEventTouchUpInside];
        [button setTitle:@"加载Flutter" forState:UIControlStateNormal];
        [button setBackgroundColor:[UIColor blueColor]];
        button.frame = CGRectMake((SCREEN_WIDTH-100)/2, 100, 100, 60);
        [self.view addSubview:button];
    
    }
    
    - (void)pressOn
    {
        FlutterViewController *flutterViewController =[FlutterViewController new];
        //设置路由参数
        [flutterViewController setInitialRoute:@"route"];
        
        NSString *channelName = @"com.pages.your/native_get";// 要与main.dart中一致
        _messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];
        [self nativeToFlutter];
        [self presentViewController:flutterViewController animated:false completion:nil];
    }
    
    - (void)nativeToFlutter
    {
        sleep(5);
        [_messageChannel invokeMethod:@"NativeToFlutter" arguments:@"NativeToFlutter"];
    }
    
    @end
    

    2.Flutter主动与Native app交互

    交互主要分为以下几步:

    • 1.Native创建MethodChannel。
    • 2.Native添加HandleBlcok。
    • 3.Flutter发送消息。

    Dart代码

    class HomePage extends StatefulWidget {
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
    
      String title = 'Flutter to Native';
      Color backGroundColor = Colors.red;
    
    // 注册一个通知
      static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get');
    
      //Dart调用Native方法,并接收返回值。
      _iOSPushToVC() async {
        title = await methodChannel.invokeMethod('FlutterToNative');
        setState(() {
          backGroundColor = Colors.green;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            backgroundColor: backGroundColor,
              body: Center(
                  child: GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    child: new Text(title),
                    onTap: (){
                      _iOSPushToVC();
                    },
                  ),
              ),
            );
      }
    }
    

    Native代码

    #import "ViewController.h"
    #import <Flutter/Flutter.h>
    
    #define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
    
    @interface ViewController ()
    
    @property (nonatomic, strong) FlutterMethodChannel *messageChannel;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button addTarget:self action:@selector(pressOn) forControlEvents:UIControlEventTouchUpInside];
        [button setTitle:@"加载Flutter" forState:UIControlStateNormal];
        [button setBackgroundColor:[UIColor blueColor]];
        button.frame = CGRectMake((SCREEN_WIDTH-100)/2, 100, 100, 60);
        [self.view addSubview:button];
    
    }
    
    - (void)pressOn
    {
        FlutterViewController *flutterViewController =[FlutterViewController new];
        //设置路由参数
        [flutterViewController setInitialRoute:@"route"];
        
        NSString *channelName = @"com.pages.your/native_get";// 要与main.dart中一致
        _messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController];
        
    
        __weak typeof(self) weakSelf = self;
        [_messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result)
        {
            __strong typeof(self) strongSelf = weakSelf;
            if ([call.method isEqualToString:@"FlutterToNative"]) {
              if (result) {
                  result(@"NativeBack");
              }
            }
        }];
        
        [self presentViewController:flutterViewController animated:false completion:nil];
    }
    
    @end
    

    四、app调试

    混合开发的时候,需要在XCode调试代码,在调试的过程中,怎么调试Dart代码呢?或者能不能使用热加载?

    1.调试Dart代码

    在混合开发过程中,在iOS项目中,我们如何调试dart代码呢?

    • 1.关闭我们的app
    • 2.点击Android Studio工具栏上的Flutter Attach按钮
      • 点击之后会提示Waiting for a connection from Flutter on iPhone 11 Pro...
    • 3.启动我们的app
      • 启动app之后,会提示Syncing files to device iPhone 11 Pro...

    接下来就可以像调试普通Flutter项目一样来调试混合开发模式下的Dart代码了。

    2.热加载

    • 1.关闭我们的app
    • 2.在terminal中运行 flutter attach命令。
    $ flutter attach
    Waiting for a connection from Flutter on iPhone 11 Pro Max...
    

    注意,这里如果提示有多个设备,如下所示:

    More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.
    
    我的 iPhone       • 00008030-000445611146802E            • ios • iOS 13.3
    iPhone 11 Pro Max • 67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-2 (simulator)
    
    

    可以使用以下命令:

     flutter attach -d 67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9 //67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9是设备对应的id
    
    • 3.启动app,启动之后会有如下提示,就代表成功了。
    Syncing files to device iPhone 11 Pro Max...                            
     4,196ms (!)                                       
    
    🔥  To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
    An Observatory debugger and profiler on iPhone 11 Pro Max is available at: http://127.0.0.1:62889/T9DjblAu03w=/
    For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".
    

    接下来就可以在terminal调试了:

    r : 热加载;
    R : 热重启;
    h : 获取帮助;
    d : 断开连接;
    q : 退出;
    

    参考资料:

    Integrate a Flutter module into your iOS project

    Upgrading Flutter added to existing iOS Xcode project

    Add Flutter to existing apps

    flutter集成进iOS工程

    闲鱼flutter-boot介绍

    优雅的 Flutter 组件化 混编方案

    Flutter和原生iOS交互

    Flutter混合开发(二):iOS项目集成Flutter模块详细指南

    深入理解Flutter Platform Channel

    Flutter混合开发二-FlutterBoost使用介绍

    如何用 Flutter 实现混合开发?

    深入理解Flutter的Platform Channel机制

    相关文章

      网友评论

        本文标题:Flutter混合开发-iOS

        本文链接:https://www.haomeiwen.com/subject/yftumhtx.html