Flutter Native混合开发

1,527 阅读6分钟

Android 集成 Flutter Module

Flutter官方介绍的是,Flutter 可以作为 Gradle 子项目源码或者 AAR 嵌入到现有的 Android 应用程序中。反正现在就我一个人开发,出于方便,就直接导入子项目源码了,编写完代码直接运行即可。

1. Android Studio导入

先介绍下使用Android Studio导入过程,直接贴图:

企业微信截图_16385211784384.png 企业微信截图_16385214915985.png 我是已经提前创建好了Flutter工程,所以用的是 Import Flutter Module,选择已有Flutter工程,点击Finish。

2. gradle编码导入

除了使用AS导入,还可以直接通过修改代码进行集成,AS导入其实就是生成以下代码:

首先,把Flutter 模块作为子项目添加到宿主应用中,在 settings.gradle 中:

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

my_flutter是Flutter工程的文件夹名称,便于管理,建议Android项目和Flutter项目放在同一目录下。

然后,在你的应用中build.gradle引入对 Flutter 模块的依赖:

dependencies {
  implementation project(':flutter')
}

提醒一下: Flutter 当前仅支持 为 x86_64armeabi-v7a 和 arm64-v8a 构建预编(AOT)的库。

android {
  //...
  defaultConfig {
    ndk {
      // Filter for architectures supported by Flutter.
      abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64'
    }
  }
}

至此,Android集成Flutter Module结束。

Android 使用 Flutter

刚开始参照Flutter中文网文档,使用FlutterEngineFlutterEngineCache来创建引擎,对于嵌套多Flutter页面交互通信并不友好,使用麻烦,效果不佳。后来改用闲鱼的FlutterBoost,虽然解决了多Flutter嵌入和通信的问题,但是总体来说,还是有很多问题,再加上Flutter版本更新迭代也太频繁,使用FlutterBoost侵入性太强,后期维护成本高,所以也没有投入使用,只是写一些练练手。

官方宣称在 Flutter 2.0.0 发布之前,FlutterEngine 的多个实例和相关的 UI 可以同时启动,但是每个实例都有明显的延迟和固定的内存占用。多个 Flutter 实例在以下场景有优势:

  • 集成了 Flutter 界面的应用,其位置并不在路由栈的叶子节点上,且其可能是混合路由栈,即 native -> Flutter -> native -> Flutter。
  • 多个 Flutter view 同时集成在同一个页面上,且同时显示。 Flutter 2.0.0 大幅减少了额外的 Flutter 引擎的内存占用,从 Android 上 约 19MB,iOS 上 约 13MB,降至 约 180kB。将固定成本减少了约 99% 后,您可以更自由地将多个 Flutter 集成至您的应用。

Flutter 2.0.0 版本以后,Flutter引入了FlutterEngineGroup,总体看下来,对于Native接入Flutter友好了许多,相关的一些技术方案也成熟了许多。刚好最近有一些全新的需求,借此机会,把Flutter接入到项目中。

关于FlutterEngineFlutterEngineCacheFlutterBoost使用方式就不列举了,只梳理一下我使用FlutterEngineGroup的方式以及使用过程中遇到的一些问题,目前我的使用方式就是上文所述的混合路由栈的模式,native -> Flutter -> native -> Flutter。代码参考官方demo multiple_flutters 来写的,根据个人需求代码有所改动,以下内容为个人工作记录,仅供参考。

1. 初始化FlutterEngineGroup

首先在全局Application中:实例化一个FlutterEngineGroup

class WorkApplication : Application() {
    lateinit var engineGroup: FlutterEngineGroup
    //...
    override fun onCreate() {
        super.onCreate()
        //...
        // 创建FlutterEngineGroup对象
        engineGroup = FlutterEngineGroup(this)
    }
}

2. 创建FlutterEngine工具类

封装一个创建FlutterEngine的工具类,官方demo中工具类中包含了MethodChannel,由于我项目中与原生通信使用的是pigeon,所以我就只保留了创建FlutterEngine相关代码。

/// kotlin
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 WorkApplication
            val dartEntrypoint = DartExecutor.DartEntrypoint(FlutterInjector.instance().flutterLoader().findAppBundlePath(), entryPoint)
            engine = app.engineGroup.createAndRunEngine(context, dartEntrypoint)
            // 2. 存入缓存
            FlutterEngineCache.getInstance().put(engineId, engine)
        }
        return engine!!
    }
}

///dart 
@pragma('vm:entry-point') 
void topMain() => runApp(MyApp());

上述代码中,engineId为缓存的key,entryPoint参数所对应的就是dart中的topMain函数,首先使用DartExecutor.DartEntrypoint函数根据Flutter工程中定义的entryPoint创建出一个DartEntrypoint对象,然后就可以使用engineGroup创建出对应的FlutterEngine

3. 在Android中使用FlutterActivity

通过上述方式创建好FlutterEngine,现在就可以在原生App中去渲染编写好的Flutter UI,参考官方Demo,通过继承FlutterActivity来创建一个用于渲染Flutter UIActivity

@Route(path = ARouterPath.FLUTTER_ACTIVITY)
class SingleFlutterActivity : FlutterActivity(){
    //Flutter Module 对应页面
    private var entryPoint:String = ""
    
    override fun provideFlutterEngine(context: Context): FlutterEngine {
        entryPoint = intent.getStringExtra("entryPoint")?:""
        return FlutterEngineManager.flutterEngine(this,entryPoint)
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        GeneratedPluginRegistrant.registerWith(flutterEngine)
    }
}

继承FlutterActivity然后override父类的provideFlutterEngine方法,这样就完成了Flutter页面整个嵌入Android Activity中了。

4. 在Android中使用FlutterView

除了在Android中创建一个FlutterActivity当做一个单独的页面,还可以在现有页面中的Android View中加入一个FlutterView,实现一个页面中同时存在Android ViewFlutterView的效果。

同样的FlutterView的使用也封装了一个工具类,比较官方Demo也根据我个人习惯做了一下调整:

class FlutterViewManager(private val context: Context, private val engine: FlutterEngine) : LifecycleObserver{
    private var flutterView: FlutterView? = null
    private var activity: ComponentActivity? = null

    fun attachToActivity(activity: ComponentActivity,flutterView: FlutterView) {
        this.activity = activity
        this.flutterView = flutterView
        engine.activityControlSurface.attachToActivity(activity, activity.lifecycle)
        flutterView.attachToFlutterEngine(engine)
        activity.lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    private fun resumeActivity() {
        if (activity != null) {
            engine.lifecycleChannel.appIsResumed()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    private fun pauseActivity() {
        if (activity != null) {
            engine.lifecycleChannel.appIsInactive()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    private fun stopActivity() {
        if (activity != null) {
            engine.lifecycleChannel.appIsPaused()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private fun destroyActivity() {
        if (activity != null) {
            engine.activityControlSurface.detachFromActivity()
            engine.lifecycleChannel.appIsDetached();
        }
        flutterView?.detachFromFlutterEngine()
        activity?.lifecycle?.removeObserver(this)
        activity = null
        flutterView = null
    }

    fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (activity != null && flutterView != null) {
            engine.activityControlSurface.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (activity != null && flutterView != null) {
            engine.activityControlSurface.onActivityResult(requestCode, resultCode, data);
        }
    }

    fun onUserLeaveHint() {
        if (activity != null && flutterView != null) {
            engine.activityControlSurface.onUserLeaveHint();
        }
    }
}

上述方法,其实核心就是通过调用flutterView.attachToFlutterEngine(engine)Flutter UI渲染到Android中的FlutterView上,engine的创建还是参考上述介绍FlutterActivity的方式创建,余下的代码是通过 Jetpack Lifecycle绑定engineActivity的生命周期,和定义一下其他业务场景下所使用的方法。

然后是在Activity中添加FlutterView的使用方式:

private lateinit var flutterEngine: FlutterEngine
private lateinit var flutterViewManager :FlutterViewManager
private lateinit var flutterView : FlutterView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(binding.root)

    flutterEngine = FlutterEngineManager.flutterEngine(mContext,"category")
    flutterView = FlutterView(mContext)
    flutterViewManager = FlutterViewManager(mContext,flutterEngine)
    flutterViewManager.attachToActivity(mActivity,flutterView)
    binding.frameLayout.addView(flutterView)
}

首先,先使用封装的FlutterEngineManager创建要添加到页面的flutterEngine,然后实例化flutterViewflutterViewManager,使用flutterViewManagerflutterEngine渲染到flutterView上,再把flutterView添加到Android原生的View中去。

在使用过程中,遇到了一个当页面中添加了两个或对个FlutterView时,Flutter中的SafeArea不生效的问题,百度搜索了一番,找到的解决方案是:

//解决 多个FlutterView SafeArea不生效
flutterView.requestApplyInsets()

暂时项目中还没有使用到FlutterFragment,虽然尝试写过一些代码去使用,但因为在一些业务场景有些问题,所以没有正式使用到项目中,暂时先不记录了,哪天用过之后再加吧!