上一小節,我們講解了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自動化項目建構技術精講+實戰
Android熱修複與插件化實踐之路
元件化封裝思想實戰Android App