乐趣区

关于android:Flutter-工程化搭建Android端

为了踊跃拥抱新技术并优化 RN 的性能问题,所以决定在新业务需要中引入 Flutter 技术栈

Flutter 混合栈开发大抵能够分为一下两种模式

native 工程间接依赖开发

具体接入形式为,先在 setting.gradle 中退出如下代码:

setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir,
        '../../Flutter Module 工程根目录 /.android/include_flutter.groovy'
))

其次在 App 的 build.gradle 中退出如下代码:

 implementation project(':flutter')

最初在主工程的 build.gradle 中退出如下代码即可:

repositories {
buildscript {
        maven {url 'http://download.flutter.io'}
    }
}
    
allprojects {
    repositories {
        maven {url 'http://download.flutter.io'}
    }
}

native 工程接入 aar

新建 Flutter module 工程

flutter create -t module xx_module

目录构造如下

xx_modlue 
          - .android // Android 测试工程
          - .ios  // iOS 测试工程
          - lib  // Flutter 主工程
                - main.dart // Flutter 入口文件
          - pubspec.yaml  // Flutter 三方包配置文件 

Flutter 中提供了将 module 打包成 aar 的命令,生成的 aar 文件门路为 xx_modlue/build/host/outputs/repo

flutter build aar

将生成的 aar 文件引入 Android 开发工程即可实现 aar 的援用

到目前为止整个 aar 的引入根本是能够失常开发的,然而存在问题,那就是在每次开发都须要手动的将生成的 aar 包复制到主工程中进行依赖,不仅操作麻烦而且会出错,所以讲 Flutter 打包及引入流程变成日常开发罕用的模式是最佳实际

flutter 打包上传流程剖析:

为合乎日常开发流程,须要将 Flutter 打成的 aar 文件上传至 maven,因而首要任务就是解决将 aar 上传至 maven 问题

查看生成的 aar 目录上面的 pom 文件会发现主工程依赖的第三方 aar 包也会被下载至 xx_modlue/build/host/outputs/repo 门路下,pom 文件如下:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.xxx.flutter</groupId>
  <artifactId>xxx</artifactId> 
  <version>release-0.0.7</version>
  <packaging>aar</packaging>
  <dependencies>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>flutter_embedding_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>armeabi_v7a_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>arm64_v8a_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.flutter</groupId>
      <artifactId>x86_64_release</artifactId>
      <version>1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

剖析 pom 文件可知在上传主工程生成的 aar 的时候咱们还须要将下载下来的第三方 aar 上传至 maven 库,因而咱们得悉具体工程化脚本流程如下:

1、获取生成的 aar 门路
2、上传第三方依赖的 aar 文件
3、更新主工程 aar 的 artifactId
4、上传主工程 aar 文件

具体脚本如下:

deploy_aar(){
    mvn deploy:deploy-file \
    -DpomFile="$FILE_PATH/$NAME.pom" \
    -DgeneratePom=false \
    -Dfile="$FILE_PATH/$NAME.aar" \
    -Durl="http://xxx.xxx.xxx:xxx/repository/public/" \
    -DrepositoryId="nexus" \
    -Dpackaging=aar \
    -s="mvn-settings.xml" \
    -Dversion="$VERSION"
}

projectDir=`pwd`

# 革除 Flutter 生成文件
flutter clean

# 获取 pub 包
flutter pub get

# 删除文件夹
rm -rf `pwd`/build/host/outputs/repo/


# 批改版本号
group="com.xxx.flutter"
type="release"
#type="debug"
#type="profile"
version="${type}-0.0.7"
artifactId="xxx"


echo "替换 Flutter/build.gradle 中的 group 为 ${group}"
path=`pwd`/.android/Flutter/build.gradle
sed -i '''29s/^.*$/group"'${group}'"/'  ${path}
echo "替换 Flutter/build.gradle 中的 version 为 ${version}"
path=`pwd`/.android/Flutter/build.gradle
sed -i '''30s/^.*$/version"'${version}'"/'  ${path}


# 打包 AAR
flutter build aar --no-debug --no-profile

# 找到 AAR 并上传
path=`pwd`
# shellcheck disable=SC2006
p=`find ${path}/build/host/outputs/repo -type f  -name "*${type}*.aar"`
echo "${p}"


array=(${p//'\n'/})
currentName=""currentPath=""
currentPom=""currentDir=""

# shellcheck disable=SC2068
for item in ${array[@]}
do
    resFile=`basename ${item}`
    echo "${item}"
    result=$(echo ${resFile} | grep "flutter_release")
    if [["$result" == ""]]
    then
      lenght=${#item}
      sub=${item:0:${lenght}-3}
      pom="${sub}pom"
      resFileLenght=${#resFile}
      subDir=${item:0:${lenght}-${resFileLenght}}
      curName=`echo ${resFile} | cut -d "-" -f 2`
      curNameLenght=${#curName}
      subVersion=${curName:0:${curNameLenght}-4}
      nameLenght="${#resFile}"
      subName=${resFile:0:${nameLenght}-4}
      export FILE_PATH="${subDir}"
      export NAME="${subName}"
      export VERSION=${subVersion}
      deploy_aar
    else
      nameLenght="${#resFile}"
      subName=${resFile:0:${nameLenght}-4}
      currentName="${subName}"
      currentPath=${item}
      currentPath=${item}
      lenght=${#item}
      sub=${item:0:${lenght}-3}
      currentPom="${sub}pom"
      resFileLenght=${#resFile}
      subDir=${item:0:${lenght}-${resFileLenght}}
      currentDir=${subDir}
    fi
done

cd "${currentDir}"
echo `pwd`
mv "${currentName}.aar" "${artifactId}-${version}.aar"
mv "${currentName}.pom" "${artifactId}-${version}.pom"
cd ${projectDir}
echo `pwd`

currentName="${artifactId}-${version}"
currentPath="${currentDir}${currentName}.aar"
currentPom="${currentDir}${currentName}.pom"
echo "current name is ${currentName}"
echo "current path is ${currentPath}"
echo "currentPom is ${currentPom}"
echo "替换 pom artifactId 为 ${artifactId}"
sed -i '''6s/^.*$/  <artifactId>'${artifactId}'<\/artifactId> /'  ${currentPom}
echo "currentDir is ${currentDir}"
echo "currentVersion is ${version}"


export FILE_PATH="${currentDir}"
export NAME="${currentName}"
export VERSION=${version}
deploy_aar

上传 maven 胜利后,主工程依赖 Flutter 代码就和增加第三方 SDK 流程统一了。

选型比照

名称 长处 毛病
native 工程间接依赖开发 接入快 工程结构复杂,无奈将 Flutter 开发从 native 开发流程中剥离
native 工程接入 aar Flutter 开发与 native 开发流程解耦 初期接入流程简单

最终抉择为通过 maven 形式接入 aar 不便后续拓展

Flutter 混合栈选型

在实现 Flutter 混合开发接入流程后,会有混合栈治理问题,在混合计划中解决的次要问题是如何去解决交替呈现的 Flutter 和 Native 页面。综合目前的开源框架,选型为 FlutterBoost

flutterBoost Flutter 端接入:

FlutterBoost.singleton.registerPageBuilders(<String, PageBuilder>{testhome: (String pageName, Map<dynamic, dynamic> params, String _) =>
          MyHomePage(title: ''),
      shoppingcar: (String pageName, Map<dynamic, dynamic> params, String _) {
        String platformItemNo = '';
        if (params.containsKey("platformItemNo")) {platformItemNo = params['platformItemNo'];
          NativeChat.print(platformItemNo);
        }
        return ShoppingCar(platformItemNo: platformItemNo);
      },
      login: (String pageName, Map<dynamic, dynamic> params, String _) =>
          LoginPage(),
      overlay: (String pageName, Map<dynamic, dynamic> params, String _) =>
          OverlayPage(),});

android 端接入:

application 初始化代码:

val router =
            INativeRouter { context, url, urlParams, requestCode, exts ->
                PageRouter.openPageByUrl(context, url, urlParams)
            }

        val boostLifecycleListener = object : FlutterBoost.BoostLifecycleListener {override fun onEngineCreated() { }

            override fun onPluginsRegistered() {}

            override fun beforeCreateEngine() {}

            override fun onEngineDestroy() {}

        }

        val platform = FlutterBoost.ConfigBuilder(application, router)
            .isDebug(BuildConfig.DEBUG)
            .whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
            .renderMode(FlutterView.RenderMode.texture)
            .lifecycleListener(boostLifecycleListener)
            .build()
        FlutterBoost.instance().init(platform)

路由配置代码

// PageRouter 路由跳转及配置页面
object PageRouter {
    /**
     * 路由映射
     */
    val pageName: HashMap<String?, String?> =
        object : HashMap<String?, String?>() {
            init {put("xxxx://shoppingCar", "shoppingCar")
                put("xxxx://login", "login")
                put("xxxx://home", "home")
                put("xxxx://overlay", "overlay")
            }
        }
    const val SHOPPING_CAR = "xxxx://shoppingCar"
    const val LOGIN_PAGE = "xxxx://login"
    const val OVERLAY = "xxxx://overlay"
    const val BUYER_PRODUCT_DETAIL = "xxxx://buyer/productdetail"
    const val TEST_SECOND = "xxxx://testSecond"

    @JvmOverloads
    fun openPageByUrl(
        context: Context,
        url: String,
        params: Map<*, *>?,
        requestCode: Int = 0
    ): Boolean {val path = url.split("\\?").toTypedArray()[0]
        Log.i("openPageByUrl", path)
        return try {
            when {pageName.containsKey(path) -> {
                    val intent =
                        BoostFlutterActivity.withNewEngine().url(pageName[path]!!)
                            .params(params!!)
                            .backgroundMode(BoostFlutterActivity.BackgroundMode.opaque)
                            .build(context)
                    if (context is Activity) {context.startActivityForResult(intent, requestCode)
                    } else {context.startActivity(intent)
                    }

                    return true
                }
                url.startsWith(TEST_SECOND) -> {
                    context.startActivity(
                        Intent(
                            context,
                            SecondActivity::class.java
                        )
                    )
                    return true
                }
                else -> false
            }
        } catch (t: Throwable) {false}
    }
}

native 跳转逻辑

// 初始化 channel 告诉

 FlutterBoost.instance().channel().addMethodCallHandler { call, result ->
            when (call.method) {
                "baseUrl" -> {result.success(ApiConstant.getApiUrl())
                }
            }
        }

// 跳转代码

 val params = hashMapOf<String, String>()
            params["param"] = param
            PageRouter.openPageByUrl(this, PageRouter.SHOPPING_CAR, params)

Flutter 测试环境搭建

在混合开发的工程中被吐槽最多的大略就是测试了吧,和 native 打包在一起调试费时费力,对前端开发要求高须要理解 native 的根本流程

退出移动版