上一小节,我们讲解了flutter工程的整体结构和flutter工程中几个重要文件的作用,下面我们就来重点讲解一下,flutter工程如何与android混编在一起的。
这里,我们就要重点分析一下android这个目录下的一些重要文件了,大家先来看一下android文件夹都有那些东西。
大家来看,里面与我们平时创建的传统android工程看起来差别并不太大,app是我们最终的生成apk文件的module,在app这个module里,你依然可以写你已经掌握的android原生代码(Java或者Kotlin)去实现你想要通过原生实现的功能,这个相信大家能够理解,那再往下这个Flutter文件夹是个什么呢,其实他是一个library,这个library有什么作用呢,我们先看一下他的build.gradle文件有些什么就清楚了。
// 我删掉了一些不影响理解的检查类型的代码
apply plugin: 'com.android.library' //说明Flutter工程是个android Library
//引入了flutter sdk中的一个flutter.gradle文件,非常重要,第一个不同点
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
// 指定flutter工程的目录,第二个不同点。
flutter {
source '../..'
}
大家来看,是不是也没有什么东西,与我们平时创建的android library也没有什么不同,不同之处,只有两点,一点是引入了一个flutter.gradle文件,一点是配置了一个flutter{}的extension,但是,就是这两点,帮我们完成了flutter工程打入到此library中的所有功能,尤其是第一点,引放flutter sdk中的flutter.gradle文件,所以我们下面就来重点看一下这个flutter.gradle文件中的核心内容,看看他是如何帮我们将flutter工程的内容打包到我们的这个Flutter库工程中的,代码如下:
// 核心就是一个自定义plugin,并自动帮我们引入
apply plugin: FlutterPlugin
class FlutterPlugin implements Plugin<Project> {
// 此plugin的核心方法。只挑选重点部分
@Override
void apply(Project project) {
//第一点,自动帮我们依赖flutter.jar
flutterJar = Paths.get(engineOut.absolutePath, "flutter.jar").toFile()
if (!flutterJar.isFile()) {
throw new GradleException('Local engine build does not contain flutter.jar')
}
localEngine = engineOut.name
localEngineSrcPath = engineOut.parentFile.parent
project.dependencies {
if (project.getConfigurations().findByName("api")) {
api project.files(flutterJar) //plugin3.0以上的api方式依赖
} else {
compile project.files(flutterJar) //plugin3.0以下的compile依赖
}
}
// 第二点,创建上文提到的flutter这个extension和FluterTask这个Task,一会儿我们来具体看task的作用
project.extensions.create("flutter", FlutterExtension)
project.afterEvaluate this.&addFlutterTask
//第三点,读取.flutter-plugins文件中的所有依赖plugin,并添加到Flutter工程中的build.gradle依赖中
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
Properties plugins = readPropertiesIfExist(pluginsFile)
plugins.each { name, _ ->
def pluginProject = project.rootProject.findProject(":$name")
if (pluginProject != null) {
project.dependencies {
if (project.getConfigurations().findByName("implementation")) {
implementation pluginProject
} else {
compile pluginProject
}
}
pluginProject.afterEvaluate {
pluginProject.android.buildTypes {
profile {
initWith debug
}
}
}
pluginProject.afterEvaluate this.&addFlutterJarCompileOnlyDependency
} else {
project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
}
}
}
//第二点中提到的创建FlutterTask方法
private void addFlutterTask(Project project) {
//找到flutter工程中的lib下的入口main.dart
String target = project.flutter.target
if (target == null) {
target = 'lib/main.dart'
}
// 处理flutter中的dart代码
def addFlutterDeps = { variant ->
FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) {
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode flutterBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
targetPath target
verbose verboseValue
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
compilationTraceFilePath compilationTraceFilePathValue
buildHotUpdate buildHotUpdateValue
buildSharedLibrary buildSharedLibraryValue
targetPlatform targetPlatformValue
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
}
//处理flutter工程中的asset
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
dependsOn flutterTask
dependsOn packageAssets ? packageAssets : variant.mergeAssets
dependsOn cleanPackageAssets ? cleanPackageAssets : "clean${variant.mergeAssets.name.capitalize()}"
into packageAssets ? packageAssets.outputDir : variant.mergeAssets.outputDir
with flutterTask.assets
}
if (packageAssets) {
// Only include configurations that exist in parent project.
Task mergeAssets = project.tasks.findByPath(":app:merge${variant.name.capitalize()}Assets")
if (mergeAssets) {
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
} else {
variant.outputs[0].processResources.dependsOn(copyFlutterAssetsTask)
}
}
}
}
通过上面对Flutter这个library以及flutter sdk中的flutter.gradle文件的代码进行学习,我们可以发现,这个Flutter module本身没有任何的具体逻辑,他的唯一作用就是通过引入flutter sdk中的flutter.gradle,自动完成将flutter工程中的lib下的代码和asset下的资源打包到其中,这样这个Flutter安卓module最终可以生成一个带有flutter代码和资源的aar包,最后这个aar包就可以被其它的app去使用.
那在我这个demo工程,app是如何使用到包含flutter代码的这个aar包的呢,下面我们打开app下的build.gradle文件来看一下,
// 修改apk的输出位置为根目录下的build/host
buildDir = new File(rootProject.projectDir, "../build/host")
dependencies {
//这里就是apk直接依赖了我们的Flutter这个library的源码,也可以通过maven的方式引用我们Flutter module生成的aar,道理相信大家都明白.
implementation project(':flutter')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation'com.squareup.okhttp3:okhttp:3.3.0'
这里有一个问题,我们的library module叫Flutter,但我们的app的build.gradle中使用的却是flutter,这个呢相对来说简单的许多,要看我们最后一个文件,include_flutter.groovy这个文件,重点代码如下:
//include我们创建的Flutter工程并命名为flutter
def scriptFile = getClass().protectionDomain.codeSource.location.path
def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
gradle.include ':flutter'
gradle.project(':flutter').projectDir = new File(flutterProjectRoot, '.android/Flutter')
//引入所有的plugin工程,下一小节具体讲plugin工程
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile()
gradle.include ":$name"
gradle.project(":$name").projectDir = pluginDirectory
}
好,到此大家就应该知道,我们的android工程是如何引入了flutter工程中的东西,从而将他们打包到同一个apk中去的,从工程层面上了解了他们如何合二为一以后,具体在代码层面如何调用,就是通过plugin最后实现flutter和android原生的通信,我们后面也会讲解。最后我们以一张图来总结一下今天所讲的内容:
欢迎关注课程:
Gradle3.0自动化项目构建技术精讲+实战