混合開(kāi)發(fā)模式(Add-to-App)

實(shí)現(xiàn) Flutter 模塊嵌入 iOS 原生項(xiàng)目的步驟:

1、在項(xiàng)目根目錄用命令創(chuàng)建 Flutter 模塊:

例如項(xiàng)目目錄這樣子

project/
├── ios_project.xcodeproj
├── ios_project.xcworkspace
├── ios_project/
│   ├── AppDelegate.swift
│   └── ...
├── Podfile
├── Podfile.lock
├── flutter_module/
│   ├── lib/
│   ├── .iOS/
│   └── ...

進(jìn)入到目錄

cd project/
flutter create -t module flutter_module

2、將 Flutter 模塊集成進(jìn) iOS 原生工程

2.1找到pod文件,將Flutter引擎添加到podfile
flutter_application_path = './flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)

示例:

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



target 'ios_project' do
  use_frameworks!
  install_all_flutter_pods(flutter_application_path)
  
  # 其他你的依賴(lài)...
end

post_install do |installer|
  flutter_post_install(installer)
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "12.0"
    end
  end
end

2.2安裝 flutter項(xiàng)目依賴(lài)、pod安裝flutter引擎
cd flutter_module  # 在 Flutter 模塊目錄下
flutter pub get

cd ..  # 回到 iOS 項(xiàng)目目錄
pod install
2.3 iOS項(xiàng)目中添加依賴(lài)及初始化引擎

flutter 官方提供了幾種方式

2.3.1 提前加載引擎預(yù)加載。
import UIKit
import Flutter
import FlutterPluginRegistrant
@main
class AppDelegate: UIResponder, UIApplicationDelegate  {
    lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController =   UINavigationController(rootViewController: ViewController())
        window?.makeKeyAndVisible()
        
        flutterEngine.run();
        // Connects plugins with iOS platform code to this app.
        GeneratedPluginRegistrant.register(with: self.flutterEngine);
        return true
    }
}
 
import UIKit
import Flutter
class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    // Make a button to call the showFlutter function when pressed.
    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)
    }
  }
}

這種情況適用于提前加載,適用于大量頁(yè)面,全局引擎,性能好。

用這種方式關(guān)閉flutter頁(yè)面時(shí)要注意 引擎持有的vc 置nil 的問(wèn)題。不然下次打不開(kāi)了。
可參考下面這種方法:

 ps:   flutter中可用這個(gè)調(diào)用原生 關(guān)閉  下次就無(wú)法打開(kāi)了
         SystemNavigator.pop(animated: true);
當(dāng) Flutter 頁(yè)面通過(guò) SystemNavigator.pop() 關(guān)閉時(shí):
Flutter 會(huì)直接調(diào)用宿主平臺(tái)的 pop 機(jī)制(iOS 上會(huì)觸發(fā) dismiss)。
但此時(shí) UIKit 并不會(huì) 將lutterEngine 持有 的VC 置為 nil

如果你用的 FlutterEngine 是單例的,不會(huì)自動(dòng) detach,下一次就打不開(kāi)頁(yè)面了。
可以這樣*/


//
//  File.swift
//  ios_flutter
//
//  Created by liuhao on 2025/5/16.
//

import Foundation
import Flutter

class SafeFlutterViewController: FlutterViewController {

    private var methodChannel: FlutterMethodChannel?

    override func viewDidLoad() {
        super.viewDidLoad()
            methodChannel = FlutterMethodChannel(name: "com.example.flutter/close",
                                                 binaryMessenger: engine.binaryMessenger)
            methodChannel?.setMethodCallHandler { [weak self] call, result in
                if call.method == "popFromFlutter" {
                    self?.dismiss(animated: true) {
                        self?.detachEngineIfNeeded()
                    }
                    result(nil)
                }
         }
    }

    deinit {
        detachEngineIfNeeded()
    }

    private func detachEngineIfNeeded() {
        if  engine.viewController === self {
            engine.viewController = nil
            print("? FlutterEngine detached.")
        }
    }
}

flutter代碼
import 'package:flutter/services.dart';

class NativeNavigator {
  static const _channel = MethodChannel('com.example.flutter/close');

  static Future<void> close() async {
    await _channel.invokeMethod('popFromFlutter');
  }
}
//封裝一個(gè)專(zhuān)門(mén)用于關(guān)閉的交互

2.3.2 還可以 每次 新的 engine 。
// Existing code omitted.
func showFlutter() {
  let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
  present(flutterViewController, animated: true, completion: nil)
}
 // flutter中可用這個(gè)調(diào)用原生 關(guān)閉
  // SystemNavigator.pop(animated: true);

作為前一個(gè)示例的替代,您可以讓其FlutterViewController隱式創(chuàng)建自己的示例,F(xiàn)lutterEngine而無(wú)需提前預(yù)熱。

通常不建議這樣做,因?yàn)閯?chuàng)建FlutterEngine按需加載可能會(huì)在呈現(xiàn)和渲染第一幀之間引入明顯的延遲FlutterViewController。但是,如果 Flutter 屏幕很少顯示,或者沒(méi)有好的啟發(fā)式方法來(lái)確定何時(shí)啟動(dòng) Dart VM,并且 Flutter 不需要在視圖控制器之間持久化狀態(tài),那么按需加載可能會(huì)很有用。

要讓FlutterViewController不存在的現(xiàn)在FlutterEngine,省略構(gòu)造FlutterEngine,并創(chuàng)建FlutterViewController沒(méi)有引擎引用的。

3、其他

如遇到錯(cuò)誤這個(gè)錯(cuò)誤

截屏2025-05-08 15.57.24.png

可嘗試這樣


image.png

3、將 Flutter 模塊集成進(jìn) Android 原生工程

工程配置

創(chuàng)建module

flutter create -t module --org com.example flutter_module

JAVA版本要求

Flutter 需要使用 JAVA 11 的特性
在嘗試將 Flutter 模塊項(xiàng)目集成到宿主 Android 應(yīng)用之前,請(qǐng)先確保宿主 Android 應(yīng)用的
build.gradle.kts 文件的 android { } 塊中聲明了以下源兼容性。

build.gradle
android {
    // ...
    compileOptions {
        sourceCompatibility = 11 // The minimum value
        targetCompatibility = 11 // The minimum value
    }
}
settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    val storageUrl: String = System.getenv("FLUTTER_STORAGE_BASE_URL") ?: "https://storage.googleapis.com"
    repositories {
        google()
        mavenCentral()
        maven("$storageUrl/download.flutter.io")
    }
}

rootProject.name = "android_study"

include(":app")

// 這段引用 flutter 的 include_flutter.groovy
val flutterInclude = File(rootDir.parentFile, "flutter_module/.android/include_flutter.groovy")
apply(from = flutterInclude)

app/build.gradle

dependencies {
    implementation(project(":flutter"))
}

3.2 開(kāi)始集成

FlutterActivity

再 AndroidManifest.xml 中注冊(cè) activity

   <activity
            android:name="io.flutter.embedding.android.FlutterActivity"
            android:theme="@style/Theme.Android_study"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            />
//導(dǎo)入頭文件
import io.flutter.embedding.android.FlutterActivity;
//按鈕點(diǎn)擊打開(kāi)  FlutterActivity
    val btnOpenFlutter = view.findViewById<Button>(R.id.button_open_flutter)
         btnOpenFlutter.setOnClickListener {
            // 這里使用Fragment的context啟動(dòng)FlutterActivity
            val context = requireContext()
            val intent = FlutterActivity.createDefaultIntent(requireContext())
            startActivity(intent)
        }



其他方式請(qǐng)?jiān)斠?jiàn)文檔

https://docs.flutter.cn/add-to-app/android/add-flutter-screen

最后編輯于
?著作權(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)容