Flutter和iOS混编详解

 

前言


 

      下面的内容是最近在使用Flutter和我们自己项目进行混编时候的一些总结以及自己踩的一些坑,处理完了就顺便把整个过程以及一些我们可能需要注意的点全都梳理出来,希望对有需要的小伙伴有点帮助,也方便自己后续的查看。

             

一:混编具体步骤以及需要注意的问题


 

      1:创建Flutter项目  (切记:下面任何命令执行出错基本上都是Flutter环境有问题,多执行 Flutter doctor 检查)

      这里需要我们留意的就一点, 创建的Flutter项目的文件层级和你想混编的原生项目要同级,就像下面这样:

 

       终端命令行如下: flutter_module:你自己的项目名称,自己定义。-t 和 --template 一样,别纠结。

flutter create -t module flutter_module

      还是前面开头说的,有问题多执行 flutter dotcor检查,要是没有问题,正确创建成功之后是下面的情况:(我临时在桌面创建的,请忽略位置)

       2:通过pod将Flutter模块导入项目

      我们在我们项目的podfile文件中加入下面两句:

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

      注意: flutter_application_path 后面的是你自己Flutter项目的名称。flutter_application_path为Flutter模块相对于podfile文件的位置。

      在target种加入下面这句

install_all_flutter_pods(flutter_application_path)

      我这里刚好有一份写demo时候的podfile文件,代码全都给出来,方便也想demo尝试的小伙伴直接复制,节省时间。

platform :ios, '14.0'
source 'https://cdn.cocoapods.org/'

use_frameworks!
#use_modular_headers!

# 忽略引入库的所有警告
inhibit_all_warnings!

# [!] Could not automatically select an Xcode project. Specify one in your Podfile like so:
# project 'path/to/Project.xcodeproj'

# [!] `xcodeproj` was renamed to `project`. Please update your Podfile accordingly.

xcodeproj 'flutter_mixed_ios.xcodeproj'

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

target 'flutter_mixed_ios' do
    
    install_all_flutter_pods(flutter_application_path)
    
    ####
    pod 'AFNetworking'
    pod 'WoodPeckeriOS'

end

 

      3:接下来就是 pod install 

      4:关于原生项目的配置更改以及问题解释

       <1> Flutter混编项目是不支持Bitcode的,具体Bitcode代表的是什么,这个大家可以翻以前我的文章: 

 

      <2> Build Phases 添加 Script 具体的操作如下所示:

 

       添加下面内容:

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed 

      注意:单纯这样添加之后编译大概率是不通过的,主要问题就是集中的 FLUTTER_ROOT 这个点上

/packages/flutter_tools/bin/xcode_backend.sh: No such file or directory

      我们就把注意力放在 No such file or directory 上,别走别的岔路。解决上面这问题的方法就是在你的项目中指定一下FLUTTER_ROOT的具体路径,让不再No such file or directory就OK了

      具体的做法是在 Build Settings中找到 User-Defined 添加 FLUTTER_ROOT 和 FLUTTER_APPLICATION_PATH

      FLUTTER_ROOT是我Flutter环境所在的具体位置 FLUTTER_APPLICATION_PATH 是我这个Flutter项目所在的路径,当然我们还有更靠谱的查找这两路径位方法。

      终端进入自己flutter项目,按照下面路径/.ios(隐藏文件)/Flutter/Generated.xcconfig  我们open Generated.xcconfig 文件就在最上面就可以看到我们需要的FLUTTER_ROOT 和 FLUTTER_APPLICATION_PATH 。

 

      经过上面的处理,我们的 No such file or directory的问题就解决了,最后我们说说 xcode_backend.sh ,其实关于它我想表达的就只有一点,就这个脚本的作用到底有哪些,他能帮我们完成什么工作呢?

      前面的疑问,这篇文章给出了具体的分析 #Flutter之ios脚本 xcode-backend.sh文件分析#,文章逐句分析了我们xcode_backend.sh脚本的代码,也就间接的阐述了它的作用。有兴趣的朋友可以好好了解一下。

      经过脚本的处理,有这样一种场景,我们在开发的时候假如修改了一些涉及到混编消息传递的内容(任何Flutter内容都可以),我们在Flutter这边修改了代码,这时候你可以直接运行Xcode查看更改的内容是否正确,里面具体的工作我们在运行Xcode它在执行xcode_backend.sh脚本的时候已经帮我们处理了。当然正常Flutter修改的内容我们运行Flutter项目查看,在原生传递消息给Flutter的时候需要我们运行iOS项目,就打个上面的比方,理解知道就可以了。

      至此,你的iOS和Flutter混编的代码是可以正常运行起来的了。 

      5:Local Network Privacy Permissions

      这个问题我们在查看Flutter官方文档进行学习的时候肯定是可以看到的。  官方解释传送门

      在你运行混编iOS项目的时候,你不处理这个问题就可以看到下面内容的日志:

      Failed to register observatory port with mDNS with error -65555. On iOS 14+, local network broadcast in apps need to be declared in the app's Info.plist. Debug and profile Flutter apps and modules host VM services on the local network to support debugging features such as hot reload and DevTools. To make your Flutter app or module attachable and debuggable, add a '_dartobservatory._tcp' value to the 'NSBonjourServices' key in your Info.plist for the Debug/Profile configurations.

      官方也给我们做出了提醒以及解释说明:

    【 在iOS14和更高版本,在你的应用程序的调试版本中启用Dart组播DNS服务,以添加调试功能,如热加载和DevTools via flutter attach。注意:该服务不能在你的应用的Release版本中启用,否则你可能会遇到应用商店拒绝。一种方法是维护应用程序信息的一个单独副本。每个构建配置的Plist。下面的说明假设默认的Debug和Release。根据应用程序的构建配置,根据需要调整名称 】

      我自己还是按照官方给的的处理方法处理的

      首先还是处理我们的plist文件,把它处理成debug和release两个模式的,我们一旦改了它们。在 build settings 中一定要改动,不然编译肯定过不了的!具体的操作如下图:

                         

      Build Settings Info.plist 这里我们添加的是 Info-$(CONFIGURATION).plist Debug和Release环境的让它们根据自己的配置内容读取。

      接下来就是在Debug环境中的配置问题,这里主要有两点:

      1、Privacy - Local Network Usage Description 填写的 Allow Flutter tools on your computer to connect and debug your application.This prompt will not appear on release builds. ,当然我是写demo随便写的,具体的在自己项目中需要自己填写,这个权限在iOS14之后审核比较严格,大家需要注意,要使用到得描述清楚,避免审核被拒绝,但具体的苹果什么加强这块的审核,我们大致了解下这个权限的用处就理解了。

    【 因为在过去的 iOS 版本中,应用可以随意扫描本地网络中的设备,因此应用就可以很轻松地得到本地网络里所有设备的名称和MAC地址。MAC地址是一种确认网络设备位置的地址,每个网卡都有一个唯一的MAC地址,加上MAC地址也具有唯一性,设备厂商会按照一定的规律分配MAC,所以不同的局域网都是独一无二可以识别的。这样就通过MAC地址和设备的名字以生成一个特定的「指纹」,持续地、跨应用地、跨设备地跟踪用户的行为,并对用户画像持续进行调整。就大部分应用而言,它们都不需要给本地网络权限。因为它们没有功能会使用到本地网络,请求这个权限的主要目的就是为了跟踪用户并推送广告。】

      2、Bonjour services 填写的 _dartobservatory._tcp

      最后还剩一点就是把Copy Bundle当中的Info-Release.plist进行一个删除。下面图片中的内容我是已经删除了的: 

       经过上面的处理之后,Local Network Privacy Permissions 这个问题我们就应该是解决了!

       

二:原生与Flutter通信


     

     首先Flutter为我们提供了以下几种原生和Flutter之间通信的方式:

  •  FlutterBasicMessageChannel 双向通道,iOS和Flutter都可以主动向对方传递消息,最简单的传递数据方式。
  •  FlutterMethodChannel 也是双向通信,它的使用和FlutterBasicMessageChannel基本上一致,不同的点在于FlutterMethodChannel可以自定义Channel的name。
  • FlutterEventChannel 用于事件流的发送(event streams), 属于持续性的单向通信, 只能是iOS端主动调用, 常用于传递原生设备的信息, 状态等, 比如电池电量, 远程通知, 网络状态变化, 手机方向, 重力感应, 定位位置变化等等。

     具体的它们三者的使用我们就不在很具体的说了,我们就从FlutterMethodChannel这个方法入手,简单的看一下Flutter给iOS发送消息以及iOS给Flutter发送消息时候具体的代码执行是什么样子的,具体的过程当中我们又遇到了那些问题,我们也简要的进行一个分析。

     1、Flutter给iOS发送消息

      iOS端的代码,下面代码大致逻辑是iOS端接收到Flutter发送的channel name为MixChannelName.backToNative,消息名称为 MixChannelMethod.iOSBack,执行返回上个控制器。

/// MixFlutterViewController 继承与 FlutterViewController
extension MixFlutterViewController{
    
    /// 返回事件
    func channelBack() {
        /// MixChannelName.backToNative 字符串channel name
        self.flutterMethodChannel = MixFlutterMethodChannel.init(name: MixChannelName.backToNative, binaryMessenger: self.engine!.binaryMessenger)
        self.flutterMethodChannel!.setMethodCallHandler { [weak self] (call:FlutterMethodCall,result:@escaping FlutterResult) in
            /// 返回上一个页面
            /// MixChannelMethod.iOSBack 字符串返回方法名称
            if call.method == MixChannelMethod.iOSBack{
                
                self?.navigationController?.popViewController(animated: true)
                self?.flutterMethodChannel = nil
            } else {
                
                result(FlutterMethodNotImplemented)
            }
        }
    }
}

class MixFlutterMethodChannel: FlutterMethodChannel {
    
    deinit {
        debugPrint("MixFlutterMethodChannel - deinit")
    }
}

      我们再看看Flutter端的发送代码是怎么处理的:

///  前面定义一个MethodChannel 名称为flutter_backToNative 和iOS端的需要保持一致
static const _messageChannel = MethodChannel("flutter_backToNative");

/// 然后在你需要发送消息的地方调用
_messageChannel.invokeMethod("backToNative");

      经过上面的处理之后,我们的iOS端是能够正接受到Flutter发送的消息的。

      2、iOS给Flutter发送消息

      Flutter端的代码,还是之前的_messageChannel这个渠道,直接调用setMethodCallHandler设置接收到消息的处理函数。

/// 建立和原生通讯的渠道
_messageChannel.setMethodCallHandler((call) => handleMessage(call.arguments));

/// 处理消息的方法
Future handleMessage(String message) async {

    print(message);
}

      iOS端的代码如下,flutterMethodChannel还是我们刚开始创建的渠道

/// 发送普通消息
/// - Parameter stringParams: stringParams description
func sendMessageWithString(_ stringParams:String){
      /// MixChannelMethod.goodsId 调用的方法名称
      self.flutterMethodChannel!.invokeMethod(MixChannelMethod.goodsId, arguments: stringParams)
}

      注意点: 在使用FlutterMethodChannel进行双向通信的时候,尤其需要注意的是iOS端和Flutter端的渠道Channel的name一定要保持一致!

      疑惑点:我在MixFlutterViewController的deinit方法中加入了日志,然后综合上面的MixFlutterMethodChannel中deinit的日志,得出一个有点不理解的点,主要疑问如下面所示是在flutterMethodChannel的创建方式上。

         /*
         "MixFlutterMethodChannel - deinit"
         2022-05-08 22:28:26.159278+0800 flutter_mixed_ios[70375:6110936] flutter: 10086
         "MixFlutterViewController - deinit"
         "MixFlutterMethodChannel - deinit"
         2022-05-08 22:28:35.960283+0800 flutter_mixed_ios[70375:6110936] flutter: 10086
         "MixFlutterMethodChannel - deinit"
         "MixFlutterViewController - deinit"
         "MixFlutterMethodChannel - deinit"
         */
         /// 使用该方法创建后 在Flutter发送消息返回 打印日志如上面注释
         self.flutterMethodChannel = MixFlutterMethodChannel.init(name: MixChannelName.backToNative, binaryMessenger: self.engine!.binaryMessenger,codec: FlutterStandardMethodCodec.sharedInstance())
        
        
        /*
         2022-05-08 22:31:21.842965+0800 flutter_mixed_ios[70389:6112382] flutter: 10086
         "MixFlutterViewController - deinit"
         */
        /// 使用该方法创建后 在Flutter发送消息返回 打印日志如上面注释
        self.flutterMethodChannel = MixFlutterMethodChannel.init(name: MixChannelName.backToNative, binaryMessenger: self.engine!.binaryMessenger)

 

       总结:经过上面的内容,关于iOS和Flutter的混编的一些东西就都介绍完毕了,疑问点还是存在,等后面找到具体的答案之后我会补充在文章后面。要是对上面内容有什么疑问,可以留言或者私信我,可以换个方式具体的沟通。

 

最后:编写本文章时候的Flutter版本:

---------------------------------------------------------------

➜  mixedflutter flutter --version

Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git

Framework • revision 5464c5bac7 (3 weeks ago) • 2022-04-18 09:55:37 -0700

Engine • revision 57d3bac3dd

Tools • Dart 2.16.2 • DevTools 2.9.2

---------------------------------------------------------------

Demo地址:

iOS项目地址:https://gitee.com/MrRisingSun/iOSMixedFlutter

Flutter项目地址:https://gitee.com/MrRisingSun/mixed-flutter

---------------------------------------------------------------

 
 
 
posted @ 2022-05-08 22:42  MrRisingSun  阅读(1952)  评论(0编辑  收藏  举报