Android原生嵌入Flutter模块

Posted 白玉梁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android原生嵌入Flutter模块相关的知识,希望对你有一定的参考价值。

读这篇文章的前提是,你对Flutter已经有一定了解,或者已经达到会开发Flutter的程度,并且Flutter相关环境已经配置OK!

本文所展示项目的运行环境:

FlutterSDK版本:flutter_windows_2.10.1-stable
androidStudio版本:Bumblebee | 2021.1
AndroidSDK版本:31
Gradle版本:7.2

第一步:在你的安卓原生项目父级目录下),执行命令:

flutter create -t module --org com.example my_flutter

此时会创建出一个包名为com.example,项目名为my_flutter的flutter项目:

注意,flutter项目跟原有项目是同级目录

. 开头的文件或文件夹里的内容,都尽量不要修改,!

第二步:打开原生项目的setting.gradle,在include ':app’下方添加如下代码:

setBinding(new Binding([gradle: this]))                                // new
evaluate(new File(                                                     // new
        settingsDir.parentFile,                                              // new
        'my_flutter/.android/include_flutter.groovy'                         // new
))

第三步:打开原生项目app下的build.gradle,添加如下代码:

dependencies 
	//...
	implementation project(':flutter')

步骤很简单,但当编译时会出现各种问题,如果你遇到一下几种问题,可以找到对应解决办法:

1.如果遇到Android SDK Command-line Tools相关问题,可以在SDK管理器中安装:

2.如果遇到android-licenses相关问题,可以执行命令:

flutter doctor --android-licenses

提示Y/N时,一路选择Y即可!

3.如果遇到:Failed to apply plugin class ‘FlutterPlugin’:

打开setting.gradle,将repositoriesMode改为:RepositoriesMode.PREFER_PROJECT

4.如果遇到引用的第三方库解析失败:

打开setting.gradle,添加maven:

dependencyResolutionManagement 
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
    repositories 
        maven  url 'https://jitpack.io' 
        maven  url 'https://maven.aliyun.com/nexus/content/groups/public/' 
        maven  url 'https://maven.aliyun.com/repository/central' 
        maven  url 'https://maven.aliyun.com/nexus/content/repositories/google' 
        google()
        mavenCentral()
    

打开原生跟目录下的build.gradle,添加maven:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript 
    repositories 
        maven  url 'https://jitpack.io' 
        maven  url 'https://maven.aliyun.com/nexus/content/groups/public/' 
        maven  url 'https://maven.aliyun.com/repository/central' 
        maven  url 'https://maven.aliyun.com/nexus/content/repositories/google' 
        google()
        mavenCentral()
    
    dependencies 
        classpath 'com.android.tools.build:gradle:7.1.1'
    


allprojects 
    repositories 
        maven  url 'https://jitpack.io' 
        maven  url 'https://maven.aliyun.com/nexus/content/groups/public/' 
        maven  url 'https://maven.aliyun.com/repository/central' 
        maven  url 'https://maven.aliyun.com/nexus/content/repositories/google' 
        google()
        mavenCentral()
    


task clean(type: Delete) 
    delete rootProject.buildDir


编译通过后,项目会变成这样:

爆红处可以不用理会,只要运行成功即可!

小提示:

flutter clean命令,可清除生成的.android、.ios、.packages、.dart_tool文件夹;
flutter packages get命令,可重新生成以上文件;
在项目因某些原因运行出错但又找不到其他原因时,可尝试使用上述步骤解决!

打开my_flutter的lib,就可以开发flutter项目页面了!

从原生跳往Flutter页面有多种途径,首先,需要在原生项目的清单文件中声明FlutterActivity:

<activity
    android:name="io.flutter.embedding.android.FlutterActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize" />

在原生页面中,通过startActivity执行跳转:

startActivity(
      FlutterActivity.createDefaultIntent(context)
    );

这是最简单最直接的跳转方式,执行后会直接通过Flutter项目的main入口进入Flutter页面,但其有一个大问题是,进入Flutter页面,就需要先加载Flutter引擎,这就导致跳转过程非常慢,会明显感觉到卡顿,这里依照官方提供的解决方案,我们可以在Application 中预先加载一个引擎缓存起来:

public class MyApplication extends Application 
  
  @Override
  public void onCreate() 
    super.onCreate();
    FlutterEngine flutterEngine = new FlutterEngine(this);
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  


然后跳转时使用已缓存的引擎:

 startActivity(
      FlutterActivity
        .withCachedEngine("my_engine_id")
        .build(currentActivity)
      );

此时,跳转将会变的非常流畅,跟原生跳转无差别!

以上步骤其实已经实现了原生+flutter混合开发的初步逻辑了,但我们知道,实际情况下,我们可能会使用Flutter做多个不同的模块,那不同模块的首页必然也不相同,以上方法则默认通过flutter的main函数进入一个默认的flutter主页,无法满足需求!那该怎么办呢,别急,官方提供了通过路由跳转的方法:

startActivity(
      FlutterActivity
        .withNewEngine()
        .initialRoute("test")
        .build(currentActivity)
      );

此时我们可以在flutter的main.dart中修改main方法如下:

//通过路由启动页面
void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) 
  switch (route) 
    case 'test1':
      return const Test1();
    case 'test2':
      return const Test2();
    case 'test3':
      return const Test3();
    default:
      return const Test1();
  

Test1…是我创建的不同Flutter页面!

此时,你就可以通过initialRoute方法传入不同的路由,跳转到不同的页面!

 //使用新Flutter引擎
 binding.btnRoute.setOnClickListener(view -> 
       String[] routes = new String[]"test1", "test2", "test3";
       new AlertDialog.Builder(this)
               .setItems(routes, (dialogInterface, i) -> 
                   startActivity(FlutterActivity
                           .withNewEngine()
                           .initialRoute(routes[i])
                           .build(MainActivity.this));
               )
               .create()
               .show();
   );

效果如下:

我们发现,跳转过程明显卡顿,这是应为跳转时使用了新的引擎withNewEngine(),那我们是否可以使用已缓存的引擎,去通过路由跳转呢?可以,但不能像使用withNewEngine一样使用withCachedEngine跳转路由,因为withCachedEngine没有initialRoute方法!(关于为什么已缓存的引擎无法在设置初始路由的原因,可以参考官方文档)

Flutter给了我们解决方案,就是在创建引擎时设置初始路由:

flutterEngine = new FlutterEngine(this);
flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
flutterEngine.getDartExecutor().executeDartEntrypoint(
  DartEntrypoint.createDefault()
);
FlutterEngineCache
  .getInstance()
  .put("my_engine_id", flutterEngine);

然后我们就可以像上面通过window.defaultRouteName接收路由的方式一样,跳转对应的flutter页面!

当然,我们还有另外一种思路,即我可以根据Flutter的功能模块,创建多个对应的FlutterEngine,每个FlutterEngine设置不同的入口函数进入不同的Flutter界面:

FlutterEngine flutterEngine1 = new FlutterEngine(this);
String pathToBundle = FlutterInjector.instance().flutterLoader().findAppBundlePath();
flutterEngine1.getDartExecutor().executeDartEntrypoint(new DartExecutor.DartEntrypoint(pathToBundle, "test1"));
FlutterEngineCache.getInstance().put("test1_engine", flutterEngine1);

FlutterEngine flutterEngine2 = new FlutterEngine(this);
flutterEngine2.getDartExecutor().executeDartEntrypoint(new DartExecutor.DartEntrypoint(pathToBundle, "test2"));
FlutterEngineCache.getInstance().put("test2_engine", flutterEngine2);

FlutterEngine flutterEngine3 = new FlutterEngine(this);
flutterEngine3.getDartExecutor().executeDartEntrypoint(new DartExecutor.DartEntrypoint(pathToBundle, "test3"));
FlutterEngineCache.getInstance().put("test3_engine", flutterEngine3);

Flutter的main.dart中:

//通过设置不同启动入口启动页面
void test1() => runApp(const Test1());

void test2() => runApp(const Test2());

void test3() => runApp(const Test3());

通过withCachedEngine执行跳转:

//使用已缓存的Flutter引擎
binding.btn1.setOnClickListener(view -> 
    startActivity(FlutterActivity
            .withCachedEngine("test1_engine")
            .build(MainActivity.this));
);

binding.btn2.setOnClickListener(view -> 
    startActivity(FlutterActivity
            .withCachedEngine("test2_engine")
            .build(MainActivity.this));
);

binding.btn3.setOnClickListener(view -> 
    startActivity(FlutterActivity
            .withCachedEngine("test3_engine")
            .build(MainActivity.this));
);

效果如下:

如图,还是非常流畅的!

当然,还有其他方式如FlutterFragment,FlutterView等,具体可以参考官方文档:

https://flutter.cn/docs/development/add-to-app/android/project-setup

打包release注意事项:

在实际打包release过程中发现了若干问题:

  • Flutter 当前仅支持 为 x86_64,armeabi-v7a 和 arm64-v8a 构建预编(AOT)的库;
  • 如果集成的有umeng多渠道,会导致打包后安装直接闪退;
  • 使用上面介绍的最后一种多引擎不同入口方式,会导致启动flutter界面黑屏;

第一个问题就不说了;
第二个问题目前只能删除AndroidManifest中的umeng的meta-data,同时删除build.gradle中多渠道配置的代码,然后在初始化UMConfigure时手动修改渠道名(稍显麻烦但没办法);
第三个问题,debug模式下没问题,但release就不行,目前不清楚原因,所以可以使用在创建引擎缓存时设置初始路由;

以上是关于Android原生嵌入Flutter模块的主要内容,如果未能解决你的问题,请参考以下文章

如何在flutter中嵌入原生android apis?

ios原生嵌套Flutter模块

Flutter 专题58 图解 Flutter 嵌入原生 AndroidView 小尝试 #yyds干货盘点#

Flutter(六)Android与Flutter混合开发(Hybird)

8-4 Flutter Android混合开发实战-调试与发布

混合开发架构|Android工程集成React NativeFlutterReactJs