Android工程内嵌Flutter篇 - 掘金 (juejin.cn)
本篇采用FlutterBoost框架,实现原生与Flutter之间的互相调用。
一、FlutterBoost简介
(A)简介
新一代Flutter-Native混合解决方案。 FlutterBoost是阿里系咸鱼技术团队开源的Flutter插件,它可以轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter像Webview那样来使用。 FlutterBoost帮开发者处理页面的映射和跳转,开发者只需关心页面的名字和参数即可(通常可以是URL)
(B)为什么采用FlutterBoost
- 官方的集成方案有诸多弊病,eg:日志不能输出到原生端、存在内存泄漏的问题、资源冗余……
- FlutterBoost 的通道的封装使得 Native 调用 Flutter 、Flutter 调用 Native 的开发更加简便
- FlutterBoost 对于页面生命周期的管理,做到精准通知
- FlutterBoost 为阿里出品,已在闲鱼生产环境中使用,正稳定为亿级用户提供服务
- iOS和Android双端接口设计统一
- Android不需要区分androidx 和support
- 不入侵引擎:flutter sdk升级不需要升级boost
二、FlutterBoost集成
(A)Flutter端需集成
/**
* 1.添加FlutterBoost依赖到yaml文件并编译
*/
dependencies:
flutter:
sdk: flutter
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '4.2.0'
/**
* 2.main.dart页面添加
*/
void main() {
///这里的CustomFlutterBinding调用务必不可缺少,用于控制Boost状态的resume和pause
CustomFlutterBinding();
、、、
}
///创建一个自定义的Binding,继承和with的关系如下,里面什么都不用写
class CustomFlutterBinding extends WidgetsFlutterBinding with BoostFlutterBinding {}
(B)Android端需集成
/**
* 1.app模块的Build.gradle文件中(如果是多模块,可以在所有模块都引用的Base模块中)
添加FlutterBoost模块引用
*/
dependencies {
、、、
implementation project(':flutter_boost')
}
/**
* 2.app模块manifest文件添加如下代码(多模块也是在这边添加)
*/
<application>
、、、
<activity
android:name="com.idlefish.flutterboost.containers.FlutterBoostActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:theme="@style/Theme.AppCompat"
android:windowSoftInputMode="adjustResize"></activity>
<meta-data
android:name="flutterEmbedding"
android:value="2">
</meta-data>
</application>
三、使用FlutterBoost实现原生与Flutter之间的互相调用
(A)Android调用Flutter并传值
/**
* 1.Flutter端入口mian.dart中配置路由
*/
class _MyAppState extends State<MyApp> {
Map<String, FlutterBoostRouteFactory> routerMap = {
'indexPage': (settings, uniqueId) {
return PageRouteBuilder<dynamic>(
settings: settings,
pageBuilder: (_, __, ___) => const IndexPager());
},
'loginPage': (settings, uniqueId) {
return PageRouteBuilder<dynamic>(
settings: settings,
pageBuilder: (_, __, ___) => LoginPager());
},
'withParamPage': (settings, uniqueId) {
return PageRouteBuilder<dynamic>(
settings: settings,
pageBuilder: (_, __, ___) => WithParamPage(uniqueId, settings.arguments as Map<String, dynamic>));
},
};
、、、
}
/**
* 2.Android端Application中添加跳转Flutter页面的方法
*/
@Override
public void onCreate(){
super.onCreate();
FlutterBoost.instance().setup(application, new FlutterBoostDelegate() {
@Override
public void pushNativeRoute(FlutterBoostRouteOptions options) {
、、、
}
@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 -> {
});
}
/**
* 3.android端点击事件跳转Flutter页面
*/
findViewById(R.id.btn_to_flutter_home_pager).setOnClickListener(v ->
FlutterBoost.instance().open(new FlutterBoostRouteOptions.Builder()
.pageName("indexPage")
.arguments(new HashMap<>())
.build()));
findViewById(R.id.btn_to_flutter_login_pager).setOnClickListener(v ->
FlutterBoost.instance().open(new FlutterBoostRouteOptions.Builder()
.pageName("loginPage")
.arguments(new HashMap<>())
.build()));
findViewById(R.id.btn_to_flutter_with_param).setOnClickListener(v -> {
Map<String, Object> params = new HashMap<>();
params.put("string", "我是原生传过来的数据");
params.put("bool", true);
params.put("int", 666);
FlutterBoost.instance().open(new FlutterBoostRouteOptions.Builder()
.pageName("withParamPage")
.arguments(params)
.build());
});
(B)Flutter调用Android并传值
/**
* 1.Android端Application中添加跳转原生页面的路由
*/
public void onCreate() {
super.onCreate();
sApplication = this;
FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
@Override
public void pushNativeRoute(FlutterBoostRouteOptions options) {
Log.i(TAG,"options.pageName="+options.pageName());
Log.i(TAG,"options.arguments="+options.arguments());
switch (options.pageName()){
case "native_main":
//这里根据options.pageName来判断你想跳转哪个页面,这里简单给一个
Intent intent = new Intent(FlutterBoost.instance().currentActivity(), TestActivity.class);
intent.putExtra("data", (String) options.arguments().get("data"));
intent.putExtra("mobile", (String) options.arguments().get("mobile"));
FlutterBoost.instance().currentActivity().startActivity(intent);
break;
}
}
@Override
public void pushFlutterRoute(FlutterBoostRouteOptions options) {
、、、
}
}, engine -> {
});
}
/**
* 2.Flutter点击事件跳转原生页面
*/
onPressed: () {
BoostNavigator.instance.push("native_main", arguments: {'data': "我是flutter传过来的参数",'mobile': "15821963583"});
},
(C)Flutter与Flutter之间调用
//Flutter之间跳转
onPressed: () {
BoostNavigator.instance.push("loginPage");
},
//返回上一页
onPressed: () {
BoostNavigator.instance.pop();
},
四、原生跳转Flutter会出现白屏或者黑屏
解决方案一
官方给出的方案是类似启动Android工程时,添加Splash页面,如下图:
具体解决方案:
/**
* Android端manifest中
*/
<activity
android:name="com.idlefish.flutterboost.containers.FlutterBoostActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" >
<!--低端机上flutter-boost仍会出现跳转黑屏现象,这种方法可以解决,原理就是类似于原生闪屏页-->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/flutter_launch_background" />
</activity>
/**
* @drawable/flutter_launch_background
*/
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<item android:bottom="65dp">
<bitmap
android:gravity="bottom|center"
android:scaleType="centerCrop"
android:src="@mipmap/launch_flutter_bg"
android:tileMode="disabled" />
</item>
</layer-list>
此方案有两个问题
1.冷启动,温启动,热启动都会展示配置的闪屏页面。
2.只能配置静态图片
解决方案二
自定义SplashScreen
在FlutterBoostActivity的父类FlutterActivity中,方法provideSplashScreen()就是用来解决原生跳Flutter白屏问题的。只要重写该方法就可解决黑白屏问题。
@Override
public SplashScreen provideSplashScreen(){
return new ASplashScreen();
}
class ASplashScreen implements SplashScreen{
View splashView;
@Nullable
@Override
public View createSplashView(@NonNull Context context, @Nullable Bundle savedInstanceState)){
splashView = LayoutInflater.from(context).inflate(R.layout.view_splash, null);
return splashView;
}
@Override
public void transitionToFlutter(@NonNul Runnable onTransitionComplete){
if(splashView != null) splashView.setVisibility(View.GONE);
}
}
/**
* 这里加了简单的ProgressBar用来loading
*/
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
/**
* 这里采用了Lottie动画,具体使用到github上自行查阅
* implementation 'com.airbnb.android:lottie:6.0.0'
*/
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<RelativeLayout
android:layout_width="120dp"
android:layout_height="60dp"
android:background="@drawable/shape_white_bg_4_corner"
android:elevation="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.airbnb.lottie.LottieAnimationView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp"
app:lottie_autoPlay="true"
app:lottie_fileName="loading.json"
app:lottie_loop="true" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
五、关于包大小对比
这里使用空项目作比较
APP单独包:2.5M
APP内嵌Flutter:41M
单独Flutter包:20M