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


目录
相关文章
|
2天前
|
Java 开发工具 Android开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着半壁江山。本文将深入探讨这两个平台在开发过程中的关键差异点,包括编程语言、开发工具、用户界面设计、性能优化以及市场覆盖等方面。通过对这些关键因素的比较分析,旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和目标受众做出明智的平台选择。
|
2天前
|
编解码 Android开发 iOS开发
深入探索Android与iOS开发的差异与挑战
【6月更文挑战第24天】在移动应用开发的广阔舞台上,Android和iOS两大操作系统扮演着主角。它们各自拥有独特的开发环境、工具集、用户基础及市场策略。本文将深度剖析这两个平台的开发差异,并探讨开发者面临的挑战,旨在为即将踏入或已在移动开发领域奋斗的开发者提供一份实用指南。
22 13
|
5天前
|
iOS开发 开发者 容器
探索iOS开发中的SwiftUI框架
【6月更文挑战第21天】本文深入探讨了苹果在iOS开发中推出的SwiftUI框架,旨在为开发者提供一种声明式、更简洁的界面设计方法。文章首先概述了SwiftUI的核心概念和优势,接着通过一个天气预报应用实例,详细讲解了如何使用SwiftUI进行布局和用户界面的设计。此外,还讨论了SwiftUI与UIKit的差异,以及如何将SwiftUI集成到现有的项目中。最后,文章展望了SwiftUI的未来发展方向,包括潜在的改进和新特性。
|
1天前
|
监控 Android开发 iOS开发
探索Android与iOS开发的差异:平台、工具和用户体验的比较
【6月更文挑战第25天】在移动应用开发的广阔天地中,Android和iOS两大平台各领风骚,它们在开发环境、工具选择及用户体验设计上展现出独特的风貌。本文将深入探讨这两个操作系统在技术实现、市场定位和用户交互方面的关键差异,旨在为开发者提供一个全景式的视图,帮助他们在面对项目决策时能够更加明智地选择适合自己项目需求的平台。
|
5天前
|
Java 开发工具 Android开发
安卓与iOS开发差异解析
【6月更文挑战第21天】本文旨在深入探讨安卓和iOS两大移动操作系统在应用开发过程中的主要差异。通过对比分析,揭示各自的设计哲学、编程语言选择、用户界面构建、性能优化策略以及发布流程的异同。文章将提供开发者视角下的实用信息,帮助他们更好地理解各自平台的特点和挑战,从而做出更明智的开发决策。
|
7天前
|
Java 开发工具 Android开发
探索安卓与iOS开发的核心差异
【6月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文将深入探讨这两大操作系统在开发过程中的主要区别,包括编程语言、开发工具、用户界面设计哲学、系统架构以及市场分布等方面。通过对这些关键差异的分析,旨在为开发者提供一份实用的指南,帮助他们在面对项目决策时,能够更加明智地选择合适的平台,并针对特定平台优化他们的应用。
|
7天前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:从工具到用户体验
【6月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计以及性能优化等方面的关键差异。我们将通过比较分析,揭示各自平台的独特优势和面临的挑战,为开发者提供决策参考,并为最终用户提供更深层次的用户体验洞察。
|
9天前
|
Swift iOS开发 开发者
探索iOS开发中的SwiftUI框架
在苹果的生态系统中,SwiftUI代表了iOS应用开发的一次重大飞跃。作为一项现代化的UI工具集,它旨在简化和加速界面设计过程,同时确保代码的清晰度与可维护性。本文将深入探讨SwiftUI的核心概念、优势以及在实际开发中的应用案例,为开发者提供全面而实用的指南。
|
1天前
|
设计模式 IDE Swift
探索iOS开发:从新手到专家的旅程
【6月更文挑战第25天】在数字时代的浪潮中,iOS开发作为一门艺术和科学的结合体,吸引了众多开发者的目光。本文将带领读者踏上一场精彩的旅程,从基础的搭建环境开始,逐步深入到高级编程技巧,再到应用发布与市场策略,全方位解读iOS开发的魅力所在。通过实际案例分析,我们将揭示那些让应用脱颖而出的秘密,以及如何在竞争激烈的应用市场中保持竞争力。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用的技巧,让你的iOS开发之旅更加顺畅。
|
6天前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的对比分析
移动应用开发的两大阵营——安卓与iOS,各自拥有独特的开发环境、用户群体和市场定位。本文将深入探讨这两个操作系统在应用开发过程中的主要差异,包括编程语言、开发工具、用户界面设计、性能优化、安全性考量以及发布流程等方面。通过比较分析,旨在为开发者提供跨平台开发的见解和策略,以优化应用性能和提升用户体验。
10 0