做者:騰訊NOW直播 -koudleren(任曉帥)java
騰訊NOW終端技術團隊在Flutter推出後就一直在關注Flutter的發展,而且在2018年4月份將Flutter引入到NOW直播APP中,在將Flutter運用在業務中的同事,也一直在研究並完善Flutter的技術,但願將本身團隊的技術和經驗分享給其餘團隊。android
Flutter做爲一個新的UI開發框架,由於其創新的理念,已經吸引了愈來愈多的人蔘與其中,在實際的項目開發中,咱們更但願將Flutter引入到咱們的APP中,而不是用Flutter從新開發一款全新的APP。因此做爲開發者,咱們更關心的是,如何使用Flutter進行混合工程的開發?值得關注的是,將Flutter集成到現有的工程中的方法, 隨着Flutter框架的升級也在不斷變換中。而本文將介紹NOW直播在混合開發中採用的方法,首先帶領你們從Flutter插件和編譯腳本入手,研究Flutter的構建工程目錄結構,最後本身實現Flutter混合工程開發的插件,給其餘團隊以參考。git
按照官網的提示,要進行Flutter的開發,要以下幾步:github
一、下載Flutter SDKapi
二、配置環境變量bash
三、在Android Studio上安裝Flutter和Dart插件app
四、File > New Flutter Project > Flutter application > Enter a project name > Finish框架
以後在Android Studio 的toolbar上就能夠看到以下的圖標:性能
咱們點擊Android Studio 上的New菜單,建立Flutter工程,而後點擊Android Studio toolbar上的Run運行Flutter程序,不由思考,爲何Flutter工程要這樣建立,在點擊Run以後發生了什麼,Hot Reload是怎麼實現的,爲了一探究竟,咱們能夠從Flutter 的Android Studio插件源碼入手。gradle
在GitHub上,有Flutter Android Studio插件的源碼,地址爲:
https://github.com/flutter/flutter-intellij
經過閱讀插件的源代碼,能夠發現插件實現不一樣功能的代碼邏輯是怎樣的,咱們能夠根據插件的配置信息查找具體的插件動做及其實現類,
插件配置信息所在的文件爲:resources/META-INF/plugin.xml
下面咱們將分別找到New Flutter Project 和 Run Flutter Project的配置信息,並進行分析。
<!-- Define the 'New Flutter Project' menu item -->
<action id="flutter.NewProject" class="io.flutter.actions.FlutterNewProjectAction" text="New Flutter Project..." description="Create a new Flutter project"> <add-to-group group-id="NewProjectOrModuleGroup" anchor="after" relative-to-action="NewProject"/> </action> 複製代碼
在註釋裏能夠看到,這個配置信息就是New Flutter Project的,action 的 id 是flutter.NewProject,對應的類文件是io.flutter.actions.FlutterNewProjectAction,
因而找到這個類,分析裏面實現的類,能夠發現以下的關係:
FlutterNewProjectAction(新建工程的入口) > FlutterProjectModel(填寫工程信息的窗口) > FlutterProjectCreator (工程建立準備及驗證) > FlutterSmallIDEProjectGenerator(獲取Flutter SDK信息)> FlutterSdk (調用了Flutter SDK的命令行代碼)
在Flutter工程建立的過程當中,最核心的代碼實際上是這一行:
return new FlutterCommand(this, appDir.getParent(), FlutterCommand.Type.CREATE, vargs)
複製代碼
這一句實際上是運行了Flutter SDK的一句命令行:
$ flutter create --template=app --org=now.tencent.com flutter_app
複製代碼
使用Flutter的命令行建立了工程。
<!-- main toolbar run actions -->
<action id="Flutter.Toolbar.ReloadAction" class="io.flutter.actions.ReloadFlutterAppRetarget"
description="Reload"
icon="FlutterIcons.HotReload">
<add-to-group group-id="ToolbarRunGroup" anchor="after" relative-to-action="RunnerActions"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl BACK_SLASH"/>
</action>
<!-- run menu actions -->
<group id="Flutter.MenuActions.Run">
<separator/>
<reference ref="Flutter.Toolbar.ReloadAction"/>
<action id="Flutter.Toolbar.RestartAction" class="io.flutter.actions.RestartFlutterAppRetarget"
description="Restart"
icon="FlutterIcons.HotRestart">
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift BACK_SLASH"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift S"/>
</action>
<action id="Flutter.Menu.RunProfileAction" class="io.flutter.actions.RunProfileFlutterApp"
description="Flutter Run Profile Mode"
icon="AllIcons.Actions.Execute">
</action>
<action id="Flutter.Menu.RunReleaseAction" class="io.flutter.actions.RunReleaseFlutterApp"
description="Flutter Run Release Mode"
icon="AllIcons.Actions.Execute">
</action>
<separator/>
<add-to-group group-id="RunMenu" anchor="after" relative-to-action="Stop"/>
</group>
複製代碼
一樣的原理,能夠發現核心的代碼以下:
[SdkRunConfig.java]
return fields.createFlutterSdkRunCommand(project, mode, FlutterLaunchMode.fromEnv(env), device);
複製代碼
[SdkFields.java]
final FlutterCommand command = flutterSdk.flutterRun(root, main.getFile(), device, runMode, flutterLaunchMode, args);
return command.createGeneralCommandLine(project);
複製代碼
實際上是運行了Flutter SDK的一句命令行:
$ flutter run ...
複製代碼
將Flutter程序Run起來,並實現了Hot Reload。
若是說Flutter的插件,是將Flutter和Android Studio(IDE)鏈接在一塊兒,那麼Flutter的gradle編譯腳本就是將Flutter和Android鏈接在一塊兒:在Android工程中添加Flutter的依賴,並採用不一樣的編譯策略,生成Flutter的構建產物,最後將Flutter產物打包到Android的APK中。
Flutter編輯腳本所在的位置是Flutter SDK文件夾下的packages/flutter_tools/gradle/flutter.gralde
接下來咱們將分析Flutter編譯腳本的代碼,研究Flutter編譯腳本的做用。主要作的內容包括:
包括:
一、Flutter SDK的路徑
二、獲取flutter.bat 的路徑
三、Flutter jar 包的路徑
四、編譯模式
等等,部分代碼以下:
String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
根據buildMode的不一樣,依賴不一樣的配置,部分代碼以下:
/** * Adds suitable flutter.jar api dependencies to the specified buildType. * * Note: The BuildType DSL type is not public, and is therefore omitted from the signature. */
private void addFlutterJarApiDependency(Project project, buildType) {
project.dependencies {
String configuration;
if (project.getConfigurations().findByName("api")) {
configuration = buildType.name + "Api";
} else {
configuration = buildType.name + "Compile";
}
add(configuration, project.files {
String buildMode = buildModeFor(buildType)
if (buildMode == "debug") {
[flutterX86Jar, debugFlutterJar]
} else if (buildMode == "profile") {
profileFlutterJar
} else {
releaseFlutterJar
}
})
}
}
複製代碼
咱們知道Flutter是用Dart代碼編寫的,爲了讓Dart代碼能夠運行在Flutter engine上,須要對Flutter代碼進行編譯,這裏有兩種編譯方式:
一、JIT編譯
JIT(Just In Time),即時編譯 ,Flutter在debug模式下使用此種編譯方式,能夠實現Hot Reload
二、AOT編譯
AOT(Ahead Of Time),靜態提早編,編譯成本地機器碼 ,Flutter在Release模式下使用此種編譯方式,擁有更好的性能 。
FlutterTask flutterTask = project.tasks.create(name: "flutterBuild${variant.name.capitalize()}", type: FlutterTask) {
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode flutterBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
targetPath target
verbose verboseValue
previewDart2 previewDart2Value
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
buildSnapshot buildSnapshotValue
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
}
複製代碼
在使用不一樣編譯器編譯的時候生成的產物也不同,具體查看下面2.5的圖。
Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
dependsOn flutterTask
dependsOn variant.mergeAssets
dependsOn "clean${variant.mergeAssets.name.capitalize()}"
into variant.mergeAssets.outputDir
with flutterTask.assets
}
複製代碼
講過上面的介紹,再來看Flutter APK的目錄結構,就很容易理解,下圖分別是Flutter Debug APK和Release APK目錄結構的對比:
NOW直播從Flutter的0.3.1版本開始接入,到如今Flutter發佈到0.5.7,見證了Flutter建立混合工程的方法的變化,方法雖然在變,可是原理不變,通過上面介紹的Flutter插件和編譯腳本,咱們很容易理解Flutter建立混合工程的原理:就是經dart代碼通過Flutter SDK編譯,而後把生成的產物打包到制定的APK路徑下。如今將告訴你如何建立Flutter的混合工程。
Flutter 最新版本0.5.7建立混合工程的方法以下:
一、在已有工程的同級目錄裏,運行:
$ flutter create -t module my_flutter
複製代碼
二、在已有工程的settings.gradle
裏添加Flutter 的model,以下:
// MyApp/settings.gradle
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new
複製代碼
三、在已有工程的build.gradle
裏添加Flutter model的依賴,以下:
// MyApp/app/build.gradle
:dependencies {
implementation project(':flutter')
:
}
複製代碼
通過上面的步驟咱們就建立了Flutter的混合工程,將Flutter工程Run起來並使用Hot Reload 的方法有兩種:
一、使用flutter run
$ cd MyApp
$ ./gradlew app:assembleDebug
$ cd ../xyz
$ flutter run --use-application-binary \
../MyApp/app/build/outputs/apk/debug/app-debug.apk
複製代碼
二、使用flutter attach
cd <path to your Flutter module>
flutter attach
複製代碼
在混合工程裏使用Flutter,發現純Flutter 工程裏在Android Studio 的toobar上顯示的icon不見了,想使用Flutter,只能使用命令行來運行,因此其實咱們能夠在Flutter插件上擴展混合工程建立的命令及Run 和 HotReload的命令。
在NOW直播裏爲了方便的進行Flutter混合工程的開發,本身實現了一個插件,示例以下:
<action id="New-Flutter-Model" class="now.tencent.FlutterNewModel"
text="New Flutter Model"
description="Create a new Flutter model">
<add-to-group group-id="NewProjectOrModuleGroup" anchor="after" relative-to-action="NewProject"/>
</action>
<action id="Hot-reload" class="now.tencent.now.HotReload" text="HotReload" description="HotReload when has started">
<add-to-group group-id="ToolbarRunGroup" anchor="after" relative-to-action="RunnerActions"/>
<keyboard-shortcut keymap="$default" first-keystroke="shift ctrl alt 0"/>
</action>
<action id="Start-Hot" class="now.tencent.now.StartHotReload" text="StartHotReload" description="StartHotReload">
<add-to-group group-id="ToolbarRunGroup" anchor="after" relative-to-action="RunnerActions"/>
<keyboard-shortcut keymap="$default" first-keystroke="shift ctrl alt 9"/>
</action>
複製代碼
這個插件,NOW直播將在以後開源,歡迎關注。
通過上面的步驟,NOW直播團隊已經解決了Flutter混合工程開發的問題,經過實現自定義的插件,解決了實際開發過程當中的問題,也在不斷完善Flutter的生態,讓咱們期待Flutter變的更好!