Flutter 混合接入实战

Flutter 混合接入是指 Flutter 以依赖库的形式接入现有的 Android 或 iOS 项目。不同于集中式的 Flutter 项目( Flutter 做为主项目,把 Android 和 iOS 项目包括在内),Flutter 混合接入更适合当前已有 Android 和 iOS 项目,以及可能需要与 Flutter 页面产生交互的场景。

混合接入的思路是基于现有的 Android 或 iOS 项目,添加对 Flutter 模块的依赖。以 Android 为例,Android 实现界面依靠 Activity 或 Fragment 组件,组件布局通过 FlutterView 做为 Android 视图容器把 Flutter 实现的界面渲染到屏幕上。Android 和 Flutter 模块通过 Platform Channel 传递消息。Flutter 模块依赖的第三方库通过实现 Android 插件与 Android 相互调用。Flutter 模块添加到现有项目的同时,插件也会注入到现有项目实现互通。

Platform Channel
Platform Channel
Platform Channel
Platform Channel
Android 现有项目
Flutter 模块
iOS 现有项目
Android 插件
iOS 插件

官方对 Flutter 接入现有项目做了说明,详细请参考 Add Flutter to existing app。本文仅演示 AAR 依赖的实现思路,目的是帮助已有 Android 项目快速接入 Flutter,或者期望 Android 和 Flutter 模块可交互的新 Android 项目。

接下来通过步骤演示来说明混合接入的方法。如果还没有 Android 或 iOS 项目,需要先创建一个 Android 或 iOS 项目。

创建 Android 项目

官方对创建 Android 项目做了说明,详细请参考 Create an Android project。但对于即将接入 Flutter 的 Android 项目,有些步骤需要注意。

使用 Android Studio 4.0 创建新项目的过程中,不要勾选 Use legacy android.support libraries。因为 Support Library 不再维护,替换为 AndroidX 。在 Flutter v1.17 之后,Flutter 模块仅支持使用 AndroidX 的 Android 项目。
Create New Project

创建 Flutter 模块

在 Android Studio 中使用 File -> New -> New Module 打开向导。设置项目名,Flutter SDK 路径,项目位置,项目描述后点击下一步。

Configure the New Flutter module
设置 Flutter 模块的包名,现有的 Android 项目支持 AndroidX,勾选 Use androidx.* artifacts,然后完成。
Set the package name

重启 Android Studio,左侧边栏下拉菜单选择 Project Files 视图。如果创建的 Flutter 项目和 Android 项目在同一个目录,就会看到如下图所示的 Android 和 Flutter 项目文件结构。

Project Files

生成 Flutter 模块包

推荐 Android 项目使用依赖 AAR 的方式接入 Flutter。这样的方式接入有明显的优势,Flutter 对 Android 代码侵入最小,Android 只需要依赖 Flutter 编译生成的 AAR 即可。而且 Android 编译不需要依赖 Flutter 环境,更便于多端团队并行开发。

接下来就介绍 Flutter AAR 生成和 Android 依赖 AAR 的方法。点选左侧边栏中的 Flutter 模块路径名称,然后在顶部菜单点击 Build -> Flutter -> Build AAR,Flutter 编译 AAR 的命令就会自动执行,并生成 debug,profile,release 三个版本的 AAR。

Flutter Build AAR

打开 Android 项目目录下 /app/build.gradle 文件,添加两个代码仓库,一个是刚生成的 AAR 的路径,推荐使用相对路径;另一个是公有的代码仓库,便于下载公有的第三方库,墙内推荐使用国内的镜像仓库。以下代码片作为示例参考,合并到 build.gradle 文件中。

android {
    buildTypes {
        // 允许 profile 类型从 debug 类型中复制配置
        profile {
            initWith debug
        }
    }
}
repositories {
    // 指定 Flutter 仓库
    maven {
        url '../../flutter_module/build/host/outputs/repo'
        // 如果使用相对路径,那么该路径是相对于本 build.gradle 文件路径
        // flutter_module 是创建 Flutter Module 时设置的项目名称
    }
    maven {
        // 国内也可以选用 storage.flutter-io.cn 域名作为镜像仓库
        url 'https://storage.googleapis.com/download.flutter.io'
    }
}

dependencies {
    // 添加 Flutter AAR 依赖
    // com.demo.exandroid.fluttermodule 是创建 Flutter Module 时设置的包名
    debugImplementation 'com.demo.exandroid.fluttermodule:flutter_debug:1.0'
    profileImplementation 'com.demo.exandroid.fluttermodule:flutter_profile:1.0'
    releaseImplementation 'com.demo.exandroid.fluttermodule:flutter_release:1.0'
}

如果 dependencies 代码块中包含了自动添加的源码依赖方式的代码,需要注释掉,避免重复依赖。

dependencies {
//    implementation project(path: ':flutter')
}

编辑完成后点击右上方出现的 Sync Now 同步 Android Gradle 配置。如果出现 No Version of NDK matched … ,就点击 Install NDK … ,Android Studio 会自动下载安装 NDK。安装完成后,Android Gradle 会继续执行编译完成。

在 Android 项目中使用 Flutter 引擎

需要修改部分 Android 代码实现 Flutter 界面的显示。打开 Android 项目下的 app 目录,参考示例代码对相应的文件进行创建和修改。

src/main/java/包名/MainActivity.kt

class MainActivity : AppCompatActivity() {
    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.activity_main)

        val fragmentManager: FragmentManager = supportFragmentManager

        // Attempt to find an existing FlutterFragment, in case this is not the
        // first time that onCreate() was run.
        flutterFragment = fragmentManager
            .findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?

        // Create and attach a FlutterFragment if one does not exist.
        if (flutterFragment == null) {
            val newFlutterFragment = FlutterFragment
                .withCachedEngine("my_engine_id")
                .build<FlutterFragment>()
            flutterFragment = newFlutterFragment
            fragmentManager
                .beginTransaction()
                .add(
                    R.id.fragment_container,
                    newFlutterFragment,
                    TAG_FLUTTER_FRAGMENT
                )
                .commit()
        }
    }
}

src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</FrameLayout>

src/main/java/包名/App.kt

class App : Application() {
    private 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(
            DartEntrypoint(
                FlutterMain.findAppBundlePath(),
                "main"
            )
        )

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

src/main/AndroidManifest.xml

    <application
        android:name=".App"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

src/main/res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="@android:style/Theme.Black.NoTitleBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="LaunchTheme" parent="Theme.AppCompat">
        <item name="android:windowBackground">@drawable/ic_splash</item>
    </style>

</resources>

App.kt 是自定义的 Application 子类,没有就新建一个,用于实现 Flutter 引擎预初始化。随后在 Activity 启动时获取 FlutterEngine,加快应用的启动速度。

styles.xml 文件的修改主要涉及去除 Android 项目默认开启的 ActionBar,以避免在 Flutter 接入后,同时看到 Flutter 和 Android 的标题栏。

使用 Fragment 做为 Flutter 界面显示的组件,可以方便以部分屏幕显示 Flutter 界面。

连接真机或虚拟机,点击工具栏的运行按钮,启动应用。

Toolbar

当这个页面出现时,说明 Flutter 已经成功接入到 Android 的现有项目了。

Flutter Hello

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值