# flutter_engine_example **Repository Path**: FlutterProjects/flutter_engine_example ## Basic Information - **Project Name**: flutter_engine_example - **Description**: Flutter:通过 FlutterEngineGroup 实现 Flutter 页面与原生页面之间的跳转 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2023-04-19 - **Last Updated**: 2024-06-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # `Flutter`:`Flutter`:通过 `FlutterEngineGroup` 实现 `Flutter` 页面与原生页面之间的跳转 [参考链接](https://juejin.cn/post/6962176733801873445#heading-6) **实现效果如图** ![Untitled1](assets/Untitled1.gif) *** # 目录 * 一、FlutterEngineGroup 的介绍 * 二、FlutterEngineGroup 工程到目录结构 * 三、FlutterEngineGroup 工程中 Dart 部分的配置代码 * 四、FlutterEngineGroup 工程中 iOS 部分的配置代码 * 五、FlutterEngineGroup 工程中 Android 部分的配置代码 * 六、问题 ## 一、`FlutterEngineGroup` 的介绍 `FlutterEngineGroup`是由官方推出的`Flutter+Native`混合开发解决方案,与`FlutterBoost`不同的是,使用的是`多引擎`的处理方式,并优化了每个引擎的大小以及数据处理方式,让大部分数据实现共享,只有部分生命周期等内容做了隔离。 *** ## 二、`FlutterEngineGroup` 工程到目录结构 > 在 `flutter_engine_example` 目录中,用 `Xcode` 创建 一个 `EngineTestIOS` 项目, 用 `Android Studio` 创建一个 `EngineTestAndroid` 项目。 然后在用 `Android Studio` 创建一个 `flutter module` 项目 `flutter_module`, 判断是不是 `module` 的方法就是看其是否有 `android` 和 `iOS` 文件夹, 如果没有,那就是 `module` **目录如图** ![1](assets/1.jpeg) *** ## 三、`FlutterEngineGroup` 工程中 `Dart` 部分的配置代码 ### 1、`main.dart`中入口相关代码 ```dart /// 首页模块入口 @pragma('vm:entry-point') void main() => runApp(AppPage()); /// 我的模块入口 @pragma('vm:entry-point') void mine() => runApp(MineApp()); ``` > * 我们定义了三个函数`main`和`mine`, 他们分别加载了`AppPage()`和`MineApp()`,也可以直接理解为两个模块,他们是相互独立的。 > * 加`@pragma('vm:entry-point')`这个注解是为了避免`Dart`的摇树优化`(tree-shaking)`将这里定义的函数认定为无用代码给优化掉了。`main`函数可以不加这个注解,统一加上也无妨。 ### 2、`mine_page`中相关代码 ```dart import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_module/app/app_page.dart'; import 'package:flutter_module/channels/tabbar_channel.dart'; import 'package:flutter_module/pages/detail_page.dart'; import 'package:flutter_module/utils/navigator_util.dart'; class MineApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primaryColor: Colors.blue ), home: MinePage(), navigatorObservers: [routeObserver], ); } } class MinePage extends StatefulWidget { @override _MinePageState createState() => _MinePageState(); } // 2. 混入 RouteAware class _MinePageState extends State with RouteAware { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("我的页面 - Flutter页面"), ), body: Container( color: Colors.blue, width: double.infinity, height: double.infinity, child: Center( child: InkWell( child: Text("试数据测试测试数据测试"), onTap: () { NavigatorUtil.pushPage(context, DetailPage(), pageName: "login"); }, ), ), ), floatingActionButton: FloatingActionButton.extended( label: Text( "跳转详情页", style: TextStyle( color: Colors.black ), ), backgroundColor: Colors.white, onPressed: () { // NavigatorUtil.present(context, DetailPage(), "detail"); NavigatorUtil.pushPage(context, DetailPage()); }, ), ); } @override void didChangeDependencies() { super.didChangeDependencies(); // 3. 订阅路由监听器 routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); } @override void dispose() { // 4. 取消订阅路由监听器 routeObserver.unsubscribe(this); super.dispose(); } void didPopNext() { // 5. 返回到当前页面 TabBarController.showTab(); } void didPushNext() { // 6. 跳转到下一个页面 TabBarController.hideTab(); } } ``` ### 3、实现当二级甚至更深层级的界面的时候需要隐藏TabBar,只有一级界面显示TabBar。 * 封装一个`TabBar`功能相关的插件类`TabBarController` ```dart import 'package:flutter/services.dart'; class TabBarController { // 定义一个MethodChannel static final channel = const MethodChannel("tab_switch"); /// 显示tabbar static Future showTab() async { final result = await channel.invokeMethod("showTab"); return result ?? 0; } /// 隐藏tabbar static Future hideTab() async { final result = await channel.invokeMethod("hideTab"); return result ?? 0; } } ``` > 定义一个`MethodChannel`,然后定义了一个`showTab`和一个`hideTab`方法去调用原生代码。 * 初始化一个路由监听器 ```dart // 路由监听器 final RouteObserver routeObserver = RouteObserver(); ``` * 监听路由的变化 ```dart class MineApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // ...省略 // 1. MaterialApp 加上路由监听器 navigatorObservers: [routeObserver], ); } } class MinePage extends StatefulWidget { @override _MinePageState createState() => _MinePageState(); } // 2. 混入 RouteAware class _MinePageState extends State with RouteAware { @override Widget build(BuildContext context) { return Scaffold( // ...省略 floatingActionButton: FloatingActionButton.extended( label: Text( "跳转详情页", style: TextStyle( color: Colors.black ), ), backgroundColor: Colors.white, onPressed: () { // 跳转 NavigatorUtil.pushPage(context, DetailPage()); }, ), ); } @override void didChangeDependencies() { super.didChangeDependencies(); // 3. 订阅路由监听器 routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); } @override void dispose() { // 4. 取消订阅路由监听器 routeObserver.unsubscribe(this); super.dispose(); } void didPopNext() { // 5. 返回到当前页面 TabBarController.showTab(); } void didPushNext() { // 6. 跳转到下一个页面 TabBarController.hideTab(); } } ``` > 当订阅路由监听器后,`MinePage`跳转到其他页面时会调用`didPushNext`,此时通知`Native`代码隐藏`TabBar`,当其他页面跳转回`MinePage`时,此时通知`Native`代码显示`TabBar`。 > `iOS`端实现详见 **`FlutterEngineGroup` 工程中 `iOS` 部分的配置代码** 中的 **5、实现隐藏/显示`TabBar`** ### 4、数据存储 * 用到三方 `shared_preferences` 定义一个全局文件 `global.dart` ```dart import 'package:shared_preferences/shared_preferences.dart'; late SharedPreferences preferences; ``` 实例化并测试存储数据 ```dart @override void initState() { super.initState(); setupApp(); } void setupApp() async { // 设置持久化数据 // 实例化 preferences = await SharedPreferences.getInstance(); // 设置string类型 await preferences?.setString("data", "我是数据存储的测试数据啊"); } ``` 在 `detail_page.dart` 访问存储数据 ```dart import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_module/utils/navigator_util.dart'; import '../global.dart'; class DetailPage extends StatefulWidget { @override State createState()=> _DetailPageState(); } class _DetailPageState extends State { String? data = preferences.get("data") as String?; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( onPressed: () { NavigatorUtil.pop(context); }, icon: Icon(Icons.arrow_back_ios_new), ), title: const Text("详情页面 - Flutter页面"), ), body: Center( child: Text("${data}"), ), ); } } ``` *** ## 四、`FlutterEngineGroup` 工程中 `iOS` 部分的配置代码 **效果如图** ![Untitled](assets/Untitled.gif) ### 1、配置 `Podfile` 文件 在 `Podfile` 文件, 添加 ```ruby source 'https://github.com/CocoaPods/Specs.git' platform :ios, '11.0' use_frameworks! # 1. 找到flutter module 的目录 flutter_application_path = '../flutter_module' # 2. 找到flutter module 的目录中的/.ios/Flutter/podhelper.rb文件 load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') target 'EngineTestIOS' do use_frameworks! # 3. 执行podhelper.rb中的install_all_flutter_pods方法 install_all_flutter_pods(flutter_application_path) # 其他的pod库 pod 'SnapKit' end post_install do |installer| flutter_post_install(installer) if defined?(flutter_post_install) end ```
> `pod install` > 执行这个命令能将`Flutter SDK` 和 `Flutter` 代码 引入到`iOS`项目中。 ### 2、在`Appdelegate`中定义一个`FlutterEngineGroup`对象 ```swift import FlutterPluginRegistrant var engineGroup = FlutterEngineGroup(name: "flutter_module", project: nil) ```
> 如果项目中有多个`Flutter`模块就需要使用`FlutterEngineGroup`, 它能管理多个`FlutterEngine`, 让他们共享资源等功能。 ### 3、定义`Controller` 接下来是将三个加载不同`Flutter APP`的`FlutterViewController`的`View`放在`HomeViewController`和`UserViewController`的`View`上。 > 为什么这样做? > `UITabbarController`的子`ViewController`几乎是同时初始化的,如果他们都是`FlutterViewController`那么会造成对`FlutterEngineGroup`共享资源的争夺,这样显示会出现异常。这也是目前使用多个`Flutter module`会出现的一个问题。所以需要改变下思路,在需要使用的时候再进行`FlutterViewController`的初始化。其实这也有点问题,就是进行预加载比较难控制,每个`Flutter module`第一次加载的时候会有点慢。 * 定义一个`FlutterViewController`子类 `TestFlutterViewController` ```swift // 1. 引入库 import FlutterPluginRegistrant class TestFlutterViewController: FlutterViewController { private var channel: FlutterMethodChannel? init(withEntrypoint entryPoint: String?) { let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate // 2. 用Appdelegate中的FlutterEngineGroup生成一个FlutterEngine,引擎加载入口是main.dart的entrypoint函数 let newEngine = appDelegate.engineGroup.makeEngine(withEntrypoint: entryPoint, libraryURI: nil) // 3. 用这个FlutterEngine初始化FlutterViewController super.init(engine: newEngine, nibName: nil, bundle: nil) } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() } } ``` > 自定义了一个`FlutterViewController`子类,这个子类会根据传过来的`entryPoint`初始化一个`FlutterEngine`, 这个`FlutterEngine`的加载入口是`main.dart`文件中的`entrypoint`函数,然后`FlutterViewController`子类持有这个`FlutterEngine`; * `HomeViewController`加载`TestFlutterViewController` ```swift class HomeViewController: UIViewController { // 1. 懒加载 main.dart 中的main入口函数对应的Flutter App private lazy var subFlutterVC = TestFlutterViewController(withEntrypoint: nil) override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isHidden = true // 2. 添加FlutterViewController addChild(subFlutterVC) let safeFrame = self.view.safeAreaLayoutGuide.layoutFrame subFlutterVC.view.frame = safeFrame self.view.addSubview(subFlutterVC.view) subFlutterVC.didMove(toParent: self) } } ``` > * `HomeViewController`加载`main.dart` 中的`main`入口函数对应的`Flutter App`, 对应的`void main() => runApp(AppPage());`的内容; > * `懒加载`也是为了解决`资源竞争`的问题。
* `UserViewController`加载`TestFlutterViewController` ```swift class UserViewController: UIViewController { // 1. 懒加载 main.dart 中的main入口函数对应的Flutter App private lazy var subFlutterVC = TestFlutterViewController(withEntrypoint: "mine") override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isHidden = true // 2. 添加FlutterViewController addChild(subFlutterVC) let safeFrame = self.view.safeAreaLayoutGuide.layoutFrame subFlutterVC.view.frame = safeFrame self.view.addSubview(subFlutterVC.view) subFlutterVC.didMove(toParent: self) } } ``` > * 对应 `void mine() => runApp(MineApp());` ### 4、注册插件 我们的`Flutter`项目中用到的插件,都需要统一注册`Flutter Engine`中。 * `Flutter`插件 ```yaml dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 common_utils: webview_flutter: share: ``` * 在`iOS`项目中注册 ```swift // 1. 引入库 import FlutterPluginRegistrant class TestFlutterViewController: FlutterViewController { override func viewDidLoad() { super.viewDidLoad() // 2. 注册插件到FlutterEngine中 GeneratedPluginRegistrant.register(with: self.pluginRegistry()) } } ``` ![3](assets/3.png)
如果没有注册,就是报错,如:使用 `shared_preferences` 数据存储,并把注册代码注释掉 ```swift class TestFlutterViewController: FlutterViewController { override func viewDidLoad() { super.viewDidLoad() // 2. 注册插件到FlutterEngine中 // GeneratedPluginRegistrant.register(with: self.pluginRegistry()) } } ``` ![2](assets/2.png) ### 5、实现隐藏/显示`TabBar` ```swift class TestFlutterViewController: FlutterViewController { private var channel: FlutterMethodChannel? override func viewDidLoad() { super.viewDidLoad() // 1. 生成FlutterMethodChannel channel = FlutterMethodChannel(name: "tab_switch", binaryMessenger: self.engine!.binaryMessenger) // 2. 注册回调方法 channel?.setMethodCallHandler({ [weak self] call, result in if call.method == "showTab" { // 3. 显示TabBar self?.showTab() } else if call.method == "hideTab" { // 3. 隐藏TabBar self?.hideTab() } else { } }) } /// 显示TabBar func showTab() { self.parent?.tabBarController?.tabBar.isHidden = false } /// 隐藏TabBar func hideTab() { self.parent?.tabBarController?.tabBar.isHidden = true } } ``` > `iOS` 端主要就是在`FlutterViewController`中初始化`FlutterMethodChannel`,监听`Flutte`r端的调用,然后去控制`UITabbarController`。 *** ## 五、`FlutterEngineGroup` 工程中 `Android` 部分的配置代码 ### 1、配置 `fragment_me.xml` 文件 ```xml ``` ### 2、配置 `setting.gradle` 文件 在 `setting.gradle` 文件, 添加 ```java setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'flutter_module/.android/include_flutter.groovy' )) include ':flutter_module' project(':flutter_module').projectDir = new File('../flutter_module') ``` ![4](assets/4.png) ### 3、配置 build.gradle 文件 在`app`的`build.gradle`下添加如下代码 ```java implementation project(':flutter') ``` ![5](assets/5.jpeg) 然后点击右上角的`sync` 同步一下,就会开始一些下载和同步的进程,等待完成。 ### 4、在`Application`中创建一个`FlutterEngineGroup`对象 #### - 新建一个`App`类继承`Application` ```java public class App extends Application { public static Context app; public static FlutterEngineGroup flutterEngineGroup; @Override public void onCreate() { super.onCreate(); app = getApplicationContext(); // 创建FlutterEngineGroup对象 flutterEngineGroup = new FlutterEngineGroup(this); } public static Context Instance() { return app; } } ``` ![6](assets/6.jpeg) #### - `AndroidManifest.xml` 需要在 `application` 设置一下 ```java android:name=".App" ``` ![7](assets/7.png) ### 5、创建一个FBFlutterEngineManager缓存管理类 ```java public class AppFlutterEngineManager { public static FlutterEngine flutterEngine(Context context, String engineId, String entryPoint) { // 1. 从缓存中获取FlutterEngine FlutterEngine engine = FlutterEngineCache.getInstance().get(engineId); if (engine == null) { // 如果缓存中没有FlutterEngine // 2. 新建FlutterEngine,执行的入口函数是entryPoint DartExecutor.DartEntrypoint dartEntrypoint = new DartExecutor.DartEntrypoint(FlutterInjector.instance().flutterLoader().findAppBundlePath(), entryPoint); engine = App.flutterEngineGroup.createAndRunEngine(context, dartEntrypoint); // 3. 存入缓存 FlutterEngineCache.getInstance().put(engineId, engine); } return engine; } } ``` > 这里我们使用了`FlutterEngineCache`缓存类,先从中获取缓存的`FlutterEngine`,如果没有取到,则新建一个`FlutterEngine`,然后缓存起来。 ### 6、Fragment中嵌入Flutter Module 我们这一步是`将FlutterEngine`和`FlutterFragment`进行绑定,然后显示。 ```java public class UserFragment extends Fragment implements OnClickTabListener { // 1. FlutterEngine对象 private FlutterEngine engine; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View userView = inflater.inflate(R.layout.fragment_me, container, false); initWithUserFragment(userView); return userView; } /** * Fragment 初始化 */ private void initWithUserFragment(final View hpView) { String engineId = String.valueOf(R.id.me_fl); // 2. 通过FBFlutterEngineManager获取FlutterEngine对象 engine = AppFlutterEngineManager.flutterEngine(requireContext(), engineId, "mine"); // 3. 用FlutterEngine对象构建出一个FlutterFragment FlutterFragment flutterFragment = FlutterFragment.withCachedEngine(engineId).build(); // 4. 显示FlutterFragment getParentFragmentManager().beginTransaction().replace(R.id.me_fl, flutterFragment).commit(); } } ``` > 我们这里使用缓存的`FlutterEngine`更能节省资源,因为`Bottom Navigation Activity`的`Fragment`来回切换的时候,Fragment是会重新新建和销毁的。 ### 7、`MethodChannel` 实现 `Flutter` 与 原生原生`(Android 、iOS)`双向通信 ```java public class UserFragment extends Fragment implements OnClickTabListener { // ...省略 private MethodChannel methodChannel; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View userView = inflater.inflate(R.layout.fragment_me, container, false); initWithUserFragment(userView); return userView; } /** * Fragment 初始化 */ private void initWithUserFragment(final View hpView) { String engineId = String.valueOf(R.id.me_fl); engine = AppFlutterEngineManager.flutterEngine(requireContext(), engineId, "mine"); // ...省略 methodChannel = new MethodChannel(engine.getDartExecutor().getBinaryMessenger(),"tab_switch"); methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { if (call.method.equals("showTab")) { MainActivity activity = (MainActivity) getActivity(); activity.switchBottomView(true); } else if (call.method.equals("hideTab")) { MainActivity activity = (MainActivity) getActivity(); activity.switchBottomView(false); } else { result.notImplemented(); } } }); } @Override public void onclick(int position) { } } ``` ### 8、总结 目前原生移动应用能集成多个`Flutter Module`,这样使用起来就方便多了。 `Android`中也可以使用`FlutterView`, 在一个`Activity`或者`Fragment`中可以展示多个`FlutterView`,但是使用个`FlutterView`需要绑定生命周期,使用起来稍显复杂。 *** ## 六、问题 ***