17

flutter-project-manage

 4 years ago
source link: http://w4lle.com/2019/11/23/flutter-project-manage/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

flutter-project-manage

本文主要介绍Android视角下在已有 App 中嵌入 Flutter 应用的实践,iOS 的方案思路基本一致,实现细节不详细阐述。

Flutter 工程类型

Flutter工程中,通常有以下几种工程类型,下面分别简单概述下:

1. Flutter Application

​ 纯净的Flutter App工程,包含标准的Dart层与Native平台层(android/&ios/)

2. Flutter Module

​ Flutter模块工程,仅包含Dart层实现,Native平台子工程的作用是构建Flutter产物,是通过Flutter自动生成的隐藏工程(.android/&.ios/)

3. Flutter Plugin

​ Flutter平台插件工程,包含Dart层与Native平台层的实现(android/&ios/),往外提供API接口

4. Flutter Package

​ Flutter纯Dart插件工程,仅包含Dart层的实现,往往定义一些公共Widget

接入Flutter工程的两种方式:

  1. 源码集成,在原生项目中Flutter作为lib直接嵌入Flutter代码,编译过程需要依赖Flutter环境,每个开发都需要配置Flutter开发环境,适合全新项目
  2. Flutter项目作为子项目module,生成aar后由原生App依赖,对于App来说屏蔽了Flutter开发环境,在原有环境中即可打包,对App开发来说屏蔽了Flutter环境,适合已经App嵌入Flutter应用

本文主要介绍第二种方式,由于Flutter官方并未提供完整的解决方案,所以接入过程中会碰到一些问题,这里给出一些解决思路供参考。

创建一个flutter module:

flutter create -t module –org com.example my_flutter

工程结构:

zAr6BnI.png!web

FlutterModule 构建 Aar

flutter build aar

但是这个命令在Flutter v1.7.8版本中会提示找不到这个命令,估计是dev版本新加入的,还没有stable。

19个小时前加入的 https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps/_compare/1f606754ee0b18d9970e5fdd7b14d8a6df8d2d72...d648f6b063a0ab7ad4eaf0546e90b1cc75b9d58b

即使没有这个命令,也可以使用./gradlew assembleDebug进行编译,结果是一样的。

如果使用flutter build aar命令,Flutter官方给出的结果是,本地作为localmaven,生成的结构如下:

build/host/outputs/repo

└── com

└── example

└── my_flutter

└── flutter_release

├── 1.0

│   ├── flutter_release-1.0.aar

│   ├── flutter_release-1.0.aar.md5

│   ├── flutter_release-1.0.aar.sha1

│   ├── flutter_release-1.0.pom

│   ├── flutter_release-1.0.pom.md5

│   └── flutter_release-1.0.pom.sha1

├── maven-metadata.xml

├── maven-metadata.xml.md5

└── maven-metadata.xml.sha1

Flutter v1.2版本之后Flutter产物自动会打包到aar中,具体脚本见 flutter.gradle,主要做了三件事情:

  1. 选择符合对应架构的Flutter引擎(flutter.so)
  2. 解析 .flutter-plugins文件,把Flutter对应的android module动态添加到Native工程的依赖中,即动态添加implementation依赖,这块后面会详细讲
  3. Hook mergeAssets/processResources Task,预先执行FlutterTask,调用flutter命令编译Dart层代码构建出flutter_assets产物,并拷贝到assets目录下

基于Flutter v1.7.8,编译产物 Debug版本

mYfiMvm.png!web

Release版本

A3UveaJ.png!web

在Flutter v1.7之前,Release的产物如下:

3I7juqy.png!web

也就是说Flutter的编译产物, 从四个二进制文件变成了一个 libapp.so 文件

这里也涉及到so兼容arm的问题,之前把libflutter.so拷贝到arm目录下即可,现在编译出的libapp.so拷贝过去是有问题的,解决办法可以参考 这个项目

如果在Flutter module 中依赖了Flutter Plugin,那么在App中依赖Flutter module编译出的aar时,会报错,例如

ERROR: Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve io.flutter.plugins.sharedpreferences:shared_preferences:1.0-SNAPSHOT. 

Show Details

Affected Modules: app

下面会分析下为什么会报错。

Flutter Plugin依赖原理

以Flutter Module为例,看下Flutter Plugin依赖的原理。

flutter package get

当Flutter Module在pub中依赖了Flutter Plugin,并且在 flutter package get后,会从远程pubserver拉取依赖的Flutter Plugin,并生成一个文件记录依赖了哪些Flutter Plugin,名字为: .flutter-plugins ,内容为 k-v,如:

flutter_webview_plugin=/Users/wanglinglong/Develop/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_webview_plugin-0.3.5/

被拉取到本地后的Flutter Plugin目录如下

NbyqQr3.png!web

动态依赖

然后在.android/app/settings.gradle 文件中添加配置脚本,使其在Gradle初始化之后执行解析操作:

rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir, 'include_flutter.groovy'))

M32yiqI.png!web

这个脚本的作用是控制配置(evaluate)顺序,操作是解析.flutter-plugins,得到各个插件的android工程,使其在.android/Flutter 配置完成之后再进行配置解析。Gradle配置阶段的目标是解析每个project中的build.gradle。

然后在.android/Flutter/build.gradle中依赖

apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

这个文件中同样解析.flutter-plugins文件,遍历后一个一个 implementation 到./android/Flutter module工程里完成依赖。

uAvaa2J.png!web

所以在Native层面来看这种依赖其实是本地依赖,只不过Flutter Plugin工程路径都在Flutter的环境变量下的缓存目录中。

这里的依赖分成两个部分,一部分是原生Native依赖,一部分是Dart依赖。

对于原生部分,Flutter Plugin作为lib被Flutter Module本地依赖,根据Android 编译常识,在打包Aar后,这些 本地依赖的工程lib不能被一起打到Aar中

所以当App中依赖Flutter Module的产物Aar时,不能获得Flutter Module 中依赖的Flutter Plugin Android原生依赖,最终会报错。

对于Dart部分,通过pub进行管理依赖,相当于源码依赖,在打包时,这些Plugin的Dart源码部分都参与打包,最终生成Flutter的构建产物。

Flutter Module打包成Aar后,Aar中包含Android原生部分编译产物和Flutter Dart部分编译产物,后面App依赖该Aar后就可以脱离Flutter的编译环境,直接进行apk打包了。

那么如何解决Aar报错的问题?

Flutter module 依赖 Flutter Plugin

已有方案大致原理都是收集Flutter Plugin 的Aar文件,然后进一步处理。

方案一:

使用 fat-aar,大致原理就是将所有的Flutter Plugin打包到同一个Aar中,这个方案比较臃肿,还可能涉及到gradle版本适配,而且可能产生多次依赖的问题。不建议使用。

方案二:

遍历所有依赖的Flutter Plugin,搜集Plugin Android工程下的Aar产物并copy到Flutter Module指定目录下,然后再push maven。

方案三:

遍历所有依赖的Flutter Plugin,根据Plugin版本信息,挨个打包成上传到maven。

由于公司内之前碰到到相关需求场景,即同一工程下多个Module如何统一管理的问题,最后解决方案就是使用的方案三,这里依然采用方案三,相比其他两种方案更稳定。

iOS 思路相同,打包成framework后上传到CDN,通过Pod进行依赖管理。

看下实现:

首先在.andorid/Flutter/build.gradle中依赖脚本 apply from: './publish_flutter_plugin.gradle'

def flutterUpload = gradle.rootProject.project(':flutter').tasks.getByName(uploadTaskName)
gradle.rootProject.ext.pluginList.each { name ->
    project.afterEvaluate {
        project.apply plugin: 'com.u51.publish'
        // 修改插件库对插件库的本地依赖为pom依
        modifyPom { pom ->
            pom.dependencies.findAll { dep ->
                if (rootProject.ext.pluginList.contains(dep.artifactId)) {
                    dep.groupId = rootProject.ext.groupId
                    dep.version = rootProject.ext.pluginMap[dep.artifactId]
                }
            }
        }
        // 设定插件本地库发布坐标版本号
        project.publish {
            groupId rootProject.ext.groupId
            version rootProject.ext.pluginMap[name]
            artifactId name
            compileEnvCheck false
            sources true
        }
        // flutter库 upload在插件本地库均upload完成后
        if (pluginUploadTask != null&&node==null) {
            flutterUpload.dependsOn(pluginUploadTask)
        }
    }
}

另外这种方案兼容持续集成环境,对于Native层面来讲,无需做任何改动。

Flutter Plugin 依赖 Flutter Plugin

上面提到的是Flutter module依赖Flutter Plugin的情况,那么如果是Flutter Plugin工程依赖Flutter Plugin工程有没有问题?

先看下Flutter Plugin的项目结构 flutter create --template=plugin -i swift -a kotlin flutter_plugin

在example下运行 flutter run命令,就可以跑起来了。

看下编译产物,其中Aar中只有原生的编译产物,并无Dart编译产物。

所以Flutter Plugin需要被pub依赖,pub将发布到远程的库下载到本地,然后在工程中

  • 原生部分,Flutter Module通过上述方案动态添加maven 依赖
  • Dart部分,Flutter Module通过pub依赖找到Dart源码,在Flutter Module中import引入,相当于源码依赖,共同参与编译,生成最终的 Dart产物

上面的场景都是Flutter Module(Flutter Application)依赖Flutter Plugin的情况,那么Flutter Plugin能不能依赖Flutter Plugin,会不会有问题?

首先Dart部分由于是pub依赖,相当于源码依赖,是没有问题的。

原生部分,由于Flutter Plugin在原生部分没有引入include_flutter.groovy(Android),所以宿主Flutter Pugin无法动态include到子Flutter Plugin,找不到依赖。

解决办法就是再给它加上,方法同上。

工程结构

总结来说,对于 Flutter 来讲只有 Flutter Module 和 Flutter Application编译会生成Dart编译产物,而Flutter Application我们基本不用考虑。

所以很自然的得出结论,我们可以把Flutter Module作为隔离层,作为Flutter层的聚合统一输出给 App,App 工程只需通过 maven or Pod 依赖Flutter Module即可。

其余的Flutter相关依赖操作都交给Flutter Module。

Flutter Module可以看成是Flutter层到Native层的双向关联。

未完待续。。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK