Flutter混合开发:在已有iOS项目中引入Flutter(下)

简介: 在android项目中添加flutter模块比较简单,因为毕竟都是google的,但是在ios中添加flutter模块有些麻烦了,我们首先参考的是官方文档flutter.cn/docs/develo…但是在实际过程中会遇到各种问题(当然我本身对ios开发不熟悉也造成了不小的困扰),这里结合官方的步骤和我的经验来说说整个接入过程和遇到的坑。我的环境是Android Studio 4.0.1 + Xcode12.4 + flutter2.0.5 (mac是M1芯片,后面会提到它的影响)

在 Xcode 中集成 frameworks


因为官方推荐的第一种方案未测试通过,且根据我们的情况,第二种方案更加贴合一些,所以我没有在第一种方案上继续纠结研究,转而使用第二种方案。

第二种方案不需要CocoaPods,首先编译打包flutter module:


flutter build ios-framework --xcframework --no-universal --output=./Flutter/


会在flutter module目录下生成一个Flutter目录,里面产出编译后的framework,如下:


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


我们可以将这个Flutter目录拷贝到ios项目下,然后在ios项目的Build Phases下的Link Binary With Libraries下添加framework,直接将Flutter.xcframework和App.xcframework等文件(注意:这里官方上使用的是release目录下的,但是我先使用的是Debug目录下的文件,后续会解释这里,先记录一下)拖拽进去即可,如下:


网络异常,图片无法展示
|


注意:这一步官网上还在Build Settings -> Framework Search Paths (FRAMEWORK_SEARCH_PATHS) 中增加 $(PROJECT_DIR)/Flutter/Release/。但是这个应该是与上面添加framework文件效果是一样的。我只做了上面添加文件,没有设置这个运行是没有问题的。不知道如果同时设置会不会出现什么问题。


然后需要将framework内嵌(embed)到项目,在项目的General下的FrameWorks, Libraries, and Embedded Content下,将刚才加入的framework改成Embed & Sign,如下:


网络异常,图片无法展示
|

然后⌘+B 编译项目即可。 这个过程还算顺利,没有出现什么问题。


ios中启动flutter页面


参考官方教程:flutter.cn/docs/develo…

先是修改AppDelegate文件,修改成:


import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
    lazy var flutterEngine = FlutterEngine(name: "flutter engine")
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        flutterEngine.run()
        return true
    }
    // MARK: UISceneSession Lifecycle
    override func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    override func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }
}
复制代码


然后修改ViewController文件:


import UIKit
import Flutter
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let button = UIButton(type:UIButton.ButtonType.custom)
        button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
        button.setTitle("Show Flutter!", for: UIControl.State.normal)
        button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
        button.backgroundColor = UIColor.blue
        self.view.addSubview(button)
    }
    @objc func showFlutter() {
        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
        let flutterViewController =
            FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        present(flutterViewController, animated: true, completion: nil)
      }
}
复制代码


然后运行即可。

就这样?显然不可能,下面说说我遇到的几个问题:


编译失败 building for iOS Simulator-arm64 but attempting to link with file built for iOS Simulator-x86_64


报错如下:

ld: warning: ignoring file xxx/Build/Products/Debug-iphonesimulator/App.framework/App, building for iOS Simulator-arm64 but attempting to link with file built for iOS Simulator-x86_64

ld: warning: ignoring file xxx/Build/Products/Debug-iphonesimulator/Flutter.framework/Flutter, building for iOS Simulator-arm64 but attempting to link with file built for iOS Simulator-x86_64

Undefined symbols for architecture arm64:

"OBJC_CLASS$_FlutterAppDelegate", referenced from:


type metadata for iostest2.AppDelegate in AppDelegate.o
复制代码


"OBJC_CLASS$_FlutterEngine", referenced from:


objc-class-ref in AppDelegate.o
复制代码


"OBJC_METACLASS$_FlutterAppDelegate", referenced from:


_OBJC_METACLASS_$__TtC8iostest211AppDelegate in AppDelegate.o
复制代码


"OBJC_CLASS$_FlutterViewController", referenced from:


objc-class-ref in ViewController.o
复制代码


ld: symbol(s) not found for architecture arm64

clang: error: linker command failed with exit code 1 (use -v to see invocation)


很明显是cpu架构的问题,但是为什么会出现这样的问题?我们看之前生成的flutter framework文件,拿Debug目录下的App.xcframework为例,这个目录下的文件如下:


网络异常,图片无法展示
|


可以看到在simulator(模拟器)上是x86_64的,而在真机上则是arm64_armv7的。从上面报错日志上看,程序是想找arm64下的文件,但是我们是打算运行到模拟器上的,所以找不到了文件。


这个问题官网上flutter.cn/docs/develo… 的最后也提到了,解决方法是在项目的Build Settings -> Archiectures -> Excluded Archiectures下将simulator都设置arm64即可,如下:


网络异常,图片无法展示
|


鼠标移到Debug上,后面会出现+号,点击就会在下面添加一条。

然后在新添加的左侧选择Any iOS Simulator SDK,双击右侧就会弹窗,在弹窗中添加一条arm64即可。

同样在Release下也操作一下,最后完成效果如上图。

这样设置后在模拟器上编译运行时就会排除arm64。再进行编译即可通过。


运行后提示Engine run configuration was invalid. Could not launch engine with configuration.


运行后,在日志区域显示如下日志:

Engine run configuration was invalid.

Could not launch engine with configuration.


点击按钮无法正常显示flutter页面。

根据网上一个大神的解释,这是因为物料出问题了(如果你上面按照我的提示做的就不会出现这个问题)。


原因是运行的是debug,但是flutter framework的物料是release的。

上面接入的时候提到过,这里官网上是引入Release目录下的文件,但是我先引用的是Debug目录下的,就是因为这个问题。但是如果已经按照官网引入release物料,就会出现上面的问题,这时候先清理一下项目

Product -> Clean Build Folder


然后在General下的FrameWorks, Libraries, and Embedded Content下将之前引入的文件移除掉,再重新引入Debug目录下的文件即可。再运行就可以正常展示flutter了。

当然,如果要运行release,则需要再执行上面的操作替换一下文件。这也是这种方案的最大弊端。


启动不同的flutter页面


上面我们只是启动flutter默认主页,可以看到在app启动时就将flutter engine启动起来,这样当我们点击按钮启动页面的时候,flutter页面很快就打开了。

当时如果启动不同的flutter页面怎么办?比如有两个按钮,分别启动flutter的主页面和second页面。参考官方文档,可以使用隐式flutter engine来启动,将ViewController的代码修改如下:


import UIKit
import Flutter
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let button1 = UIButton(type:UIButton.ButtonType.custom)
        button1.addTarget(self, action: #selector(showMain), for: .touchUpInside)
        button1.setTitle("show main!", for: UIControl.State.normal)
        button1.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
        button1.backgroundColor = UIColor.blue
        self.view.addSubview(button1)
        let button2 = UIButton(type:UIButton.ButtonType.custom)
        button2.addTarget(self, action: #selector(showSecond), for: .touchUpInside)
        button2.setTitle("show second!", for: UIControl.State.normal)
        button2.frame = CGRect(x: 80.0, y: 310.0, width: 160.0, height: 40.0)
        button2.backgroundColor = UIColor.blue
        self.view.addSubview(button2)
    }
    @objc func showMain() {
        let flutterViewController = FlutterViewController(project: nil, initialRoute: "/", nibName: nil, bundle: nil);
        present(flutterViewController, animated: true, completion: nil)
      }
    @objc func showSecond() {
        let flutterViewController = FlutterViewController(project: nil, initialRoute: "second", nibName: nil, bundle: nil);
        present(flutterViewController, animated: true, completion: nil)
      }
}
复制代码


这样就可以启动不同的页面,但是可以发现我们没有用到之前在AppDelegate创建的flutterEngine,因为创建FlutterViewController时都会隐式的创建新的flutterEngine,这也导致了一个问题,每次启动页面都需要等待一段时间。

我们可以预先创建两个flutterEngine,AppDelegate代码修改如下:


import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
    lazy var flutterEngine1 = FlutterEngine(name: "main")
    lazy var flutterEngine2 = FlutterEngine(name: "second")
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        flutterEngine1.run(withEntrypoint: "main", initialRoute: "/")
        flutterEngine2.run(withEntrypoint: "main", initialRoute: "second")
        return true
    }
    // MARK: UISceneSession Lifecycle
    override func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    override func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }
}
复制代码


然后修改ViewController的代码如下:


import UIKit
import Flutter
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let button1 = UIButton(type:UIButton.ButtonType.custom)
        button1.addTarget(self, action: #selector(showMain), for: .touchUpInside)
        button1.setTitle("show main!", for: UIControl.State.normal)
        button1.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
        button1.backgroundColor = UIColor.blue
        self.view.addSubview(button1)
        let button2 = UIButton(type:UIButton.ButtonType.custom)
        button2.addTarget(self, action: #selector(showSecond), for: .touchUpInside)
        button2.setTitle("show second!", for: UIControl.State.normal)
        button2.frame = CGRect(x: 80.0, y: 310.0, width: 160.0, height: 40.0)
        button2.backgroundColor = UIColor.blue
        self.view.addSubview(button2)
    }
    @objc func showMain() {
        let flutterEngine1 = (UIApplication.shared.delegate as! AppDelegate).flutterEngine1
        let flutterViewController =
            FlutterViewController(engine: flutterEngine1, nibName: nil, bundle: nil)
        present(flutterViewController, animated: true, completion: nil)
      }
    @objc func showSecond() {
        let flutterEngine2 = (UIApplication.shared.delegate as! AppDelegate).flutterEngine2
        let flutterViewController =
            FlutterViewController(engine: flutterEngine2, nibName: nil, bundle: nil)
        present(flutterViewController, animated: true, completion: nil)
      }
}
复制代码


这样再启动页面就会瞬间打开了,因为flutterEngine已经提前启动起来了。

#####Undefined symbol: _FlutterDefaultDartEntrypoint

过程中出现过一个问题,一开始启动flutterEngine的代码是根据官网上的写法如下:


flutterEngine.run(withEntrypoint: FlutterDefaultDartEntrypoint, initialRoute: FlutterDefaultInitialRoute)
复制代码


这样可以启动flutter的默认页面。但是编译报错:

Undefined symbol: _FlutterDefaultDartEntrypoint

Undefined symbol: _FlutterDefaultInitialRoute


在FlutterEngine.h源码下可以看到对应的变量,但是通过在Debug/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework下的Flutter文件(C文件生成的二进制文件)中搜索发现并没有这两个字段,说明在C文件中并没有定义这两个字段。


目前还不确定是不是flutter编译导致的问题。但是我们可以解决这个问题,首先

FlutterDefaultInitialRoute就是默认路径,其实就是"/"。而FlutterDefaultDartEntrypoint就是默认入口,就是flutter中的main函数,所以就是"main"。所以在上面代码中我直接使用了这两个字符串来代替这两个字段。


总结


所以我们现在面临着与Android同样的困境,需要解决两个问题:

1、不支持传参数

2、每一个页面都需要一个flutterEngine,所以每加一个flutter页面就需要在ios代码中新增一个flutterEngine

所以我们一样需要用一个类似闲鱼flutter-boost原理的框架来管理flutter页面,下一步我会开发一个简单的快速启动框架。


目录
相关文章
|
26天前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
105 4
|
27天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
4天前
|
iOS开发 开发者 MacOS
深入探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】 本文将带领读者深入了解Apple最新推出的SwiftUI框架,这一革命性的用户界面构建工具为iOS开发者提供了一种声明式、高效且直观的方式来创建复杂的用户界面。通过分析SwiftUI的核心概念、主要特性以及在实际项目中的应用示例,我们将展示如何利用SwiftUI简化UI代码,提高开发效率,并保持应用程序的高性能和响应性。无论你是iOS开发的新手还是有经验的开发者,本文都将为你提供宝贵的见解和实用的指导。
85 66
|
14天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
18天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
20天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
26天前
|
开发框架 Dart 前端开发
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。本文从 Flutter 简介、特点、开发环境搭建、应用架构、组件详解、路由管理、状态管理、与原生代码交互、性能优化、应用发布与部署及未来趋势等方面,全面解析 Flutter 技术,助你掌握这一前沿开发工具。
56 8
|
23天前
|
安全 IDE Swift
探索iOS开发之旅:从初学者到专家
在这篇文章中,我们将一起踏上iOS开发的旅程,从基础概念的理解到深入掌握核心技术。无论你是编程新手还是希望提升技能的开发者,这里都有你需要的指南和启示。我们将通过实际案例和代码示例,展示如何构建一个功能齐全的iOS应用。准备好了吗?让我们一起开始吧!
|
26天前
|
存储 JavaScript 前端开发
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战。本文介绍了几种常用的状态管理框架,如Provider和Redux,分析了它们的基本原理、优缺点及适用场景,并提供了选择框架的建议和使用实例,旨在帮助开发者提高开发效率和应用性能。
34 4
|
26天前
|
传感器 前端开发 Android开发
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求。本文深入探讨了插件开发的基本概念、流程、集成方法、常见类型及开发实例,如相机插件的开发步骤,同时强调了版本兼容性、性能优化等注意事项,并展望了插件开发的未来趋势。
39 2