Flutter混合开发之FlutterFragment使用

我们知道,原生Android集成Flutter主要有两种方式,一种是创建flutter module,然后以原生module那样依赖;另一种方式是将flutter module打包成aar,然后在原生工程中依赖aar包,官方推荐aar的方式接入。
如何在原生Android工程中以aar的方式接入Flutter,大家可以参考我之前文章的介绍:原生Android工程接入Flutter aar。今天想给大家分享的是FlutterFragment的使用。
一、Android原生工程 在Android原生开发中,实现底部Tab导航通常有3种方式,分别是:

  • RadioGroup + ViewPager + Fragment:能够预加载相邻的Fragment
  • FragmentTabHost + Fragment:加载选中的Fragment
  • BottomNavigationView:有选中动画效果
此处,我们使用BottomNavigationView来实现底部Tab导航。首先,我们新建一个Android原生工程,然后再新建三个 Fragment 。activity_main.xml布局代码如下:

代码中引入了一个bottom_nav_menu.xml布局,代码如下:

其中,BottomNavigationView常用的属性如下:
  • app:iteamBackground:指的是底部导航栏的背景颜色,默认是主题的颜色
  • app:menu:指的是底部菜单(文字和图片都写在这个里面,推荐图片使用矢量图)
  • app:itemTextColor:指的是导航栏文字的颜色
  • app:itemIconTint:指的是导航栏中图片的颜色
最后,在MainActivity.java中实现Tab的切换,代码如下:
class MainActivity : AppCompatActivity() {private var fragments = mutableListOf() private var lastfragment = 0override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)initFragment() initNavigationSelectedListener() }private fun initFragment() { val homeFragment = HomeFragment() val carFragment = CarFragment() val mineFragment = MineFragment() fragments.add(homeFragment) fragments.add(carFragment) fragments.add(mineFragment)supportFragmentManager.beginTransaction() .replace(R.id.fl_container, homeFragment) .show(homeFragment) .commit() }private fun switchFragment(index: Int) { if (lastfragment != index) { val transaction = supportFragmentManager.beginTransaction() //隐藏上个Fragment transaction.hide(fragments[lastfragment]) if (!fragments[index].isAdded) { transaction.add(R.id.fl_container, fragments[index]) } transaction.show(fragments[index]).commitAllowingStateLoss() lastfragment = index } }private fun initNavigationSelectedListener() { findViewById(R.id.bottom_navigation).setOnNavigationItemSelectedListener { item -> when (item.itemId) { R.id.nav_home -> { switchFragment(0) return@setOnNavigationItemSelectedListener true } R.id.nav_car -> { switchFragment(1) return@setOnNavigationItemSelectedListener true } R.id.nav_me -> { switchFragment(2) return@setOnNavigationItemSelectedListener true } } false } } }

二、引入Flutter Module 首先,创建一个Flutter Module工程。创建Flutter Module有两种方式,一种是使用Android Studio进行生成,另一种是直接使用命令行。使用命令行创建flutter module的如下:
flutter create -t module flutter_module

然后,进入到flutter_module,执行flutter build aar命令生成aar包,如果没有任何出错,会在/flutter_module/.android/Flutter/build/outputs目录下生成对应的aar包,如下图。
Flutter混合开发之FlutterFragment使用
文章图片

接下来,我们把生成的aar包拷贝到Android工程的libs中,然后打开app/build.grade添加本地依赖。
repositories { flatDir { dirs 'libs' } }dependencies { ... //添加本地依赖 implementation fileTree(dir: 'libs', include: ['*.jar']) implementation(name: 'flutter_relaese-1.0', ext: 'aar') implementation 'io.flutter:flutter_embedding_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881' implementation 'io.flutter:armeabi_v7a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881' implementation 'io.flutter:arm64_v8a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881' implementation 'io.flutter:x86_64_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881' }

然后在外层的build.gradle中申明为本地依赖,代码如下:
buildscript { repositories { ... maven { url "http://download.flutter.io"//flutter依赖 } }dependencies { classpath 'com.android.tools.build:gradle:4.0.0' } }

三、使用Flutter Module 默认情况下,Android提供了FlutterActivity、Fragment和FlutterView视图,本例子我们讲的是Fragment的使用。
首先,我们创建一个 FlutterEngineGroup 对象,FlutterEngineGroup 可以用来管理多个 FlutterEngine 对象,而多个 FlutterEngine 是可以共享资源的,目的是减少 FlutterEngine 的资源占用,MyApplication的代码如下:
class MyApplication : Application() {lateinit var engineGroup: FlutterEngineGroupoverride fun onCreate() { super.onCreate() // 创建FlutterEngineGroup对象 engineGroup = FlutterEngineGroup(this) } }

接着,创建一个 FlutterEngineManager 缓存管理类,在 FlutterEngineManager 中创建一个静态方法 flutterEngine,用来缓存FlutterEngine。
object FlutterEngineManager {fun flutterEngine(context: Context, engineId: String, entryPoint: String): FlutterEngine { // 1. 从缓存中获取FlutterEngine var engine = FlutterEngineCache.getInstance().get(engineId) if (engine == null) { // 如果缓存中没有FlutterEngine // 1. 新建FlutterEngine,执行的入口函数是entryPoint val app = context.applicationContext as MyApplication val dartEntrypoint = DartExecutor.DartEntrypoint( FlutterInjector.instance().flutterLoader().findAppBundlePath(), entryPoint ) engine = app.engineGroup.createAndRunEngine(context, dartEntrypoint) // 2. 存入缓存 FlutterEngineCache.getInstance().put(engineId, engine) } return engine!! }}

在上面的代码中,我们会先从中获取缓存的 FlutterEngine ,如果没有则新建一个 FlutterEngine ,然后再缓存起来。
接下来,我们将 FlutterEngine 和 FlutterFragment 进行绑定,如果默认没有提供路由,那么打开的是flutter module的路由首页。如果要指定flutter module的首页,可以使用setInitialRoute()方法。
class HomeFragment : Fragment() {// 1. FlutterEngine对象 private lateinit var engine: FlutterEngine private var engineId="home_fra"override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 2. 通过FBFlutterEngineManager获取FlutterEngine对象 engine = FlutterEngineManager.flutterEngine(requireActivity(), engineId, "main") // 3. 用FlutterEngine对象构建出一个FlutterFragment val flutterFragment = FlutterFragment.withCachedEngine(engineId).build() // 4. 显示FlutterFragment parentFragmentManager.beginTransaction().replace(R.id.home_fl, flutterFragment).commit() }override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_home, container, false) } }

我们这里使用缓存的 FlutterEngine 更能节省资源,因为 Bottom Navigation Activity 的 Fragment 来回切换的时候, Fragment 是会重新新建和销毁,比较消耗资源。
如果我们在进入将二级页面时候,返回的时候,还需要将 activity_main.xml 中的 BottomNavigationView 隐藏,涉及的代码如下。
class MainActivity : AppCompatActivity() {...//省略其他代码fun switchBottomView(show: Boolean) { val navView: BottomNavigationView = findViewById(R.id.nav_view) if (show) { navView.visibility = View.VISIBLE } else { navView.visibility = View.GONE } }}

如果要和Flutter进行数据交互,那么我们可以使用MethodChannel,然后使用setMethodCallHandler即可将Android数据回调给Fluter,代码如下。
class HomeFragment : Fragment() {// 1. FlutterEngine对象 private lateinit var engine: FlutterEngine private var engineId="home_fra" private lateinit var channel: MethodChanneloverride fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)initEngine() initChannel() }private fun initEngine() { // 2. 通过FBFlutterEngineManager获取FlutterEngine对象 engine = FlutterEngineManager.flutterEngine(requireActivity(), engineId, "main") // 3. 用FlutterEngine对象构建出一个FlutterFragment val flutterFragment = FlutterFragment.withCachedEngine(engineId).build() // 4. 显示FlutterFragment parentFragmentManager.beginTransaction().replace(R.id.home_fl, flutterFragment).commit() }private fun initChannel() { channel = MethodChannel(engine.dartExecutor.binaryMessenger, "tab_switch") channel.setMethodCallHandler { call, result -> when (call.method) { "showTab" -> { val activity = requireActivity() as MainActivity activity.switchBottomView(true) result.success(null) } "hideTab" -> { val activity = requireActivity() as MainActivity activity.switchBottomView(false) result.success(null) } else -> { result.notImplemented() } } } }override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_home, container, false) }}

接着在Flutter里面是有invokeMethod方法注入即可。
class PluginManager { static const MethodChannel _channel = MethodChannel('tab_switch'); static Future showTab(Map params) async { String resultStr = await _channel.invokeMethod('showTab', params); return resultStr; }}

【Flutter混合开发之FlutterFragment使用】目前原生移动APP可以在应用集成多个 Flutter Module ,这样就方便我们进行多业务的模块化开发了。除了FlutterActivity、Fragment,在Android中可以使用FlutterView 会稍微复杂点,应使用个 FlutterView 需要绑定生命周期,需要开发者自己去管理FlutterView生命周期。

    推荐阅读