二. 嵌入原有项目
首先,我们先明确一点:Flutter设计初衷并不是为了和其它平台进行混合开发,它的目的是为了打造一个完整的跨平台应用程序。
但是,实际开发中,原有项目完全使用Flutter进行重构并不现实,对于原有项目我们更多可能采用混合开发的方式。
2.1. 创建Flutter模块
对于需要进行混合开发的原有项目,Flutter可以作为一个库或者模块,继承进现有项目中。
- 模块引入到你的Android或iOS应用中,以使用Flutter渲染一部分的UI,或者共享的Dart代码。
- 在Flutter v1.12中,添加到现有应用的基本场景已经被支持,每个应用在同一时间可以集成一个全屏幕的Flutter实例。
但是,目前一些场景依然是有限制的:
- 运行多个Flutter实例,或在屏幕局部上运行Flutter可能会导致不可以预测的行为;
- 在后台模式使用Flutter的能力还在开发中(目前不支持);
- 将Flutter库打包到另一个可共享的库或将多个Flutter库打包到同一个应用中,都不支持;
- 添加到应用在Android平台的实现基于 FlutterPlugin 的 API,一些不支持
FlutterPlugin
的插件可能会有不可预知的行为。
创建Flutter Module
flutter create --template module my_flutter
创建完成后,该模块和普通的Flutter项目一直,可以通过Android Studio或VSCode打开、开发、运行;
目录结构如下:
- 和之前项目不同的iOS和Android项目是一个隐藏文件,并且我们通常不会单独打开它们再来运行;
- 它们的作用是将Flutter Module进行编译,之后继承到现有的项目中;
my_flutter/ ├── .ios/ ├── .android/ ├── lib/ │ └── main.dart ├── test/ └── pubspec.yaml
2.2. 嵌入iOS项目
嵌入到现有iOS项目有多种方式:
- 可以使用 CocoaPods 依赖管理和已安装的 Flutter SDK ;
- 也可以通过手动编译 Flutter engine 、你的 dart 代码和所有 Flutter plugin 成 framework ,用 Xcode 手动集成到你的应用中,并更新编译设置;
目前iOS项目几乎都已经使用Cocoapods进行管理,所以推荐使用第一种CocoaPods方式;
我们按照如下的方式,搭建一个需要继承的iOS项目:
1.为了进行测试,我们这里创建一个默认的iOS项目:使用Xcode创建即可
创建一个iOS项目
2.将项目加入CocoaPods进行管理
- 电脑上需要已经安装了CocoaPods
初始化CocoaPods:
pod init
安装CocoaPods的依赖:
pod install
编译Podfile文件:
# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' # 添加模块所在路径 flutter_application_path = '../my_flutter' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') target 'ios_my_test' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # 安装Flutter模块 install_all_flutter_pods(flutter_application_path) # Pods for ios_my_test end
重新执行安装CocoaPods的依赖:
pod install
2.2.1. Swift代码
为了在既有的iOS应用中展示Flutter页面,需要启动 Flutter Engine
和 FlutterViewController
。
通常建议为我们的应用预热一个 长时间存活
的FlutterEngine:
- 我们将在应用启动的 app delegate 中创建一个
FlutterEngine
,并作为属性暴露给外界。
import UIKit import FlutterPluginRegistrant @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { // 1.创建一个FlutterEngine对象 lazy var flutterEngine = FlutterEngine(name: "my flutter engine") func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 2.启动flutterEngine flutterEngine.run() return true } }
在启动的ViewController中,创建一个UIButton,并且点击这个Button时,弹出FlutterViewController
import UIKit import Flutter class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 1.创建一个按钮 let button = UIButton(type: UIButton.ButtonType.custom) button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside) button.setTitle("Show Flutter", for: .normal) button.frame = CGRect(x: 80, y: 210, width: 160, height: 40) button.backgroundColor = UIColor.blue self.view.addSubview(button) } @objc func showFlutter() { // 2.创建FlutterViewController对象(需要先获取flutterEngine) let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine; let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil); navigationController?.pushViewController(flutterViewController, animated: true); } }
代码效果
我们也可以省略预先创建的 FlutterEngine
:
- 不推荐这样来做,因为在第一针图像渲染完成之前,可能会出现明显的延迟。
func showFlutter() { let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil) present(flutterViewController, animated: true, completion: nil) }
2.2.2. Objective-C代码
如果上面的代码希望使用Objective-C也是可以实现的:
- 代码的逻辑是完成一直的
AppDelegate.h代码:
@import UIKit; @import Flutter; @interface AppDelegate : FlutterAppDelegate @property (nonatomic,strong) FlutterEngine *flutterEngine; @end
AppDelegate.m代码:
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Used to connect plugins. #import "AppDelegate.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions { self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"]; [self.flutterEngine run]; [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end
ViewController.m代码:
@import Flutter; #import "AppDelegate.h" #import "ViewController.h" @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Make a button to call the showFlutter function when pressed. UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button addTarget:self action:@selector(showFlutter) forControlEvents:UIControlEventTouchUpInside]; [button setTitle:@"Show Flutter!" forState:UIControlStateNormal]; button.backgroundColor = UIColor.blueColor; button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0); [self.view addSubview:button]; } - (void)showFlutter { FlutterEngine *flutterEngine = ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine; FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil]; [self presentViewController:flutterViewController animated:YES completion:nil]; } @end
2.3.嵌入Android项目
嵌入到现有Android项目有多种方式:
- 编译为AAR文件(Android Archive)
- 通过Flutter编译为aar,添加相关的依赖
- 依赖模块的源码方式,在gradle进行配置
这里我们采用第二种方式
1.创建一个Android的测试项目
- 使用Android Studio创建
创建Android项目
2.添加相关的依赖
修改Android项目中的settings.gradle文件:
// Include the host app project. include ':app' // assumed existing content setBinding(new Binding([gradle: this])) // new evaluate(new File( // new settingsDir.parentFile, // new 'my_flutter/.android/include_flutter.groovy' // new ))
另外,我们需要在Android项目工程的build.gradle中添加依赖:
dependencies { implementation project(':flutter') }
编译代码,可能会出现如下错误:
- 这是因为从Java8开始才支持接口方法
- Flutter Android引擎使用了该Java8的新特性
解决办法:通过设置Android项目工程的build.gradle配置使用Java8编译:
compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 }
接下来,我们这里尝试添加一个Flutter的screen到Android应用程序中
Flutter提供了一个FlutterActivity来展示Flutter界面在Android应用程序中,我们需要先对FlutterActivity进行注册:
- 在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" />
2.3.1. Java代码
package com.coderwhy.testandroid; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); startActivity( FlutterActivity.createDefaultIntent(this) ); } }
也可以在创建时,传入默认的路由:
package com.coderwhy.testandroid; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); startActivity( FlutterActivity .withNewEngine() .initialRoute("/my_route") .build(currentActivity) ); } }
将Flutter页面嵌入到Android项目
2.3.2. Kotlin代码
package com.coderwhy.test_demo_a_k import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import io.flutter.embedding.android.FlutterActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) startActivity( FlutterActivity.createDefaultIntent(this) ) } }
也可以在创建时指定路由:
package com.coderwhy.test_demo_a_k import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import io.flutter.embedding.android.FlutterActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) startActivity( FlutterActivity .withNewEngine() .initialRoute("/my_route") .build(this) ); } }
三. Flutter模块调试
一旦将Flutter模块继承到你的项目中,并且使用Flutter平台的API运行Flutter引擎或UI,那么就可以先普通的Android或者iOS一样来构建自己的Android或者iOS项目了
但是Flutter的有一个非常大的优势是其快速开发,也就是hot reload。
那么对应Flutter模块,我们如何使用hot reload加速我们的调试速度呢?
- 可以使用flutter attach
# --app-id是指定哪一个应用程序 # -d是指定连接哪一个设备 flutter attach --app-id com.coderwhy.ios-my-test -d 3D7A877C-B0DD-4871-8D6E-0C5263B986CD
attach调试模式