Android与Flutter混合开发-UI交互

前言

  • 本人是做android的,这边只介绍下Android和Flutter的混合开发。
  • 关于原生和Flutter的混合开发,网上有很多相关的文章了,基本上都是使用FlutterView和FlutterFragment的方式去做的,但是在新版Flutter SDK 1.12版本上,Flutter团队把io.flutter.facade.Flutter这个包给删了,上面俩种方式直接凉了,根本无法在Android项目里拿到Flutter对象。所以只能去看官方文档,跑下新的集成方法,这边做个备忘,也同时说下会踩的坑。
  • Flutter官方文档:https://flutter.cn/docs/development/add-to-app
  • 本文章适用于Flutter SDK 1.12版本

注:说明下,这个链接是中文文档,但是还有很多章节还是没翻译,例如将Flutter module集成到Android项目的章节。ios的已被翻译,大佬们都是喜欢玩ios,所以首先翻译ios?

准备工作

新建一个Android项目

  • 这个随意,新建一个Android项目或者使用已有的Android项目都可以。

配置Android项目

  • Flutter 目前只支持 armeabi-v7a 和 arm64-v8a 架构。
android {
  //...
  defaultConfig {
    ndk {
      // 限制下架构,Flutter仅支持:armeabi-v7a 和 arm64-v8a 架构(目前1.12版本)
      abiFilters 'armeabi-v7a', 'arm64-v8a'
    }
  }
}
  • Flutter使用了 Java 8的特性,设置支持Java 8
android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}

新建一个Flutter Module

  • 新建Flutter Module

  • 设置Flutter Module

导入Flutter Module

自动导入

注:Android Studio支持自动导入,前提条件是:Android项目和Flutter Module在同一文件夹下。

  • 选择:“New Module”

  • 选择 “Import Flutter Module”

  • 选择下你的Flutter Module项目文件夹就行了
手动导入

注:只需要设置下就行了,上面自动导入,就是默认生成代码的。

  • MyApp/app/build.gradle,添加下依赖
dependencies {
  implementation project(':flutter')
}
  • MyApp/settings.gradle,settings.gradle加上下述代码,“flutter_module”是项目名称,写上你自己的项目名称(官网上的代码)
include ':app'                                   
setBinding(new Binding([gradle: this]))                            
evaluate(new File(                                                      
  settingsDir.parentFile,                                              
  'flutter_module/.android/include_flutter.groovy'                         
))
  • 一般默认生成的是下述代码形式
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir,
  '../flutter_module/.android/include_flutter.groovy'
))
  • 说明下路径符号相关区别
    • / :根目录
    • ./ :当前目录
    • …/ : 父级目录(上一级目录)
    • settingsDir:获取的是当前项目的全路径,包括项目文件夹
    • settingsDir.parentFile:获取项目文件夹上一级路径

Flutter和Android UI交互

添加单个Flutter页面

使用FlutterActivity的形式,呈现Flutter界面十分方便

  • 前置条件:FlutterActivity 必须在 AndroidManifest.xml 中注册
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/AppTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />
  • 启动Flutter Module默认路由(最简单形式)
Intent intent = FlutterActivity.createDefaultIntent(this);
startActivity(intent);

这样可以直接跳转到Flutter界面,实际是启动FlutterActivity,在FlutterActivity里面加载Flutter的Ui界面。

  • 选择性跳转某个路由界面
Intent intent = FlutterActivity.withNewEngine()
                .initialRoute("/you/route/") //设置你的路由地址
                .build(this);
startActivity(intent);

实际上createDefaultIntent()方法里面就是封装了withNewEngine().build(launchContext)方法,有兴趣的,可以点进代码里面看看。

  • 使用缓存的 FlutterEngine
//使用缓存的FlutterEngine(最大程度地减少启动标准的延迟)
FlutterEngine flutterEngine = new FlutterEngine(this);  //初始路由
//flutterEngine.getNavigationChannel().setInitialRoute("your/route/here"); //自定义路由
//开始执行Dart代码以预热FlutterEngine。
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
//缓存FlutterActivity要使用的FlutterEngine。
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);
Intent intent = FlutterActivity
        .withCachedEngine("my_engine_id")
        .build(this);

//某个按钮的点击事件
findViewById(R.id.flutter_button).setOnClickListener(v -> {
        startActivity(intent);
    });

说明下:实际上FlutterActivity.withNewEngine()…方式跳转,都是新生成一个FlutterEngine对象,每个FlutterEngine都有一个非常重要的预热时间。这意味着启动一个标准的FlutterActivity 会在Flutter体验变得可见之前有一个短暂的延迟。为了最小化这个延迟,我们可以在到达FlutterActivity之前预热FlutterEngine,然后可以使用缓存的FlutterEngine。这种情况在debug安装的情况尤其显著,会有一段时间黑屏,提前缓存好FlutterEngine,可以避免这种情况,提升交互体验,推荐。

添加FlutterFragment

  • 说明:FlutterFragment可用的地方还是蛮多的,可用于显示一个滑动的抽屉、标签式内容、 ViewPager 中的一个页面等等,当然,如果FlutterActivity能解决,建议使用FlutterActivity,因为FlutterActivity更容易使用。

官网原话:If an Activity is equally applicable for your application needs, consider using a FlutterActivity instead of a FlutterFragment, which is quicker and easier to use.

  • 使用FlutterFragment
public class MyActivity extends FragmentActivity {
    private FlutterFragment flutterFragment;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity_layout);

          //直接启动FlutterFragment
        FragmentManager fragmentManager = getSupportFragmentManager();
        //默认路由,相当于:initialRoute("/")
        //FlutterFragment flutterFragment = FlutterFragment.createDefault();
        FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
                .initialRoute("/")   //设置路由
                .build();
        fragmentManager
                .beginTransaction()
                .add(R.id.flutter_ui, flutterFragment, "flutter_fragment")
                .commit();
    }
}
  • R.id.flutter_ui:是布局文件里面一个Fragment的id
  • 比较坑比的地方
    • FlutterFragment无法强转成Fragment类型,涉及到强转的部分会爆红,但是不影响运行。
    • 原因:看了下FlutterFragment,继承Fragment的,子类强转父类是没问题的,但是很坑的是,FlutterFragment里面用的是Support包,我用的是Androidx,导致俩个对象没法没强转。

就是:support.v4.app.Fragment的子类没法强转成androidx.fragment.app.Fragment ? 这个报错真是血坑。


- 解决办法:在下只找到了可能的原因,实在找不到解决之法。可能的解决办法
- 报错就报错吧,留在那,不影响程序运行,佛系点
- 等待Flutter团队,把这个FlutterFragment里面fuck的support包给删掉,换成androidx
- 有其他办法解决,希望评论告知下

  • 使用上面的代码,足以把你的Flutter页面展示出来了
  • 使FlutterFragment周期和Activity同步
public class MyActivity extends FragmentActivity {
    @Override
    public void onPostResume() {
        super.onPostResume();
        flutterFragment.onPostResume();
    }

    @Override
    protected void onNewIntent(@NonNull Intent intent) {
        flutterFragment.onNewIntent(intent);
    }

    @Override
    public void onBackPressed() {
        flutterFragment.onBackPressed();
    }

    @Override
    public void onRequestPermissionsResult(
        int requestCode,
        @NonNull String[] permissions,
        @NonNull int[] grantResults
    ) {
        flutterFragment.onRequestPermissionsResult(
            requestCode,
            permissions,
            grantResults
        );
    }

    @Override
    public void onUserLeaveHint() {
        flutterFragment.onUserLeaveHint();
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        flutterFragment.onTrimMemory(level);
    }
}
  • 使用缓存的 FlutterEngine
//使用缓存的FlutterEngine(最大程度地减少启动标准的延迟)
FragmentManager fragmentManager = getSupportFragmentManager();
FlutterEngine flutterEngine = new FlutterEngine(this);  //初始路由
// flutterEngine.getNavigationChannel().setInitialRoute("/"); //自定义路由
//开始执行Dart代码以预热FlutterEngine。
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);//缓存要使用的FlutterEngine。

//点击事件
findViewById(R.id.flutter_button).setOnClickListener(v -> {
        FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").build();
        fragmentManager
                .beginTransaction()
                .add(R.id.flutter_ui, flutterFragment, "flutter_fragment")
                .commit();
    });

基本上和上面使用FlutterActivity缓存机制一样

  • Flutter官网上写完缓存代码,单独来了这么段代码:
flutterFragment.withCachedEngine("my_engine_id").build();
  • 真是哔哔了狗,我还以为withCachedEngine(“my_engine_id”).build()直接把缓存的FlutterEngine写入到flutterFragment对象里面,实际上,build() 返回的是FlutterFragment对象,上面的代码只是取设置好的FlutterFragment。
    • 猜测下官网上代码意图,难道是这样:
fragmentManager
          .beginTransaction()
          .add(R.id.flutter_ui, flutterFragment.withCachedEngine("my_engine_id").build(), "flutter_fragment")
          .commit();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值