一、官方方案
創建Android項目
如果你已經有Android項目,可以直接使用。這里我們先創建一個空的android項目來模擬已有的項目,取名叫TestFlutter。
創建Flutter模塊
在Android工程目錄同級目錄下執行命令
flutter create -t module flutter_module
上面的命令會創建一個flutter的項目模塊,在flutter文件夾中有一個.android的隱藏文件夾,里面包裹了一個安卓庫的工程模塊。
可以嘗試用Gradle編譯這個庫,但這不是必須的步驟:
$ cd .android/
$ ./gradlew flutter:assembleDebug
編譯后會在.android/Flutter/build/outputs/aar/路徑下產生flutter-debug.aar的文件。
將Flutter模塊作為依賴添加到主項目
打開你的Android工程的setting.gradle文件,添加如下代碼:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
這幾行代碼的意思就是將你剛才創建的那個module作為android模塊引入到Android工程中。
點擊同步完成后,到你app目錄的build.gradle文件把依賴加上:
dependencies {
implementation project(':flutter')
}
再次同步完成就已經將Flutter添加到了你的項目了。接下來就可以開始混合開發了。
遇到問題總結
1、比如,我們不在Android工程的同級目錄去flutter create -t module my_flutter會怎么樣,我嘗試了,只需要對路徑加上你工程目錄名即可,這么寫
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'你工程目錄名/flutter_module/.android/include_flutter.groovy'
))
2、release 混淆問題
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
二、閑魚方案
通過Android studio 新建Flutter Application項目
在命令行輸入命令flutter build apk
會編譯生成apk文件,位于build/app/outputs/apk/release/文件夾下。
這個apk里的產物實際上是在Android的app/build.gradle構建代碼里引入了Flutter的構建代碼。
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
通過閱讀flutter構建源碼我們發現在構建apk文件的時候,會將需要的文件構建到apk中。
1.assets文件夾
assets文件夾下面有flutter_assets文件夾、flutter_shared文件夾、isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr文件。
flutter_assets里是flutter工程產生的assets文件
flutter_shared里是封裝在flutter.jar里面的處理字符編碼的ICU庫
isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr為特定平臺的數據和指令
2.lib文件夾
lib文件夾下是特定平臺(arm或者x86)的so文件。
flutter在Android平臺下會默認生成arm-v7架構的的so庫,flutter.gradle源碼中會根據target-platform屬性判斷平臺動態生成對應的so,官方注釋目前flutter只支持在debug模式下生成x86的so。
提取aar
上面通過編譯命令得到了apk,那想要打包成aar,理論上只要把app/build.gradle中的apply plugin: 'com.android.application'修改為apply plugin: 'com.android.library',同時刪除applicationId "com.shanbay.flutterapp"再次執行flutter build apk命令,便可以得到app-release.aar文件。
或者進入android文件目錄下,執行命令 ./gradlew assembleRelease 也可以編譯得到app-release.aar
集成到現有項目
我們將得到的aar文件集成到現有的Android工程中使用,但是打開flutter頁面卻閃退了,同時flutter報出了error,錯誤是說aar里面缺少icudtl.dat文件。
解壓縮aar查看文件結構,可以發現其中的問題。
在aar文件夾下的assets里面缺少了flutter_shared文件夾,icudtl.dat文件正是在該文件夾里面,也就是說flutter.gradle在編譯流程中并沒有將icudtl.dat文件打進aar包里面,這一點從flutter庫的issue里面得到了證實,我們的辦法是將apk里面得到的flutter_shared文件夾手動copy到flutter工程中,再次編譯aar,這樣就可以得到有icudtl.dat的aar文件。再次集成到Android項目中便可以成功運行,不會產生錯誤。
總結
這個方案需要兩個步驟,第一步是先編譯成apk取得icudtl.dat文件放入到工程中,第二步修改apply plugin: 'com.android.library'再次編譯取得aar。
如果Flutter項目引用了path_provider、shared_preferences 需要將這兩個jar包導入aar中不然會提示找不到這兩個jar包的PathProviderPlugin、SharedPreferencesPlugin
在app的build.gradle 里面添加代碼
implementation fileTree(dir: 'libs', include: ['*.jar'])
為了方便打aar包和flutter項目運行,我們可以設置一個變量來控制
在android目錄下的gradle.properties里面添加BUILD_MODE=aar,src目錄下新建一個文件夾里面放置AndroidManifest.xml
在app的build.gradle中添加以下代碼
之后需要打aar包的時候,只需要在gradle.properties中修改BUILD_MODE為aar就可以用來打aar包,修改為flutter就是正常的flutter編譯模式
build.gradle 相關代碼
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
def BUILD_MODE = localProperties.getProperty('BUILD_MODE')
def isRunAsFlutter = "flutter".equals(BUILD_MODE)
if (isRunAsFlutter) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
if (isRunAsFlutter) {
applicationId "com.test.flutter"
}
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
sourceSets {
main {
def srcFile = isRunAsFlutter ? 'src/main/AndroidManifest.xml' : 'src/maven/AndroidManifest.xml'
manifest.srcFile srcFile
java {
srcDir 'src/main/java'
}
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
}
}
}
flutter {
source '../..'
}
dependencies {
if (!isRunAsFlutter) {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-v13:28.0.0'
implementation 'com.android.support:support-annotations:28.0.0'
}