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

【Flutter 工程化搭建(Android端)】将生成的aar文件引入Android开发工程即可完成aar的引用
到目前为止整个aar的引入基本是可以正常开发的,但是存在问题,那就是在每次开发都需要手动的将生成的aar包复制到主工程中进行依赖,不仅操作麻烦而且会出错,所以讲Flutter打包及引入流程变成日常开发常用的模式是最佳实践
flutter 打包上传流程分析:
为符合日常开发流程,需要将Flutter打成的aar文件上传至maven,因此首要任务就是解决将aar上传至maven问题
查看生成的aar目录下面的pom文件会发现主工程依赖的第三方aar包也会被下载至xx_modlue/build/host/outputs/repo路径下,pom文件如下:
4.0.0 com.xxx.flutter xxx release-0.0.7aar io.flutter flutter_embedding_release 1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408 compile io.flutter armeabi_v7a_release 1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408 compile io.flutter arm64_v8a_release 1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408 compile io.flutter x86_64_release 1.0.0-af51afceb8886cc11e25047523c4e0c7e1f5d408 compile

分析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 donecd "${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> /'${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({ testhome: (String pageName, Map params, String _) => MyHomePage(title: ''), shoppingcar: (String pageName, Map params, String _) { String platformItemNo = ''; if (params.containsKey("platformItemNo")) { platformItemNo = params['platformItemNo']; NativeChat.print(platformItemNo); } return ShoppingCar(platformItemNo: platformItemNo); }, login: (String pageName, Map params, String _) => LoginPage(), overlay: (String pageName, Map 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 = object : HashMap() { 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() params["param"] = param PageRouter.openPageByUrl(this, PageRouter.SHOPPING_CAR, params)

Flutter 测试环境搭建
在混合开发的工程中被吐槽最多的大概就是测试了吧,和native打包在一起调试费时费力,对前端开发要求高需要了解native的基本流程

    推荐阅读