简述Flutter集成到Android原生项目

开发集成环境
[✓] Flutter (Channel stable, v1.12.13+hotfix.9, on Mac OS X 10.14.6 18G103, locale zh-Hans-CN)

接触使用Flutter也有段时间了,利用假期来梳理下在Flutter在使用层面的一些混编知识点,其实整理起来会发现原生与Flutter的混编原生与RN的混编相似度很大,其实所有的跨平台框架谈到和原生混编核心也就是方法调用和消息通信,自然很相似了,这里简单列以下几点(前几点方便你前期技术调研方案可行性研究,便于你快速去创建demo落实到项目中进行试手,后期再逐步梳理Dart语法笔记以及Flutter开发中的笔记)(这里就不讲述Flutter的环境搭建了):
一、简述Flutter集成到Android原生项目
二、Android原生以AAR形式集成Flutter项目
三、Flutter与原生(Android/IOS)的消息通信
四、Flutter中如何使用原生控件/组件
五、Flutter状态管理Provider与Redux
六、Flutter升级及开发中遇到的问题汇总

image.png

在现有Android项目中集成Flutter项目,可参考官网方法Add Flutter to existing app
当然也可按照下列步骤操作即可。

1、 新建Flutter项目,选择Flutter Module类型

  • 通过Android Studio 选择 File -> New -> New Flutter Project -> Flutter Module 创建。
  • 通过命令行 $ flutter create -t module my_flutter 创建。
    创建完成后,Flutter Module可正常运行到设备上。

2、 在Android项目中集成Flutter项目

在宿主项目app下的的 build.gradle 里面,android {} 下修改:

android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}

如何在原生项目中引入Flutter模块

方式一:主module通过模块依赖方式来依赖flutter

将flutter作为module,然后native主工程引入进来。这种方式适合参与人数比较少的项目,如果有多人协作开发的大型项目就不合适了,因为其他人首先要配置Flutter环境,而且团队里面其他人还要配置module的依赖,都要熟悉flutter,成本是很高的。

  • 在工程的settings.gradle增加以下配置:
    // 加入下面配置
    setBinding(new Binding([gradle: this]))
    evaluate(new File(
            settingsDir.parentFile,
            'my_flutter/.android/include_flutter.groovy' //更改成自己的项目目录
    ))
    
  • 在app 的gradle里添加依赖:
    implementation project(':my_flutter')
    
    弊端:
    这种方式适合参与人数比较少的项目,如果有多人协作开发的大型项目就不合适了,因为其他人首先要配置Flutter环境,而且团队里面其他人还要配置module的依赖,都要熟悉flutter,成本是很高的。所以还需要以依赖jar/aar的方式来集成。
方式二:通过aar包引入
  • 将Flutter module打包成aar文件:
    进入根目录下的.android目录下执行./gradlew assembleRelease 编译成功后会在.android/Flutter/build/outputs/aar/flutter-release.aar 生成aar文件。 (此种方式生成的aar包之前还能用,当前版本会报错,稍后会提供通过使用fat-aar使用脚本打包方式)
    注意:暂时以第一种方式集成,稍后在【二、Android原生以AAR形式集成Flutter项目】 会详细讲解方式二的使用。

3、在Android项目中加载Flutter页面:

  • 继承FlutterActivity加载Flutter页面
    如果初期只是为了加载出对应的Flutter页面,简单点可不继承FlutterActivity创建自己的Activity,可直接使用FlutterActivity
    public class MyFlutterActivity extends FlutterActivity {
    
      // 定义Channel名称
      private static final String CHANNEL_NATIVE = "com.cc.flutter/native";
    
     public static void openFlutter(Activity activity, String routerUrl){
          Intent intent = MyFlutterActivity.withNewEngine()
                .initialRoute(routerUrl)
                .build(activity);
          //设置Activity透明
          //intent.putExtra("background_mode","transparent");
          activity.startActivity(intent);
      }
    
      public static CCEngineIntentBuilder withNewEngine() {
          return new CCEngineIntentBuilder(MyFlutterActivity.class);
      }
    
      public static CCEngineIntentBuilder withNewEngine(Class<? extends FlutterActivity> activityClass) {
          return new CCEngineIntentBuilder(activityClass);
      }
    
      public static class CCEngineIntentBuilder extends NewEngineIntentBuilder {
    
        protected CCEngineIntentBuilder(Class<? extends FlutterActivity> activityClass) {
              super(activityClass);
          }
      }
    
      @Override
      public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
          GeneratedPluginRegistrant.registerWith(flutterEngine);
          //此处是Flutter与原生通信方法,暂时可不关注
          MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor(), CHANNEL_NATIVE);
          methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
                System.out.println("MethodChannel call.method:"+call.method+ "  call arguments:"+call.arguments.toString());
                switch (call.method){
                    case "envType":
                        result.success(2);
                        break;
                    default:
                        result.error("404", "未匹配到对应的方法"+call.method, null);
                }
            }
        });
    }
    
    此处的FlutterActivity使用的是import io.flutter.embedding.android.FlutterActivity; 而不是io.flutter.app.FlutterActivity , 貌似是说为了更好地支持将 Flutter 添加到现有项目的执行环境,托管 Flutter 运行时的旧版 Android 平台端包装器位于 io.flutter.app.FlutterActivity 及其关联的类现在已弃用。新的包装器 io.flutter.embedding.android.FlutterActivity 及相关类替代了他们。
    其实Flutter 1.2升级了很多东西,具体可参考 Upgrading pre 1.12 Android projects 一步步操作。(稍后我也会梳理在开发中需要操作的升级点及碰到的问题)
  • AndroidManifest注册Activity:
    <!--flutter相关 start-->
          <activity android:name=".activity.flutter.MyFlutterActivity"
              android:launchMode="singleTop"
              android:screenOrientation="portrait"
              android:windowSoftInputMode="adjustPan" />
    
          <activity
              android:name="io.flutter.embedding.android.FlutterActivity"
              android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
              android:hardwareAccelerated="true"
              android:theme="@style/AppTheme"
              android:windowSoftInputMode="adjustResize" >
          </activity>
          <meta-data
              android:name="flutterEmbedding"
              android:value="2" />
          <!--flutter相关 end-->
    
  • Flutter中添加如下代码:
    import 'dart:ui';
    import 'package:flutter/material.dart';
    
    void main() => runApp(_widgetForRoute(window.defaultRouteName));
    
    Widget _widgetForRoute(String route) {
      switch (route) {
        case 'route1':
          return MaterialApp(
            debugShowCheckedModeBanner: false,
            home: Scaffold(
              appBar: AppBar(
                title: Text('MyFlutter页面'),
                centerTitle: true,
              ),
              body: Center(
                child: Column(
                  children: <Widget>[
                    Text('Flutter页面,route=$route, params=$paramsJson'),
                    RaisedButton(
                      textColor: Colors.blue,
                      child: Text("跳转原生页面"),
                      onPressed:(){
                        // 跳转原生页面
                        Map<String, dynamic> result = {'name': '你好,${params["name"]}'};
                        nativeChannel.invokeMethod('jumpToNative', result);
                      },
                    ),
                  ],
                ),
              ),
            ),
          );
        default:
          return Center(
            child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
          );
      }
    }
    

以上是Android原生项目中集成Flutter步骤。


这样就实现了 Android 原生跳转到 Flutter 页面进行渲染, 可以边开发,边编译看效果了。

注意:文中暂时以第一种方式集成,稍后在【二、Android原生以AAR形式集成Flutter项目】 会详细讲解方式二的使用。

推荐阅读更多精彩内容