1. 概要
Flutter Boost 是eBay项目组合作开发的两个 Flutter 混和合作开发架构,项目大背景可以看看eBay的这篇该文:APOCALYPSE用它已经开始Flutter混和合作开发——FlutterBoost。
该文中主要就讲诉了多发动机存在许多实际问题,所以eBay现阶段选用的混和计划是共享资源同两个发动机的计划。而 Flutter Boost 的 Feature 如下表右图:
可F83E43Se全功能混和计划全力支持更加繁杂的混和商业模式,比如说全力支持网页Tab这种情况无侵入性计划:不再倚赖修正Flutter的计划全力支持通用型网页开发周期统一明晰的设计基本概念Flutter Boost 选用共享资源发动机的商业模式来实现,主要就路子是由 Native 罐子 Container 通过消息驱动力 Flutter 网页罐子 Container,从而达到 Native Container 与 Flutter Container 的并行目的。简单的认知,eBay总而言之把 Flutter 罐子弄成应用程序的觉得。核对两个网页门牌号,接着由罐子去管理网页的绘出。在 Native 侧他们只须要重视如果调用罐子,接着增设罐子相关联的网页象征方可。
有鉴于网路上没有相关的网络连接文件格式和采用讲义,我这两天也正好抽时间研究了一下,遂重新整理Seiches,为准。由于字数原因,责任编辑只科学研究 Android 端网络连接与源标识符,iOS 的部分先期有机会则补足该文来传授。
注:责任编辑网络连接的 Flutter Boost 版为 1.12.13,相关联全力支持的 Flutter SDK 版为 1.12.13-hotfixes,是现阶段新一代的版。但 Flutter Boost 版预览之后,网络连接形式和采用形式可能会有许多改变,故参照责任编辑时请看清楚 1.12.13 版。2. 网络连接
2.1 建立 Flutter Module
在已经开始之前,他们须要确保工程建设产品目录如下表右图结构右图:
— flutter_hybrid
— flutter_module
— FlutterHybridAndroid
— FlutterHybridiOS
即,iOS 工程建设与 Android 工程建设与 flutter_module 产品目录在同两个层次。如此,便于管理,同时也确保先期软件系统标识符中方向的连续性。
接着,他们来建立 Flutter Module:
cdflutter_hybrid
flutter create -t module flutter_module
须要注意的是,如果要建立全力支持 Android X 的 flutter module,命令上须要加上 –androidx 参数,即:
flutter create –androidx -t module flutter_module
注:如果安装倚赖过慢,可以切换为国内的倚赖镜像源。
export PUB_HOSTED_URL=https://pub.flutter-io.cnexport FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
当然他们也可以通过新版的 Android Studio 来可视化建立两个 Flutter Module (需 3.6.1 以上版,并给 IDE 装上 Flutter 与 Dart 插件),具体方法可以见官网介绍(https://flutter.dev/docs/development/add-to-app/android/project-setup),此处不再赘述。但个人建议采用责任编辑介绍的更为通用型的方法去建立并软件系统 Flutter Module。
2.2 软件系统 Flutter Module
建立好 Flutter Module 之后,须要在 Native 工程建设中软件系统 flutter_module。具体有两种形式:
源标识符倚赖arr 倚赖2.2.1 源标识符倚赖软件系统
源标识符倚赖的优点是合作开发、调试方便,也就是在 Android 工程建设的 settings.gradle 和 app 产品目录下的 build.gradle 文件中加入对 flutter_module 的倚赖方可。
首先,在 settings.gradle 文件中,增加以下标识符:
include :app // 已存在的内容
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
flutter_module/.android/include_flutter.groovy
))
setBinding 与 evaluate 增加之后他们就可以在 app/build.gradle 中增加 :flutter 倚赖:
…
dependencies {
implementation project(:flutter)
…
}
同时也须要在该文件中的 android() 配置指定一下编译时的 Java 版为 Java 8,否则会报错
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
最后执行一下 gradle sync 下载倚赖库。如果软件系统成功,会在左侧的项目产品目录中看到与项目同级的 flutter_module 文件夹。
2.2.2 arr 倚赖软件系统
如果须要用远程打包,而远程的机器上没有 flutter 环境,就可以把 flutter 打包成 arr 文件进行倚赖。生成 aar 文件之后再在主工程建设里引用,flutter aar 中包含了 flutter sdk 的标识符,所以这种形式是不须要flutter 环境的,也适合第三方快速网络连接。
cd.android/
./gradlew flutter:assembleDebug
2.3 添加 Flutter Boost 倚赖
首先在 Flutter Module 项目中加入 flutter-boost 倚赖,即在 pubspec.yaml 文件中的 dev_dependencies 配置增加 flutter-boost 倚赖:
dev_dependencies:flutter_boost:git:url:https://github.com/alibaba/flutter_boost.gitref:1.12.13
以上 flutter boost 全力支持 AndroidX,如果想全力支持 support,则须要切换分支:
flutter_boost:git:url:https://github.com/alibaba/flutter_boost.gitref:task/task_v1.12.13_support_hotfixes
编辑完之后在 flutter_module 产品目录下执行以下命令安装倚赖。
flutter packages get
之后在 Android 工程建设中的 app 产品目录下的 build.gradle 文件中增加 :flutter_boost 倚赖,
dependencies {
…
implementation project(:flutter)
implementation project(:flutter_boost)
}
因为 Flutter Boost 是以 Flutter Plugin 的形式软件系统到他们的项目中来的,所以他们还须要做许多工作,首先在 app 产品目录下的 build.gradle 文件的头部增加以下标识符:
def localProperties = new Properties()
def localPropertiesFile = rootProject.file(local.properties)
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader(UTF-8) { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty(flutter.sdk)
if (flutterRoot == null) {
throw new GradleException(“Flutter SDK not found. Define location with flutter.sdk in the local.properties file.”)
}
def flutterVersionCode = localProperties.getProperty(flutter.versionCode)
if (flutterVersionCode == null) {
flutterVersionCode = 1
}
def flutterVersionName = localProperties.getProperty(flutter.versionName)
if (flutterVersionName == null) {
flutterVersionName = 1.0
}
这须要他们在 Android 工程建设下的 local.properties 文件中指定以下他们本地的 Flutter SDK 的位置(没有改文件就新建两个):
flutter.sdk = /Users/airing/flutter
最后再在工程建设产品目录下的 settings.gradle 中增加以下标识符引入 flutter-plugin:
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), .flutter-plugins)
if (pluginsFile.exists()) {
pluginsFile.withReader(UTF-8) { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve(android).toFile()
include “:$name”
project(“:$name”).projectDir = pluginDirectory
}
同样的,修正完 Android 工程建设的倚赖之后,须要 gradle sync 一下。
至此,Flutter Boost 软件系统成功。接下来,他们采用 Flutter Boost 进行混和合作开发。而混和合作开发,主要就涉及到两个场景:
在 Native 项目中加入 Flutter 网页,即 Add a single Flutter screen。在 Native 网页中嵌入 Flutter 模块,即 Add a Flutter Fragment。这两种形式在 Flutter 的官网路上都有课堂教学传授,他们这里主要就看看如果采用 Flutter boost 究竟要如何实现的,并顺便探究一下其实现原理。
3. 混和合作开发1: Flutter View
3.1 在 Flutter Module 中采用 Flutter Boost
首先引入倚赖
import package:flutter_boost/flutter_boost.dart;
随后在 main 方法中运行的 rootWidget 中注册两个新的网页,以便 Native 工程建设可以跳转过来。
import package:flutter/material.dart;
import package:flutter_boost/flutter_boost.dart;
import simple_page_widgets.dart;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
first: (pageName, params, _) => FirstRouteWidget(),
second: (pageName, params, _) => SecondRouteWidget(),
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Flutter Boost example,
builder: FlutterBoost.init(),
home: Container(
color:Colors.white
));
}
}
标识符很简单:
在 initState 的时候采用 FlutterBoost.singleton.registerPageBuilders 注册网页在 bulider 中调用 FlutterBoost。3.2 在 Android 工程建设中采用 Flutter Boost
在 Android 项目中增加两个 Flutter 网页,即是添加两个 Flutter Activity(iOS 即是添加两个新的 FlutterViewController,这里不再花字数去传授 iOS 的实现了,有兴趣的同学可以自己去阅读 Flutter Boost 的示例标识符和源标识符)。
这里他们在 AndroidManifest.xml 的 Application 配置中添加两个 Flutter Boost Activity:
<activity
android:name=“com.idlefish.flutterboost.containers.BoostFlutterActivity”
android:theme=“@style/Theme.AppCompat”
android:configChanges=“orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density”
android:hardwareAccelerated=“true”
android:windowSoftInputMode=“adjustResize” >
<meta-data android:name=“io.flutter.embedding.android.SplashScreenDrawable” android:resource=“@drawable/page_loading”/>
</activity>
除此之外还须要在 AndroidManifest.xml 中添加 flutterEmbedding 版增设:
<meta-data android:name=“flutterEmbedding”
android:value=“2”>
</meta-data>
接着要进行调用 FlutterBoost 的工作,建议在 Application 的 onCreate 方法中初始化:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
INativeRouter router = new INativeRouter() {
@Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
String assembleUrl=Utils.assembleUrl(url,urlParams);
PageRouter.openPageByUrl(context,assembleUrl, urlParams);
}
};
FlutterBoost.BoostLifecycleListener boostLifecycleListener= new FlutterBoost.BoostLifecycleListener(){
@Override
public void beforeCreateEngine() {
}
@Override
public void onEngineCreated() {
}
@Override
public void onPluginsRegistered() {
}
@Override
public void onEngineDestroy() {
}
};
Platform platform = new FlutterBoost
.ConfigBuilder(this,router)
.isDebug(true)
.whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
.renderMode(FlutterView.RenderMode.texture)
.lifecycleListener(boostLifecycleListener)
.build();
FlutterBoost.instance().init(platform);
}
}
FlutterBoost 在 Android 工程建设中调用须要进行 4 步工作:
注册路由跳转方法(先期会说 PageRouter 的实现)增加 flutter boost 的开发周期监听函数,可以在 Flutter Engine 建立之前、建立之后、销毁之后与 Flutter Plugin 注册之后回调事件。声明 Flutter boost 配置,把路由和开发周期函数配置上。调用 Flutter boost。接着要在 Android 工程建设中实现两个网页路由的工具类 PageRouter,这里直接摆上 Flutter Boost 示例标识符中的实现了,比较全面:
public class PageRouter {
public final static Map<String, String> pageName = new HashMap<String, String>() {{
put(“first”, “first”);
put(“second”, “second”);
put(“tab”, “tab”);
put(“sample://flutterPage”, “flutterPage”);
}};
public static final String NATIVE_PAGE_URL = “sample://nativePage”;
public static final String FLUTTER_PAGE_URL = “sample://flutterPage”;
public static final String FLUTTER_FRAGMENT_PAGE_URL = “sample://flutterFragmentPage”;
public static boolean openPageByUrl(Context context, String url, Map params) {
return openPageByUrl(context, url, params, 0);
}
public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
String path = url.split(“\\?”)[0];
Log.i(“openPageByUrl”,path);
try {
if (pageName.containsKey(path)) {
Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
if(context instanceof Activity){
Activity activity=(Activity)context;
activity.startActivityForResult(intent,requestCode);
}else{
context.startActivity(intent);
}
return true;
} else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
return true;
} else if (url.startsWith(NATIVE_PAGE_URL)) {
context.startActivity(new Intent(context, NativePageActivity.class));
return true;
}
return false;
} catch (Throwable t) {
return false;
}
}
}
3.3 在 Native 项目中打开 Flutter 网页
调用比较简单,在 Native 网页上的按钮绑定上 onClick 监听来实现点击打开他们注册的 Flutter 中的 first 网页,还可以顺便传上两个 map 参数:
@Override
public void onClick(View v) {
Map params = new HashMap();
params.put(“test1”,“v_test1”);
params.put(“test2”,“v_test2”);
PageRouter.openPageByUrl(this, “first”, params);
}
他们回顾一下他们在 3.1 中 Flutter 中注册网页的标识符,发现有两个 params 参数,没错那就是 Native 打开 Flutter 时传过来的参数,他们可以打印出来或者传给 widget 做额外的处理:
FlutterBoost.singleton.registerPageBuilders({
first: (pageName, params, _) => {
print(“flutterPage params:$params”);
return FirstRouteWidget(params:params);
},
second: (pageName, params, _) => SecondRouteWidget(),
});
3.4 在 Flutter 网页中打开 Native 网页
同样的,他们可能还会遇到一种场景,在 Native 中打开 Flutter 网页之后,他们 Flutter 中的业务又须要再打开两个新的 Native 网页,那须要怎么做?在 Flutter 中采用 FlutterBoost.singleton.open 方可,如下表右图:
// 后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。// 例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open(“sample://nativePage”, urlParams: <dynamic,dynamic>{
“query”: {“aaa”: “bbb”}
}),
当然,这个方法不单单全力支持打开 Native 网页,也可以打开两个新的 Flutter 网页,只须要写好路由名就好,这里不再赘述。
注:得益于 Flutter 的 JIT 编译商业模式,他们可以通过 flutter attach 命令来实现 hot reload 功能,在合作开发 Flutter 网页时无需重新编译工程建设。4. 混和合作开发2:Flutter Fragment
他们假设工程建设中存在两个 Activity,配置如下表右图:
<activity
android:name=“.FlutterFragmentPageActivity”
android:theme=“@style/Theme.AppCompat”
android:configChanges=“orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density”
android:hardwareAccelerated=“true”
android:windowSoftInputMode=“adjustResize”>
<meta-data android:name=“io.flutter.embedding.android.SplashScreenDrawable” android:resource=“@drawable/page_loading”/>
</activity>
而相关联 layout 中他们要加入两个 FrameLayout 组件作为占位符:
<FrameLayout
android:layout_width=“match_parent”
android:layout_height=“0dp”
android:layout_weight=“1”
android:id=“@+id/fragment_stub”/>
最后,在标识符中拿到相关联 url 的 Flutter widget,塞到占位组件里方可:
@Override
public void onClick(View v) {
FlutterFragment mFragment = new FlutterFragment.NewEngineFragmentBuilder().url(“flutterFragment”).build();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_stub, mFragment)
.commit();
}
5. Flutter Boost 源标识符导出
本节主要就简单分析一下 Flutter Boost 的原理,只有知根知底才能用得得心应手。由于字数问题,不可能全部的源标识符都分析一遍,本节只分析具有代表性的源标识符,其余的原理基本一致,留给读者自行阅读。
那本节就从 Dart 端切入,关注其中两个 api,两个是注册网页的 registerPageBuilders,另两个是打开网页的 open,看看 Flutter Boost 是如何实现它们的。
5.1 注册网页
他们在采用 Flutter Boost 的流程中,第一步是要在 Flutter 中注册网页,调用了 registerPageBuilders 函数,那他们来看一下这个函数是如何实现的。
在 flutter_boost.dart 文件中,他们很容易就找到了这个函数的入口:
///Register a map buildersvoid registerPageBuilders(Map<String, PageBuilder> builders) {
ContainerCoordinator.singleton.registerPageBuilders(builders);
}
它调用了 ContainerCoordinator 单例的 registerPageBuilders,那他们接着看 container_coordinator.dart 文件中这个函数的实现:
final Map<String, PageBuilder> _pageBuilders = <String, PageBuilder>{};
PageBuilder _defaultPageBuilder;
///Register page builder for a key.
void registerPageBuilder(String pageName, PageBuilder builder) {
if (pageName != null && builder != null) {
_pageBuilders[pageName] = builder;
}
}
其中 PageBuilder 他们可以找到定义,是两个 Widget,那么这个函数其实就将他们注册的 Widget 塞到两个 Map 里,而他们指定的路由名,就是它的 key。那他们接着要关注的是 _pageBuilders 定义好之后会怎么被采用?
BoostContainerSettings _createContainerSettings(
String name, Map params, String pageId) {
Widget page;
final BoostContainerSettings routeSettings = BoostContainerSettings(
uniqueId: pageId,
name: name,
params: params,
builder: (BuildContext ctx) {
//Try to build a page using keyed builder.
if (_pageBuilders[name] != null) {
page = _pageBuilders[name](name, params, pageId);
}
//Build a page using default builder.
if (page == null && _defaultPageBuilder != null) {
page = _defaultPageBuilder(name, params, pageId);
}
assert(page != null);
Logger.log(build widget:$page for page:$name($pageId));
return page;
});
return routeSettings;
}
可以发现,它在 _createContainerSettings 中 build widget 之后返回两个 routeSetting,该变量在 _nativeContainerWillShow 中被 pushContainer 调用,而 _nativeContainerWillShow 会在 _onMethodCall 中被调用。
bool _nativeContainerWillShow(String name, Map params, String pageId) {
if (FlutterBoost.containerManager?.containsContainer(pageId) != true) {
FlutterBoost.containerManager
?.pushContainer(_createContainerSettings(name, params, pageId));
}
return true;
}
Future<dynamic> _onMethodCall(MethodCall call) {
Logger.log(“onMetohdCall ${call.method}“);
switch (call.method) {
// 省略无关标识符
case “willShowPageContainer”:
{
String pageName = call.arguments[“pageName”];
Map params = call.arguments[“params”];
String uniqueId = call.arguments[“uniqueId”];
_nativeContainerWillShow(pageName, params, uniqueId);
}
break;
}
// 省略无关标识符
}
以上两段标识符的作用是当 Dart 端监听到来自 Native 的通信之后,如果 Native 传递了两个要打开两个网页罐子的信息(willShowPageContainer)之后,FlutterBoost 的罐子管理器就会根据用户注册配置的路由网页去打开两个新的罐子。而这里的 pushContainer 主要就做许多路由管理和绑定监听等操作,我们就不再细看这部分的逻辑了,主要就还是看看 _onMethodCall 的 Native 与 Dart 的互相通信。
5.2 通信
ContainerCoordinator(BoostChannel channel) {
assert(_instance == null);
_instance = this;
channel.addEventListener(“lifecycle”,
(String name, Map arguments) => _onChannelEvent(arguments));
channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
}
Flutter Boost 中负责通信的是 BoostChannel,它的本质上是 MethodChannel 的一层封装,而 MethodChannel 是 Native 与 Flutter 通信的计划之一,有兴趣的同学可以自己查阅 MethodChannel 相关的资料加以了解。
可以阅读 Flutter 官网对 MethodChannel 的介绍:https://flutter.dev/docs/development/platform-integration/platform-channels
5.3 打开网页
最后他们再来看两个打开网页的函数 open,它的实现在库中也容易找到:
Future<Map<dynamic, dynamic>> open(String url,
{Map<dynamic, dynamic> urlParams, Map<dynamic, dynamic> exts}) {
Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
properties[“url”] = url;
properties[“urlParams”] = urlParams;
properties[“exts”] = exts;
return channel.invokeMethod<Map<dynamic, dynamic>>(openPage, properties);
}
可以发现,它的工作其实就是包装好参数后把 openPage 的消息发送给 Native。那他们再来看看 Native 侧接受到这个消息之后作何处理吧!在 Android 侧的 Flutter Boost 源标识符中可以找到 FlutterBoostPlugin.java 这个文件,其中有 MethodChannel 的逻辑来监听 Dart 侧的消息:
class BoostMethodHandler implements MethodChannel.MethodCallHandler {
@Override
public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
FlutterViewContainerManager mManager = (FlutterViewContainerManager) FlutterBoost.instance().containerManager();
switch (methodCall.method) {
// 省略无关的分支
case “openPage”: {
try {
Map<String, Object> params = methodCall.argument(“urlParams”);
Map<String, Object> exts = methodCall.argument(“exts”);
String url = methodCall.argument(“url”);
mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
@Override
public void onResult(Map<String, Object> rlt) {
if (result != null) {
result.success(rlt);
}
}
});
} catch (Throwable t) {
result.error(“open page error”, t.getMessage(), t);
}
}
break;
default: {
result.notImplemented();
}
}
}
}
在收到来自 Dart 的 openPage 消息后,Android 侧的罐子管理器(FlutterViewContainerMananger)会根据 Dart 侧携带来的配置数据打开两个罐子,而这个 openContainer 通过阅读源标识符,可以发现它最后是两个抽象方法,须要他们自己在业务侧实现。回看他们在 3.2 节中在 Android 中初始化 Flutter Boost 第一步工作,做的就是实现这个 openContainer,而它最后交由他们封装的 PageRouter 工具类来实现了,即 context.startActivity()。
至此,他们在 Android 工程建设中软件系统了 Flutter Boost 来实现 Flutter 在 Android 项目中的混和合作开发。责任编辑只是初步分析了下 Flutter Boost 的源标识符,后续有机会会补上详细的分析和 iOS 侧的网络连接采用。敬请期待。