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页面,下一步我会开发一个简单的快速启动框架。


目录
相关文章
|
8天前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
43 4
|
9天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
8天前
|
传感器 前端开发 Android开发
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求。本文深入探讨了插件开发的基本概念、流程、集成方法、常见类型及开发实例,如相机插件的开发步骤,同时强调了版本兼容性、性能优化等注意事项,并展望了插件开发的未来趋势。
21 2
|
17天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
37 9
|
13天前
|
设计模式 Swift iOS开发
探索iOS开发:从基础到高级,打造你的第一款App
【10月更文挑战第40天】在这个数字时代,掌握移动应用开发已成为许多技术爱好者的梦想。本文将带你走进iOS开发的世界,从最基础的概念出发,逐步深入到高级功能实现,最终指导你完成自己的第一款App。无论你是编程新手还是有志于扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。让我们一起开始这段旅程吧!
|
16天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
14天前
|
iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第39天】在苹果的生态系统中,SwiftUI框架以其声明式语法和易用性成为开发者的新宠。本文将深入SwiftUI的核心概念,通过实际案例展示如何利用这一框架快速构建用户界面,并探讨其对iOS应用开发流程的影响。
|
17天前
|
JSON 前端开发 API
探索iOS开发之旅:打造你的第一个天气应用
【10月更文挑战第36天】在这篇文章中,我们将踏上一段激动人心的旅程,一起构建属于我们自己的iOS天气应用。通过这个实战项目,你将学习到如何从零开始搭建一个iOS应用,掌握基本的用户界面设计、网络请求处理以及数据解析等核心技能。无论你是编程新手还是希望扩展你的iOS开发技能,这个项目都将为你提供宝贵的实践经验。准备好了吗?让我们开始吧!
|
20天前
|
存储 数据可视化 Swift
探索iOS开发之旅:从新手到专家
【10月更文挑战第33天】在这篇文章中,我们将一起踏上一场激动人心的iOS开发之旅。无论你是刚刚入门的新手,还是已经有一定经验的开发者,这篇文章都将为你提供宝贵的知识和技能。我们将从基础的iOS开发概念开始,逐步深入到更复杂的主题,如用户界面设计、数据存储和网络编程等。通过阅读这篇文章,你将获得成为一名优秀iOS开发者所需的全面技能和知识。让我们一起开始吧!