Flutter 教程(十三)混合开发

混合开发是指 Flutter 与原生 Android、iOS 一起混合开发的 App。混合开发是为了充分利用 Flutter 在纯界面开发上的优势,以及避免Flutter对底层硬件强相关的开发支持的不足。这里以在Android原生项目中嵌入Flutter为例,iOS 的混合开发可以看 这里

创建 flutter 模块并配置

我们可以使用如下命令来创建 Flutter 模块,其中 flutter_module 是 Flutter 模块的名字。

lua 复制代码
flutter create -t module flutter_module 

创建完成后,我们需要在Android原生项目的 settings.gradle 文件中关联 Flutter 模块,如下所示:

kts 方式使用如下配置

ini 复制代码
include(":app")
val filePath = settingsDir.toString() + "/flutter_module/.android/include_flutter.groovy"
apply(from = File(filePath))

groovy 配置方式如下:

php 复制代码
include(":app")                                   
setBinding(new Binding([gradle: this]))           
def filePath = settingsDir.toString() + "/flutter_module/.android/include_flutter.groovy" 
apply from: filePath                             

再到 app 目录下的 build.gradle 文件中添加 Flutter 模块,如下所示:

scss 复制代码
dependencies {
  // kts
  implementation(project(":flutter"))
  // groovy
  implementation project(':flutter')
  //...其他代码
}

启动 FlutterActivity

Flutter 提供了 FlutterActivity,让我们可以直接启动Flutter 界面。首先我们需要在 AndroidManifest.xml 文件中注册。代码示例如下:

ini 复制代码
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

然后我们创建一个 FlutterEngine,FlutterEngine 是 Flutter 的核心,它用于加载执行 Dart 代码,并参与构建和渲染 UI 界面。创建代码如下:

kotlin 复制代码
class MyApplication : Application() {

    lateinit var flutterEngine: FlutterEngine

    override fun onCreate() {
        super.onCreate()
        // Instantiate a FlutterEngine.
        flutterEngine = FlutterEngine(this)

        // Start executing Dart code to pre-warm the FlutterEngine.
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // Cache the FlutterEngine to be used by FlutterActivity.
        FlutterEngineCache
            .getInstance()
            .put("my_engine_id", flutterEngine)
    }
}

然后我们就可以在代码中启动 FlutterActivity 了,代码示例如下:

less 复制代码
button.setOnClickListener {
        startActivity(
            FlutterActivity
            .withCachedEngine("my_engine_id")
            .build(this))
    }

效果如下图所示:

FlutterFragment

处理 FlutterActivity 外,Flutter 还提供了 FlutterFragment,方便我们把 Flutter 界面作为 Fragment 在 App 中使用。代码示例如下:

kotlin 复制代码
class FlutterFragmentActivity : FragmentActivity() {

    companion object {
        private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
    }


    private var flutterFragment: FlutterFragment? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.my_activity_layout)

        val fragmentManager: FragmentManager = supportFragmentManager

        // 尝试查找现有的 FlutterFragment,以防这不是第一次调用 onCreate() 方法。
        flutterFragment = fragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?

        // 如果 FlutterFragment 不存在,则创建并附加一个新的 FlutterFragment。
        if (flutterFragment == null) {
            var newFlutterFragment = FlutterFragment.createDefault()
            flutterFragment = newFlutterFragment
            fragmentManager
                .beginTransaction()
                .add(
                    R.id.fragment_container,
                    newFlutterFragment,
                    TAG_FLUTTER_FRAGMENT
                )
                .commit()
        }
    }

    override fun onPostResume() {
        super.onPostResume()
        // 调用 FlutterFragment 的 onPostResume() 方法
        flutterFragment?.onPostResume()
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        // 调用 FlutterFragment 的 onNewIntent() 方法,并传入新的意图
        flutterFragment?.onNewIntent(intent)
    }

    override fun onBackPressed() {
        super.onBackPressed()
        // 调用 FlutterFragment 的 onBackPressed() 方法
        flutterFragment?.onBackPressed()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        // 调用 FlutterFragment 的 onRequestPermissionsResult() 方法,传入请求码、权限数组和授权结果数组
        flutterFragment?.onRequestPermissionsResult(
            requestCode,
            permissions,
            grantResults
        )
    }

    override fun onActivityResult(
        requestCode: Int,
        resultCode: Int,
        data: Intent?
    ) {
        super.onActivityResult(requestCode, resultCode, data)
        // 调用 FlutterFragment 的 onActivityResult() 方法,传入请求码、结果码和数据意图
        flutterFragment?.onActivityResult(
            requestCode,
            resultCode,
            data
        )
    }

    override fun onUserLeaveHint() {
        // 调用 FlutterFragment 的 onUserLeaveHint() 方法
        flutterFragment?.onUserLeaveHint()
    }

    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        // 调用 FlutterFragment 的 onTrimMemory() 方法,传入内存清理级别
        flutterFragment?.onTrimMemory(level)
    }
}

效果如下图所示:

更多关于 FlutterFragment 的用法可以看 Flutter 中文文档

嵌入 Flutter 界面

我们还可以通过 FlutterView 来将 Flutter 界面嵌入某个 View 中。代码示例如下:

kotlin 复制代码
class FlutterViewActivity : FragmentActivity() {

    private var flutterEngine: FlutterEngine? = FlutterEngineCache.getInstance().get("my_engine_id")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_flutter_view)
        val flutterView = FlutterView(this)
        val lp = FrameLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        val flContainer = findViewById<FrameLayout>(R.id.container)
        flContainer.addView(flutterView, lp)
        flutterEngine?.let {
            flutterView.attachToFlutterEngine(it)
        }
    }

    override fun onResume() {
        super.onResume()
        flutterEngine?.lifecycleChannel?.appIsResumed()
    }

    override fun onPause() {
        super.onPause()
        flutterEngine?.lifecycleChannel?.appIsInactive()
    }

    override fun onStop() {
        super.onStop()
        flutterEngine?.lifecycleChannel?.appIsPaused()
    }

}

效果如下图所示:

数据传输

Flutter 提供了 MethodChannel 来与 Android 进行数据传输。下面我们来看看如何使用这个类来实现双方的通信。

在 Android 中的代码如下所示:

kotlin 复制代码
private var flutterEngine: FlutterEngine? = FlutterEngineCache.getInstance().get("my_engine_id")
// MethodChannel 是一个双向通信通道,用于在 Flutter 和 Android 之间传递消息
private val methodChannel by lazy {
    val engine = flutterEngine ?: return@lazy null
    MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL)
}

private fun listenFlutterEvent() {
    // 处理来自 Flutter 的方法调用
    methodChannel?.setMethodCallHandler { call, result ->
        when (call.method) {
            // flutter 传输数据过来
            "getFlutterVersion" -> {
                Log.d(TAG, "getFlutterVersion ${call.argument<String>("flutterVersion")}")
            }
            // flutter 调用 Android 的方法
            "getPlatformVersion" -> {
                result.success("Android ${android.os.Build.VERSION.RELEASE}")
            }
            else -> result.notImplemented()
        }
    }
}

其中 setMethodCallHandler 方法用于设置处理来自 Flutter 的方法调用回调。在回调中,我们可以使用 call.argument 方法获取从 Flutter 中传入的值。也可以使用 result.success 设置返回值给 Flutter。

Flutter 端的代码示例如下:

csharp 复制代码
static const String CHANNEL = "com.example.flutterandroidproject/channel";
void getPlatformVersion() async {
// 通过 MethodChannel 获取 platform 的版本信息
String platformVersion = await MethodChannel(CHANNEL).invokeMethod('getPlatformVersion');
setState(() {
  _platformVersion = platformVersion;
});
}

void getFlutterVersion() async {
// 通过 MethodChannel 传递给 Android 当前 flutter 版本信息
await MethodChannel(CHANNEL).invokeMethod('getFlutterVersion', {"flutterVersion": "1.0.0"});
}

其中 invokeMethod 方法,用于从 Flutter 调用 Android 方法。可以看到我们可以通过获取 invokeMethod 的返回值来获取 Android 端返回的数据。

Gradle异常处理

Build was configured to prefer settings repositories over project repositories but repository 'maven' was added by plugin 'dev.flutter.flutter-gradle-plugin'

如果发生了如图所示的构建问题,需要将根目录下的 setting.gradle.kts 中的 repositoriesMode 改成 RepositoriesMode.PREFER_SETTINGS ,代码示例如下:

bash 复制代码
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        ...
    }
}

Could not find io.flutter:flutter_embedding_debug:1.0.0-18b71d647a292a980abb405ac7d16fe1f0b20434.

如果遇到这个错误,我们需要在项目的 setting.gradle.kts 中增加如下代码:

ini 复制代码
pluginManagement {
    repositories {
       maven { url = uri("https://storage.googleapis.com/download.flutter.io") }

        ...
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        maven { url = uri("https://storage.googleapis.com/download.flutter.io") }

        ...
    }
}

参考

相关推荐
pengyu7 分钟前
系统化掌握Dart网络编程之Dio(四):拦截器篇
android·flutter·dart
bst@微胖子1 小时前
Flutter之用户输入&网络数据&缓存
android·flutter·缓存
恋猫de小郭5 小时前
注意,暂时不要升级 MacOS ,Flutter/RN 等构建 ipa 可能会因 「ITMS-90048」This bundle is invalid 被拒绝
android·前端·flutter
前端极客探险家8 小时前
Flutter vs React Native:跨平台移动开发框架对比
flutter·react native·react.js
每次的天空14 小时前
Flutter学习总结之Android渲染对比
android·学习·flutter
LinXunFeng16 小时前
Flutter - Xcode16 还原编译速度
前端·flutter·xcode
小墙程序员19 小时前
Flutter 教程(十四)动画
flutter
leluckys19 小时前
flutter 专题 六十八 Flutter 多图片上传
前端·javascript·flutter
Jalor1 天前
Flutter 与 HarmonyOS NEXT | IAPKit(应用内支付服务)避坑指南
前端·flutter·harmonyos