Android工程调用Flutter篇(FlutterBoost框架)

2,277 阅读3分钟

Android工程内嵌Flutter篇 - 掘金 (juejin.cn)

本篇采用FlutterBoost框架,实现原生与Flutter之间的互相调用。

一、FlutterBoost简介

(A)简介
新一代Flutter-Native混合解决方案。 FlutterBoost是阿里系咸鱼技术团队开源的Flutter插件,它可以轻松地为现有原生应用程序提供Flutter混合集成方案。FlutterBoost的理念是将Flutter像Webview那样来使用。 FlutterBoost帮开发者处理页面的映射和跳转,开发者只需关心页面的名字和参数即可(通常可以是URL)

(B)为什么采用FlutterBoost

  1. 官方的集成方案有诸多弊病,eg:日志不能输出到原生端、存在内存泄漏的问题、资源冗余……
  2. FlutterBoost 的通道的封装使得 Native 调用 Flutter 、Flutter 调用 Native 的开发更加简便
  3. FlutterBoost 对于页面生命周期的管理,做到精准通知
  4. FlutterBoost 为阿里出品,已在闲鱼生产环境中使用,正稳定为亿级用户提供服务
  5. iOS和Android双端接口设计统一
  6. Android不需要区分androidx 和support
  7. 不入侵引擎: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之间的互相调用

image.png (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页面,如下图:

image.png 具体解决方案:

/**
 * 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

image.png