Flutter混合開(kāi)發(fā):在已有iOS項(xiàng)目中引入Flutter

一、前言:

目前混合開(kāi)發(fā)屬于主流,因?yàn)槎鄶?shù)都在原來(lái)的項(xiàng)目上集成Flutter模塊,除非新的項(xiàng)目用純Flutter;
想要在已有的原生 App 里嵌入一些 Flutter 頁(yè)面,有兩個(gè)辦法:

  • 將原生工程作為 Flutter 工程的子工程,由 Flutter 統(tǒng)一管理。這種模式,就是統(tǒng)一管理模式。
  • 將 Flutter 工程作為原生工程共用的子模塊,維持原有的原生工程管理方式不變。這種模式,就是三端分離模式。


    image.png

    三端代碼分離的模式來(lái)進(jìn)行依賴治理,實(shí)現(xiàn)了 Flutter 工程的輕量級(jí)接入,三端代碼分離模式把 Flutter 模塊作為原生工程的子模塊,還可以快速實(shí)現(xiàn) Flutter 功能的“熱插拔”,降低原生工程的改造成本。而 Flutter 工程通過(guò) Android Studio 進(jìn)行管理,無(wú)需打開(kāi)原生工程,可直接進(jìn)行 Dart 代碼和原生代碼的開(kāi)發(fā)調(diào)試;
    三端工程分離模式的關(guān)鍵是抽離 Flutter 工程,將不同平臺(tái)的構(gòu)建產(chǎn)物依照標(biāo)準(zhǔn)組件化的形式進(jìn)行管理,即 Android 使用 aar、iOS 使用 pod。換句話說(shuō),接下來(lái)介紹的混編方案會(huì)將 Flutter 模塊打包成 aar 和 pod,這樣原生工程就可以像引用其他第三方原生組件庫(kù)那樣快速接入 Flutter 了

二、集成(以iOS為例),使用Pods方式

官方給出了三種接入方案,這三種方案各有優(yōu)缺點(diǎn),我們先簡(jiǎn)單看看這三種方案:

  • 使用 CocoaPods 和 Flutter SDK 集成:ios項(xiàng)目中用CocoaPods直接接入管理flutter module。這種方案需要所有開(kāi)發(fā)人員都配置flutter環(huán)境,且安裝CocoaPods;優(yōu)點(diǎn)是通過(guò)CocoaPods自動(dòng)集成,配置簡(jiǎn)單。
  • 在 Xcode 中集成 frameworks:將flutter module先build成FrameWork文件,然后在ios項(xiàng)目中引入文件。這種方案的優(yōu)點(diǎn)是ios開(kāi)發(fā)人員不需要flutter環(huán)境,且項(xiàng)目不需要安裝CocoaPods;缺點(diǎn)是每次修改都需要重新build,重新導(dǎo)入。
  • 通過(guò)CocoaPods打包Framework:與2類似,只不過(guò)在build時(shí)加入--cocoapods參數(shù):flutter build ios-framework --cocoapods --xcframework --no-universal --output=some/path/MyApp/Flutter/。打包出來(lái)的是Flutter.podspec 文件,ios項(xiàng)目中通過(guò)CocoaPods管理集成。這個(gè)方案的與2方案差不多,缺點(diǎn)也是每次改動(dòng)需要重新build,優(yōu)點(diǎn)是ios開(kāi)發(fā)人員不需要flutter環(huán)境。
    所以要根據(jù)自身的情況來(lái)選擇符合自己的方案。官方推薦第一種方案,我也先嘗試了第一個(gè)方案

若新建pod工程,執(zhí)行pod init可能遇到報(bào)錯(cuò)

[報(bào)錯(cuò) /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.3/lib/cocoapods/user_interface/error_report.rb:34:in `force_encoding': can't modify frozen String (FrozenError)](https://www.cnblogs.com/ZhangShengjie/p/17902473.html)

解決方案修改如下


image.png

在原生工程中,需要在同級(jí)目錄創(chuàng)建 Flutter 模塊,構(gòu)建 iOS 的 Flutter 依賴庫(kù),Flutter 提供了這樣的命令, 在原生項(xiàng)目的同級(jí)目錄下,執(zhí)行 Flutter 命令創(chuàng)建名為 flutter_library 的模塊即可

flutter create -t module flutter_library

這里的 Flutter 模塊,也是 Flutter 工程,我們用 Android Studio或vs code 打開(kāi)它,其目錄如下圖所示:


image.png

打開(kāi) main.dart 文件,將其邏輯更新為以下代碼邏輯,即一個(gè)寫(xiě)著“Hello from Flutter”的全屏紅色的 Flutter Widget:

import 'package:flutter/material.dart';
import 'dart:ui';

void main() => runApp(_widgetForRoute(window.defaultRouteName));//獨(dú)立運(yùn)行傳入默認(rèn)路由

Widget _widgetForRoute(String route) {
  switch (route) {
    default:
      return MaterialApp(
        home: Scaffold(
          backgroundColor: const Color(0xFFD63031),//ARGB紅色
          body: Center(
            child: Text(
              'Hello from Flutter', //顯示的文字
              textDirection: TextDirection.ltr,
              style: TextStyle(
                fontSize: 20.0,
                color: Colors.blue,
              ),
            ),
          ),
        ),
      );
  }
}

接下來(lái),我們要做的事情就是把這段代碼編譯打包,構(gòu)建出對(duì)應(yīng)的 Android 和 iOS 依賴庫(kù),實(shí)現(xiàn)原生工程的接入;

原生項(xiàng)目打開(kāi)Podfile,加入Flutter,如下

// my_flutter 是創(chuàng)建Flutter的模塊名稱
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

platform :ios, '9.0' 
target 'NativeIOS' do
  use_frameworks!
  /// 這邊引入
  install_all_flutter_pods(flutter_application_path)
  /// 其他
  ...
end

如果flutter sdk使用的是最新3.x版本,執(zhí)行pod update會(huì)報(bào)錯(cuò)如下


image.png

解決辦法:
在 podfile文件最后添加

post_install do |installer|
  flutter_post_install(installer) if defined?(flutter_post_install)
end

在 iOS 平臺(tái),原生工程對(duì) Flutter 的依賴分別是:

  • Flutter 庫(kù)和引擎,即 Flutter.framework;
  • Flutter 工程的產(chǎn)物,即 App.framework

iOS 平臺(tái)的 Flutter 模塊抽取,實(shí)際上就是通過(guò)打包命令生成這兩個(gè)產(chǎn)物,并將它們封裝成一個(gè) pod 供原生工程引用

在 Flutter_library 的根目錄下,執(zhí)行 iOS 打包構(gòu)建命令

Flutter build ios --debug

這條命令的作用是編譯 Flutter 工程生成兩個(gè)產(chǎn)物:Flutter.framework 和 App.framework
這里就會(huì)出現(xiàn)簽名問(wèn)題。執(zhí)行上面命令后會(huì)報(bào)錯(cuò)

No valid code signing certificates were found

You can connect to your Apple Developer account by signing in with your Apple ID

in Xcode and create an iOS Development Certificate as well as a Provisioning

Profile for your project by:

1- Open the Flutter project's Xcode target with

   open iOS/Runner.xcworkspace
2- Select the 'Runner' project in the navigator then the 'Runner' target

 in the project settings
3- Make sure a 'Development Team' is selected. - For Xcode 10, look under General > Signing > Team. - For Xcode 11 and newer, look under Signing & Capabilities > Team.

 You may need to:

     - Log in with your Apple ID in Xcode first
     - Ensure you have a valid unique Bundle ID
     - Register your device with your Apple Developer Account
     - Let Xcode automatically provision a profile for your app
4- Build or run your project again

5- Trust your newly created Development Certificate on your iOS device

 via Settings > General > Device Management > [your new certificate] > Trust
For more information, please visit: https://developer.apple.com/library/content/documentation/IDEs/Conceptual/ AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html

Or run on an iOS simulator without code signing

可以在build的時(shí)候選擇不簽名,命令如下:

flutter build ios --no-codesign

然后在原生項(xiàng)目下 執(zhí)行 pod install 如果以上不報(bào)錯(cuò),混合開(kāi)發(fā)模式到這里就集成完了
然后編譯工程報(bào)錯(cuò)如下:

iOS Xcode 15 Sandbox: rsync(xxxx) deny(1) file-write-create
image.png

解決方案:

設(shè)置里面搜索user 把User Script Sanboxing 改為NO

image.png

然后修改原生代碼,啟動(dòng) FlutterEngineFlutterViewController

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
     [self.flutterEngine run];
     [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
     return YES;
}
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    
    FlutterViewController *flutterViewController =
             [[FlutterViewController alloc] initWithEngine:appDelegate.flutterEngine nibName:nil bundle:nil];
    flutterViewController.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:flutterViewController animated:YES completion:nil];
}

注:確保引擎啟動(dòng)完畢后,再調(diào)用FlutterViewController,否則flutter頁(yè)面展示失??;

最后點(diǎn)擊運(yùn)行,一個(gè)寫(xiě)著“Hello from Flutter”的全屏紅色的 Flutter Widget 也展示出來(lái)了。至此,iOS 工程的接入我們也順利搞定了


image.png

參考文檔

https://cloud.tencent.com/developer/article/1947233

https://time.geekbang.org/column/article/129754

https://blog.51cto.com/u_16213580/7532307

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容