目前大多数公司都有自己开发多年的项目,不可能直接用Flutter从头开发一套,那样不实现,除非是小项目,因此只能是在原有的基础上用Flutter来开发新业务或重构旧业务,而这里就需要用到Flutter的混合开发
一、创建Flutter模块
使用混合开发就不能像之前一样直接上来就创建一个Flutter项目,而是要使用Flutter模板
# flutter_module_lxf 可以随便你命名 flutter create --template module flutter_module_lxf # --template 可以替换为 -t # flutter create -t module flutter_module_lxf
创建出来的Flutter模块依然是可以像之前创建的Flutter项目一样打开和运行的。
目录下有也有ios和android目录,只不过前面加了个点 ,成了点目录。
二、iOS
集成
通过Cocoapods,将Flutter模块编译成一个库,再到原生项目中进行引入和使用即可
在Podfile中添加两行配置
# 指定我们刚刚创建的 Flutter 模块的路径 flutter_application_path = '../flutter_module_lxf' # 拼接脚本文件的路径: .ios/Flutter/podhelper.rb load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
在每个需要引用Flutter的Target下,都需要添加一行配置
install_all_flutter_pods(flutter_application_path)
添加后如下所示:
flutter_application_path = '../flutter_module_lxf' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') use_frameworks! target 'LXFFlutterHybridDemo' do install_all_flutter_pods(flutter_application_path) end
添加完成后,执行一次pod install
使用
两个步骤
- 获取 Flutter引擎FlutterEngine
- 通过FlutterEngine创建FlutterViewController
基本使用
AppDelegate类中声明一个FlutterEngine变量,在didFinishLaunchingWithOptions方法中启动Flutter引擎
// AppDelegate.swift import Flutter @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { // 创建 Flutter引擎 lazy var flutterEngine = FlutterEngine(name: "lxf") func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 启动 Flutter引擎 flutterEngine.run() return true } ... }
ViewController中添加一个按钮,点击弹出Flutter模块
// ViewController.swift override func viewDidLoad() { super.viewDidLoad() let btn = UIButton(type: .custom) btn.frame = CGRect(x: 100, y: 200, width: 200, height: 44) btn.backgroundColor = .black btn.addTarget(self, action: #selector(showFlutterVc), for: .touchUpInside) btn.setTitle("弹出Flutter模块", for: .normal) self.view.addSubview(btn) } @objc func showFlutterVc() { // 创建FlutterViewController let flutterVc = FlutterViewController(engine: fetchFlutterEngine(), nibName: nil, bundle: nil) self.present(flutterVc, animated: true, completion: nil) } func fetchFlutterEngine() -> FlutterEngine { return (UIApplication.shared.delegate as! AppDelegate).flutterEngine }
如果遇到报Command PhaseScriptExecution failed with a nonzero exit code错误,如下图所示:
请先用Android Studio或VSCode打开Flutter模块项目并运行到iOS设备上,让其帮我们对iOS项目进行一些初始化配置。成功运行后就可以关闭Flutter模块项目的运行了,接着再用Xcode打开原生项目运行即可。
修改初始路由
官方文档里面提到,修改初始路由,需要在Flutter引擎在run之前,通过invokeMethod调用setInitialRoute方法进行设置,代码如下
// 修改初始路由 flutterEngine.navigationChannel.invokeMethod("setInitialRoute", arguments: "/other") // 启动 Flutter引擎 flutterEngine.run()
但是,我发现这样写并没有起任何作用,在Flutter的官方issue上也有人提到这个问题: 【setInitialRoute is broken for iOS add-to-app #59895】,目前只能官方进行修复和调整API
临时可以使用如下方式实现:
let flutterVc = FlutterViewController(project: FlutterDartProject(), nibName: nil, bundle: nil) flutterVc.setInitialRoute("/other") self.present(flutterVc, animated: true, completion: nil)
虽然这么写可以实现这个功能,但是会有明显的类似卡顿的现象,因为使用这种方式去创建FlutterViewController之前,会隐式创建和启动一个FlutterEngine,而我们弹出FlutterViewController时FlutterEngine还没加载完毕,所以我们会看到先弹出了一个透明的界面,再显示/other路由对应的界面视图。
使用 FlutterAppDelegate
使用FlutterAppDelegate这个不是必要的操作,但是如果你想让Flutter模块也能使用原生的功能的话,建议使用
原生功能
- 处理openURL的回调
- 列表视图在点击状态栏后滚到顶部
class AppDelegate: FlutterAppDelegate
更具体的使用,请阅读 官方文档
三、Android
修改安卓项目 根目录下的settings.gradle文件
// settings.gradle include ':app' // assumed existing content setBinding(new Binding([gradle: this])) // new evaluate(new File( // new settingsDir.parentFile, // new // 这里的 flutter_module_lxf 请修改为你自己创建的Flutter模板目录名称 'flutter_module_lxf/.android/include_flutter.groovy' // new ))
修改安卓项目app目录下的build.gradle文件
// app/build.gradle dependencies { ... // 配置flutter依赖 implementation project(':flutter') }
如果在编译的时候遇到如下错误
Default interface methods are only supported starting with Android N (--min-api 24): void androidx.lifecycle.DefaultLifecycleObserver.onCreate(androidx.lifecycle.LifecycleOwner)
请确认是否指定了使用Java 8进行编译 【官方文档 - Java 8 requirement】
修改安卓项目app目录下的build.gradle文件
// app/build.gradle android { ... compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } ... }
修改app/src/main/AndroidManifest.xml文件
// app/src/main/AndroidManifest.xml <activity android:name="io.flutter.embedding.android.FlutterActivity" android:theme="@style/AppTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" />
添加一个按钮,点击弹出Flutter模块
<!-- activity_main.xml --> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:text="弹出Flutter模块" android:background="#000000" android:textColor="#ffffff" android:gravity="center" android:onClick="btnClick" />
// MainActivity.java public void btnClick(View v) { startActivity( FlutterActivity.createDefaultIntent(this) ); }
四、调试与热重载
由于当前我们是使用原生开发工具(如:Xcode)来运行项目,每次修改我们的Flutter模块的代码,也就需要重新运行才能看到效果,不像之前按下Cmd + s就能进行热重载。这样Flutter模块的开发效率极其低下,那有没有办法可以让我们像之前开发Flutter项目时那样进行热重载呢?答案是有的
Flutter官方提供了flutter attach,以辅助我们开发,到终端下执行
flutter attach
如果当前有多个设备,会提示我们需要指定attach哪个设备
按要求加上指定参数即可
flutter attach -d FE305309-9E79-418D-BA3F-7EFECF2980BC
如图,这样就关联上了,你在dart文件里面对界面进行任何修改后,按r进行热重载,按R进行热启动。
如果你使用的是Android Studio,可以直接选择对应的设备后,点击右边的Flutter Attach按钮,执行成功后就可以跟之前一样按Cmd + s进行热重载了。