Flutter的混编的之路

Flutter的混编的之路前言最近在接触这方面的东西,也踩了很多坑,这里主要做下记录和分享,帮助大家避坑,也让自己铭记。路由方面我是用闲鱼的FlutterBoost,今天

大家好,欢迎来到IT知识分享网。

前言

最近在接触这方面的东西,也踩了很多坑,这里主要做下记录和分享,帮助大家避坑,也让自己铭记。

路由方面我是用闲鱼的FlutterBoost,今天就主要讲一下FlutterBoost 的集成之路,以及产物打包等流程。

在这里我先插个题题外话,关于Flutter的话题在各大平台都有论述,有一些中庸的观点,也有一些极端的观点,也有将Flutter 与Compose相比较的。在这里想表达一下我的观点,Flutter 是跨端方案的首选,如果有跨端的需求,Flutter还是一个很好的选择。在没有跨端需求的情况下,为了节省人力成本,可以在一些不重要的模块使用Flutter开发,或者项目前期为了快速上线,也可以使用Flutter进行快速开发,毕竟Flutter 在开发效率方面还有有目共睹的(熟练使用的前提下)。如果是自己做产品,我还是建议使用原生进行开发。

还有一个话题是将Flutter和Compose相比较的,在这里我想说明的是,Flutter的重点是跨端,Compose的重点在Android的声明式UI,两者的侧重点不同。

好了,题外话就说到这里,接下来开始聊我们今天的主题。

FlutterBoost的集成

在这里先贴一下FlutterBoost的官方地址

https://github.com/alibaba/flutter_boost

首先在同一级目录中新建两个项目,一个flutter moudle项目,一个是Android 项目。

Flutter的混编的之路

然后再在flutter_moudle的pubspec.yml 的dependencies 中 进行依赖flutter_boost

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  flutter_boost:
    git:
      url: https://github.com/alibaba/flutter_boost.git
      ref: v3.0-preview.18

然后运行 flutter pub get 进行依赖导入。

然后在main.dart文件中,进行路由配置

引自flutter boost 官方文档 https://github.com/alibaba/flutter_boost/blob/master/docs/install.md

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';

void main() {
  ///这里的CustomFlutterBinding调用务必不可缺少,用于控制Boost状态的resume和pause
  CustomFlutterBinding();
  runApp(MyApp());
}


///创建一个自定义的Binding,继承和with的关系如下,里面什么都不用写
class CustomFlutterBinding extends WidgetsFlutterBinding with BoostFlutterBinding {}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  /// 由于很多同学说没有跳转动画,这里是因为之前exmaple里面用的是 [PageRouteBuilder],
  /// 其实这里是可以自定义的,和Boost没太多关系,比如我想用类似iOS平台的动画,
  /// 那么只需要像下面这样写成 [CupertinoPageRoute] 即可
  /// (这里全写成[MaterialPageRoute]也行,这里只不过用[CupertinoPageRoute]举例子)
  ///
  /// 注意,如果需要push的时候,两个页面都需要动的话,
  /// (就是像iOS native那样,在push的时候,前面一个页面也会向左推一段距离)
  /// 那么前后两个页面都必须是遵循CupertinoRouteTransitionMixin的路由
  /// 简单来说,就两个页面都是CupertinoPageRoute就好
  /// 如果用MaterialPageRoute的话同理

  Map<String, FlutterBoostRouteFactory> routerMap = {
    'mainPage': (RouteSettings settings, String uniqueId) {
      return CupertinoPageRoute(
          settings: settings,
          builder: (_) {
            Map<String, Object> map = settings.arguments as Map<String, Object> ;
            String data = map['data'] as String;
            return MainPage(
              data: data,
            );
          });
    },
    'simplePage': (settings, uniqueId) {
      return CupertinoPageRoute(
          settings: settings,
          builder: (_) {
            Map<String, Object> map = settings.arguments as Map<String, Object>;
            String data = map['data'] as String;
            return SimplePage(
              data: data,
            );
          });
    },
  };

  Route<dynamic> routeFactory(RouteSettings settings, String uniqueId) {
    FlutterBoostRouteFactory func = routerMap[settings.name] as FlutterBoostRouteFactory;
    return func(settings, uniqueId);
  }

  Widget appBuilder(Widget home) {
    return MaterialApp(
      home: home,
      debugShowCheckedModeBanner: true,

      ///必须加上builder参数,否则showDialog等会出问题
      builder: (_, __) {
        return home;
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return FlutterBoostApp(
      routeFactory,
      appBuilder: appBuilder,
    );
  }
}

class MainPage extends StatelessWidget {
  const MainPage({Object data});
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: Text('Main Page')),
    );
  }
}

class SimplePage extends StatelessWidget {
  const SimplePage({Object data});
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body:  Center(child: Text('SimplePage')),
    );
  }
}

flutter 部分 就集成完了,然后是 Android 部分。

在项目根目录的setting.gradle中导入flutter_moudle工程

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')

然后在app的build.gradle中引用一下

implementation project(':flutter')
implementation project(':flutter_boost')

在清单文件中声明一下activity

<activity
        android:name="com.idlefish.flutterboost.containers.FlutterBoostActivity"
        android:theme="@style/Theme.AppCompat"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize" >

</activity>
<meta-data android:name="flutterEmbedding"
           android:value="2">
</meta-data>

最后在Application 进行注册一下flutter boost

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
            @Override
            public void pushNativeRoute(FlutterBoostRouteOptions options) {
                //这里根据options.pageName来判断你想跳转哪个页面,这里简单给一个
                Intent intent = new Intent(FlutterBoost.instance().currentActivity(), YourTargetAcitvity.class);
                FlutterBoost.instance().currentActivity().startActivityForResult(intent, options.requestCode());
            }

            @Override
            public void pushFlutterRoute(FlutterBoostRouteOptions options) {
                Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class)
                        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
                        .destroyEngineWithActivity(false)
                        .uniqueId(options.uniqueId())
                        .url(options.pageName())
                        .urlParams(options.arguments())
                        .build(FlutterBoost.instance().currentActivity());
                FlutterBoost.instance().currentActivity().startActivity(intent);
            }
        }, engine -> {
        });
    }
}

这里我们就完成了集成。

FlutterBoost的使用

这里就简单一下具体使用,详细使用参考官方文档

Flutter开启新页面

BoostNavigator.instance.push(
    "yourPage", //required
    withContainer: false, //optional
    arguments: {"key":"value"}, //optional
    opaque: true, //optional,default value is true
);

///or
Navigator.of(context).pushNamed('simplePage', arguments: {'data': _controller.text});

关闭页面

///pop一次
BoostNavigator.instance.pop(result);

Android开启新页面

FlutterBoostRouteOptions options = new FlutterBoostRouteOptions.Builder()
                .pageName("pageName")
                .arguments(new HashMap<>())
                .requestCode(1111)
                .build();
FlutterBoost.instance().open(options);

关闭页面

FlutterBoost.instance().close(uniqueId);

给Android发送消息:

BoostChannel.instance.sendEventToNative('flutter_msg', {'img_file': file.toString()});

在Android监听消息

       val listener = EventListener { key: String, args: Map<Any?, Any?> ->
            when (key) {
                "weeklyToNative" -> {
                    val path = args["img_file"]?.toString()?.getFileName()
                    path?.let {
                        showShareDialog<String>(path, false)
                    }
                }
            }
        }
        remover = FlutterBoost.instance().addEventListener("flutter_msg", listener)
        remover.remove()//可以在页面销毁的时候移除监听

这样就完成了 flutter 和原生之间交互。

但是这样还不完善,像我们有个需求就是flutter传数据给原生,原生进行一个弹窗。所以我们需要进行改进一下。

我们新建一个FlutterPage类,然后继承自FlutterBoostActivity ,然后在Application 中进行替换一下

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
           @Override
            public void pushFlutterRoute(FlutterBoostRouteOptions options) {
                                                  //在这里将原来的FlutterBoostActivity 换成我们自己的 FlutterPage
                Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterPage.class)
                    .....
                FlutterBoost.instance().currentActivity().startActivity(intent);
            }
        }, engine -> {
        });
    }
}

上文我们提到了flutter 消息的监听,就可以在FlutterPage 中进行监听,可以依附这个activity 进行弹窗等UI操作。

编译产物

在flutter moudle中,可以通过Build->Flutter->Buiold AAR ,来将整个moudle 打包成aar 提供给我们项目原生项目使用。也可以通过flutter build aar 命令 来进行打包,通过 –output-dir 参数来修改产物仓库的路径。这样会有一个缺点,仓库的路径只能设置在本地,如果是单人开发的话,可能没有问题,直接依赖自己本地的仓库就可以了,如果是多人协作开发, 大家每个人都需要在本地重新进行build aar 一下,这样的话就很麻烦。

既然有麻烦就需要解决麻烦。

我的想法是既然可以在project中通过对本地仓库 的flutter moudle 的aar 进行引用,那我们通过一个Android moudle 也可以进行依赖引用。

Flutter的混编的之路

具体flutter 产物 aar 的用法,在打包完成之后,控制台上会有对应的使用提示,按照提示去依赖就就好了。

当我们在android 的moudle 中 进行打包aar 的时候,会发现产物的体积很小

Flutter的混编的之路

这是因为进行打包的时候并不会将flutter moudle的 aar 的其他aar依赖进行打包进去。(aar不能进行二次打包)

这是时候就需要借助 fat-aar-android 这个插件帮我合并一下

Github:https://github.com/kezong/fat-aar-android

具体方法在官方文档有详细说明。我们使用的时候需要下面这个进行配置,这样就可以将所有远程依赖在pom中声明的依赖项同时打入在最终产物里

fataar {
    transitive = true
}

然后通过exclude关键字将多余的依赖过滤一下:

//例子
embed('com.facebook.fresco:fresco:1.11.0') {
    exclude(group:'com.facebook.soloader', module:'soloader')
    transitive = false
}

注:我这里测试使用这种过滤方式效果不明显,故自己魔改源码(只增加了一行代码),只依赖了自己需要依赖的选项。

在这里还有一个操作,我是将libflutter.so放在了libs目录下

Flutter的混编的之路

否则的话,项目依赖了最终产物之后也不能将libflutter.so打包进apk内。

Flutter的混编的之路

这就是我们最终产物了, 可以看到我们需要的依赖都已经打包进去了。

然后在android moudle 的 build.gradle 中 配置一下maven-publish插件,就可以将我们的产物上传私仓, 大家就可以方便使用了。

在这里还是有一些缺陷,就是进行需要两步操作,现在flutter 打包,然后在Android 进行上传。一键操作的方案 尝试过gradle task的形式,因为这种在win环境 还是在mac 环境上都支持的,但是在实践过程中发现 gradle 执行shell cd命令的时候,会失效,无法切换目录,因为flutter build aar 的命令 需要在flutter moudle 的根目录下执行的,所以这种方案就放弃了。目前在尝试 CI/CD 流水线的形式,还在搭建环境。我这也算抛砖引玉,想大家集思广益,看看有没有其他更合适的方案。

总结

今天主要是跟大家分享了 Flutter 和原生混编的一种方案,使用flutter boost 和原生进行交互,以及flutter 编译产物的 快捷打包交付方案。大家有好的想法也可以多多留言交流。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/48230.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信