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") }

        ...
    }
}

参考

相关推荐
火柴就是我1 天前
flutter 之真手势冲突处理
android·flutter
Speed1231 天前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭1 天前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone1 天前
从flutter源码看其渲染机制
android·flutter
ALLIN2 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei2 天前
Flutter 国际化
flutter
Dabei2 天前
Flutter MQTT 通信文档
flutter
Dabei2 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉2 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter