淺談Flutter構建

不一樣版本構建產物的差別

寫這篇文章的時間爲:2019.08.25,當前 Flutter SDK 最新版本爲 v1.9.5,其中 stable 最新版本爲 v1.7.8+hotfix.4。python

隨着版本的更新,Flutter 構建的產物也在調整,以 Android 爲例,咱們使用默認的 flutter_app 項目來測試,在不修改源碼的狀況下,使用不一樣的 SDK 版原本執行打包命令,每一個版本都打兩個包:debug 和 release。android

APK

每一個版本生成的 Flutter 產物以下所示,這裏不列出 fonts、LICENSE 這些文件:ios

版本 類型 內容 說明
1.5.4 debug assets/flutter_assets/kernel_blob.bin
assets/flutter_assets/isolate_snapshot_data
assets/flutter_assets/vm_snapshot_data
lib/x86_64/libflutter.so
lib/x86/libflutter.so
lib/armeabi-v7a/libflutter.so
1.5.4 release assets/isolate_snapshot_data
assets/isolate_snapshot_instr
assets/vm_snapshot_data
assets/vm_snapshot_instr
lib/armeabi-v7a/libflutter.so
1.6.7 debug 新增了
assets/snapshot_blob.bind.d.fingerprint
assets/snapshot_blob.bin.d
1.6.7 release 無變化
1.7.8 debug 刪除了
assets/snapshot_blob.bind.d.fingerprint
assets/snapshot_blob.bin.d
新增了
lib/arm64-v8a/libflutter.so
1.7.8 release 刪除了
assets/isolate_snapshot_data
assets/isolate_snapshot_instr
assets/vm_snapshot_data
assets/vm_snapshot_instr
新增了
lib/armeabi-v7a/libapp.so
lib/arm64-v8a/libflutter.so
lib/arm64-v8a/libapp.so
從這個版本開始,release 下不使用 snapshot,同時增長了同時打包 64 位 so 文件
1.8.4 debug 無變化
1.8.4 release 無變化
1.9.5 debug 無變化
1.9.5 release 無變化

從上面的表格來看,1.5.x 版本和 1.7.x 版本的變化是比較明顯的,主要的變化有兩點,第一,1.7.x 版本增長了支持同時編譯 32 位和 64 位兩種架構,這個在以前一直被詬病,如今官方支持了。第二,release 模式下,不使用 snapshot 文件,而使用 libapp.so。snapshot 是 Dart VM 所支持的一種文件格式,相似 JVM 的 jar 同樣,能夠運行在虛擬機環境的文件。git

除了變化之外,咱們還注意到一些不變的文件,好比 debug 模式下,一直存在的 kernel_blob.bin 文件;lib 目錄下 libflutter.so 文件;這些文件各自的做用又是什麼呢。帶着疑問,咱們一塊兒看看 Flutter 的構建過程。github

Flutter 支持的編譯模式

關於編譯模式這塊主要參考的:shell

Flutter 是基於 Dart 開發的,因此 Flutter 的構建跟 Dart 是分不開的。因此,咱們先講 Dart。json

在將 Dart 以前,咱們先了解兩種編譯模式,JIT 和 AOT:api

JIT

JIT(Just In Time) 翻譯爲 即時編譯,指的是在程序運行中,將熱點代碼編譯成機器碼,提升運行效率。常見例子有 V8 引擎和 JVM,JIT 能夠充分利用解釋型語言的優勢,動態執行源碼,而不用考慮平臺差別性。這裏須要注意的是,對於 JVM 來講,源碼指字節碼,而不是 Java 源碼。性能優化

這裏須要區分,JIT 和解釋型語言的區別,JIT 是一種編譯模式,好比,Java 是編譯型語言,但它也可使用 JIT。bash

AOT

AOT(Ahead Of Time) 稱爲 運行前編譯,指的是在程序運行以前,已經編譯成對應平臺的機器碼,不須要在運行中解釋編譯,就能夠直接運行。常見例子有 C 和 C++。

雖然,咱們會區別 JIT 和 AOT 兩種編譯模式,但實際上,有不少語言並非徹底使用 JIT 或者 AOT 的,一般它們會混用這兩種模式,來達到最大的性能優化。

Dart 支持的編譯模式

Dart VM 支持四種編譯模式:

  • Script:最多見的 JIT 模式,能夠直接在虛擬機中執行 Dart 源碼,像解釋型語言同樣使用。

    經過執行 dart xxx.dart 就能夠運行,寫一些臨時腳本,很是方便。

  • Kernel Snapshots:JIT 模式,和 Script 模式不一樣的是,這種模式執行的是 Kernel AST 的二進制數據,這裏不包含解析後的類和函數,編譯後的代碼,因此它們能夠在不一樣的平臺之間移植。

    Flutter’s Compilation Patterns 這篇文章中,將 Kernel Snapshots 稱爲 Script Snapshots 也是對的,這應該是以前的叫法,從 dart-langwiki 中能夠看出來:

    Kernel Snapshots And Script Snapshots

    Dart Kernel 是 Dart 程序中的一種中間語言,更多的資料可閱讀 Kernel Documentation

    經過執行 dart --snapshot-kind=kernel --snapshot=xx.snapshot xx.dart 生成。

  • JIT Application Snapshots:JIT 模式,這裏執行的是已經解析過的類和函數,因此它會運行起來會更快。可是這不是平臺無關,它只能針對 32位 或者 64位 架構運行。

    經過執行 dart --snapshot-kind=app-jit --snapshot=xx.snapshot xx.dart 生成。

  • AOT Application Snapshots:AOT 模式,在這種模式下,Dart 源碼會被提早編譯成特定平臺的二進制文件。

    要使用 AOT 模式,須要使用 dart2aot 命令, 具體使用爲:dart2aot xx.dart xx.dart.aot,而後使用 dartaotruntime 命令執行。

Dart JIT 模式須要配合 Dart VM(虛擬機) ,AOT 則會使用 runtime 來執行,引用官網中的圖片來講明:

jit-and-aot

能夠用下面這張圖來總結上面的四種模式:

圖片來源於 flutters-compilation-patterns

Dart’s compilation patterns

四種模式下的產物大小和啓動速度比較:

圖片來源於 exploring-flutter-in-android

whai is snapshot

Flutter 構建過程

下面的分析都是基於 v1.5.4-hotfix.2

執行 flutter build apk 時,默認會生成 release APK,實際是執行的 sdk/bin/flutter 命令,參數爲 build apk:

DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"
DART="$DART_SDK_PATH/bin/dart"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
"$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
複製代碼

上面的命令完整應該爲:dart flutter_tools.snapshot args,snapshot 文件咱們上面提過,是 Dart 的一種代碼集合,相似 Java 中 Jar 包。

flutter_tools 的源碼位於 sdk/packages/flutter_tools/bin 下的 flutter_tools.dart,跟 Java 同樣,這裏也有個 main() 函數,其中調用的是 executable.main() 函數。

在 main 函數中,會註冊多個命令處理器,好比:

  • DoctorCommand 對應 flutter doctor 命令
  • CleanCommand 對應 flutter clean 命令
  • 等等

咱們此次要研究的目標是 BuildCommand,它裏面又包含了如下子命令處理器:

  • BuildApkCommand 對應 flutter build apk 命令
  • BuildAppBundleCommand 對應 flutter build bundle 命令
  • BuildAotCommand 對應 flutter build aot 命令
  • 等等

Build APK

首先咱們要理清 build apk 或者 build ios,和 build bundle、build aot 之間的關係。這裏咱們以 build apk 爲例:

當執行 flutter build apk 時,最終會調用到 gradle.dart 中的 _buildGradleProjectV2() 方法,在這裏最後也是調用 gradle 執行 assemble task。而在項目中的 android/app/build.gradle 中能夠看到:

apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
複製代碼

也就是說,會在 flutter.gradle 這裏去插入一些編譯 Flutter 產物的腳本。

flutter.gradle 是經過插件的形式去實現的,這個插件命名爲 FlutterPlugin。這個插件的主要做用有一些幾點:

  • 除了默認的 debug 和 release 以外,新增了 profile、dynamicProfile、dynamicRelease 這三種 buildTypes:

    project.android.buildTypes {                                
        profile {                                               
            initWith debug                                      
            if (it.hasProperty('matchingFallbacks')) {          
                matchingFallbacks = ['debug', 'release']        
            }                                                   
        }                                                       
        dynamicProfile {                                        
            initWith debug                                      
            if (it.hasProperty('matchingFallbacks')) {          
                matchingFallbacks = ['debug', 'release']        
            }                                                   
        }                                                       
        dynamicRelease {                                        
            initWith debug                                      
            if (it.hasProperty('matchingFallbacks')) {          
                matchingFallbacks = ['debug', 'release']        
            }                                                   
        }                                                       
    }                                                           
    複製代碼
  • 動態添加 flutter.jar 依賴:

    private void addFlutterJarApiDependency(Project project, buildType, Task flutterX86JarTask) {   
        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") {                                                         
                    [flutterX86JarTask, debugFlutterJar]                                            
                } else if (buildMode == "profile") {                                                
                    profileFlutterJar                                                               
                } else if (buildMode == "dynamicProfile") {                                         
                    dynamicProfileFlutterJar                                                        
                } else if (buildMode == "dynamicRelease") {                                         
                    dynamicReleaseFlutterJar                                                        
                } else {                                                                            
                    releaseFlutterJar                                                               
                }                                                                                   
            })                                                                                      
        }                                                                                           
    }                                                                                               
    複製代碼
  • 動態添加第三方插件依賴:

    project.dependencies {                                             
        if (project.getConfigurations().findByName("implementation")) {
            implementation pluginProject                               
        } else {                                                       
            compile pluginProject                                      
        }                                                              
    複製代碼
  • 在 assemble task 中添加一個 FlutterTask,這個 task 很是重要,這裏會去生成 Flutter 所須要的產物:

    首先,當 buildType 是 profile 或 release 時,會執行 flutter build aot

    if (buildMode == "profile" || buildMode == "release") {                               
        project.exec {                                                                    
            executable flutterExecutable.absolutePath                                     
            workingDir sourceDir                                                          
            if (localEngine != null) {                                                    
                args "--local-engine", localEngine                                        
                args "--local-engine-src-path", localEngineSrcPath                        
            }                                                                             
            args "build", "aot"                                                           
            args "--suppress-analytics"                                                   
            args "--quiet"                                                                
            args "--target", targetPath                                                   
            args "--target-platform", "android-arm"                                       
            args "--output-dir", "${intermediateDir}"                                     
            if (trackWidgetCreation) {                                                    
                args "--track-widget-creation"                                            
            }                                                                             
            if (extraFrontEndOptions != null) {                                           
                args "--extra-front-end-options", "${extraFrontEndOptions}"               
            }                                                                             
            if (extraGenSnapshotOptions != null) {                                        
                args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"         
            }                                                                             
            if (buildSharedLibrary) {                                                     
                args "--build-shared-library"                                             
            }                                                                             
            if (targetPlatform != null) {                                                 
                args "--target-platform", "${targetPlatform}"                             
            }                                                                             
            args "--${buildMode}"                                                         
        }                                                                                 
    }                                                                                     
    複製代碼

    其次,執行 flutter build bundle 命令,當 buildType 爲 profile 或 release 時,添加額外的 --precompiled 選項。

    project.exec {                                                                    
        executable flutterExecutable.absolutePath                                     
        workingDir sourceDir                                                          
        if (localEngine != null) {                                                    
            args "--local-engine", localEngine                                        
            args "--local-engine-src-path", localEngineSrcPath                        
        }                                                                             
        args "build", "bundle"                                                        
        args "--suppress-analytics"                                                   
        args "--target", targetPath                                                   
        if (verbose) {                                                                
            args "--verbose"                                                          
        }                                                                             
        if (fileSystemRoots != null) {                                                
            for (root in fileSystemRoots) {                                           
                args "--filesystem-root", root                                        
            }                                                                         
        }                                                                             
        if (fileSystemScheme != null) {                                               
            args "--filesystem-scheme", fileSystemScheme                              
        }                                                                             
        if (trackWidgetCreation) {                                                    
            args "--track-widget-creation"                                            
        }                                                                             
        if (compilationTraceFilePath != null) {                                       
            args "--compilation-trace-file", compilationTraceFilePath                 
        }                                                                             
        if (createPatch) {                                                            
            args "--patch"                                                            
            args "--build-number", project.android.defaultConfig.versionCode          
            if (buildNumber != null) {                                                
                assert buildNumber == project.android.defaultConfig.versionCode       
            }                                                                         
        }                                                                             
        if (baselineDir != null) {                                                    
            args "--baseline-dir", baselineDir                                        
        }                                                                             
        if (extraFrontEndOptions != null) {                                           
            args "--extra-front-end-options", "${extraFrontEndOptions}"               
        }                                                                             
        if (extraGenSnapshotOptions != null) {                                        
            args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"         
        }                                                                             
        if (targetPlatform != null) {                                                 
            args "--target-platform", "${targetPlatform}"                             
        }                                                                             
        if (buildMode == "release" || buildMode == "profile") {                       
            args "--precompiled"                                                      
        } else {                                                                      
            args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"                
        }                                                                             
        args "--asset-dir", "${intermediateDir}/flutter_assets"                       
        if (buildMode == "debug") {                                                   
            args "--debug"                                                            
        }                                                                             
        if (buildMode == "profile" || buildMode == "dynamicProfile") {                
            args "--profile"                                                          
        }                                                                             
        if (buildMode == "release" || buildMode == "dynamicRelease") {                
            args "--release"                                                          
        }                                                                             
        if (buildMode == "dynamicProfile" || buildMode == "dynamicRelease") {         
            args "--dynamic"                                                          
        }                                                                             
    }                                                                                 
    複製代碼

稍微總結下,當 buildType 爲 debug 時,只須要執行:

flutter build bundle
複製代碼

而當 buildType 爲 release 時,須要執行兩個命令:

flutter build aot
flutter build bundle --precompiled
複製代碼

Build AOT

當執行 flutter build aot 時,相關的邏輯在 BuildAotCommand 中,它主要分紅兩個步驟:

  1. 編譯 kernel。kernel 指 Dart 的一種中間語言,更多資料能夠閱讀 Kernel-Documentation。最終會調用到 compile.dart 中的 KernelCompiler.compile() 方法:

    final List<String> command = <String>[                                                                  
      engineDartPath,                                                                                       
      frontendServer,                                                                                       
      '--sdk-root',                                                                                         
      sdkRoot,                                                                                              
      '--strong',                                                                                           
      '--target=$targetModel',                                                                              
    ];                                                                                                      
    if (trackWidgetCreation)                                                                                
      command.add('--track-widget-creation');                                                               
    if (!linkPlatformKernelIn)                                                                              
      command.add('--no-link-platform');                                                                    
    if (aot) {                                                                                              
      command.add('--aot');                                                                                 
      command.add('--tfa');                                                                                 
    }                                                                                                       
    if (targetProductVm) {                                                                                  
      command.add('-Ddart.vm.product=true');                                                                
    }                                                                                                       
    if (incrementalCompilerByteStorePath != null) {                                                         
      command.add('--incremental');                                                                         
    }                                                                                                       
    Uri mainUri;                                                                                            
    if (packagesPath != null) {                                                                             
      command.addAll(<String>['--packages', packagesPath]);                                                 
      mainUri = PackageUriMapper.findUri(mainPath, packagesPath, fileSystemScheme, fileSystemRoots);        
    }                                                                                                       
    if (outputFilePath != null) {                                                                           
      command.addAll(<String>['--output-dill', outputFilePath]);                                            
    }                                                                                                       
    if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) {                      
      command.addAll(<String>['--depfile', depFilePath]);                                                   
    }                                                                                                       
    if (fileSystemRoots != null) {                                                                          
      for (String root in fileSystemRoots) {                                                                
        command.addAll(<String>['--filesystem-root', root]);                                                
      }                                                                                                     
    }                                                                                                       
    if (fileSystemScheme != null) {                                                                         
      command.addAll(<String>['--filesystem-scheme', fileSystemScheme]);                                    
    }                                                                                                       
    if (initializeFromDill != null) {                                                                       
      command.addAll(<String>['--initialize-from-dill', initializeFromDill]);                               
    }                                                                                                       
                                                                                                            
    if (extraFrontEndOptions != null)                                                                       
      command.addAll(extraFrontEndOptions);                                                                 
                                                                                                            
    command.add(mainUri?.toString() ?? mainPath);                                                           
                                                                                                            
    printTrace(command.join(' '));                                                                          
    final Process server = await processManager                                                             
      .start(command)                                                                                       
      .catchError((dynamic error, StackTrace stack) {                                                       
        printError('Failed to start frontend server $error, $stack');                                       
      });                                                                                                   
                                                                                                            
    複製代碼

    engineDart 指向 Dart SDK 目錄,上面代碼執行的最終命令能夠簡化爲:

    dart frontend_server.dart.snapshot --output-dill app.dill packages:main.dart
    複製代碼

    app.dill 這裏面其實就包含了咱們的業務代碼了,可使用 strings app.dill 查看:

    app.dill

  2. 生成 snpshot。相關的代碼位於 base/build.dart 中的 AOTSnapshotter.build() 函數中,有兩種模式:app-aot-assemble 和 app-aot-blobs。

    app-aot-assemble

    在執行 aot 命令時,增長 --build-shared-library 選項,完整命令以下:

    flutter build aot --build-shared-library

    iOS 只能使用這種模式,在 Flutter SDK 1.7.x 以後,這個也是 Android 的默認選項。

    // buildSharedLibrary is ignored for iOS builds. 
    if (platform == TargetPlatform.ios)                                                     
      buildSharedLibrary = false;                                                           
                                                                                            
    if (buildSharedLibrary && androidSdk.ndk == null) {
      // 須要有 NDK 環境
      final String explanation = AndroidNdk.explainMissingNdk(androidSdk.directory);        
      printError(                                                                           
        'Could not find NDK in Android SDK at ${androidSdk.directory}:\n'                   
        '\n'                                                                                
        ' $explanation\n'                                                                  
        '\n'                                                                                
        'Unable to build with --build-shared-library\n'                                     
        'To install the NDK, see instructions at https://developer.android.com/ndk/guides/' 
      );                                                                                    
      return 1;                                                                             
    }                                                                                       
    複製代碼

    這種模式下,會將產物編譯爲二進制文件,在 iOS 上爲 App.framework,Android 上則爲 app.so。

    // Assembly AOT snapshot. 
    outputPaths.add(assembly);                               
    genSnapshotArgs.add('--snapshot_kind=app-aot-assembly'); 
    genSnapshotArgs.add('--assembly=$assembly');             
    複製代碼
    app-aot-blobs

    當使用這種模式時,會生成四個產物,分別是:

    instr 全稱是 Instructions

    瞭解更多:Flutter-engine-operation-in-AOT-Mode

    • isolate_snapshot_data:

      表示 isolate 堆存儲區的初始狀態和特定的信息。和 vm_snapshot_data 配合,更快的啓動 Dart VM。

    • isolate_snapshot_instr:

      包含由 Dart isolate 執行的 AOT 代碼。

    • vm_snapshot_data:

      表示 isolates 之間的共享的 Dart 堆存儲區的初始狀態,用於更快的啓動 Dart VM。

    • vm_snapshot_instr:

      包含 VM 中全部的 isolates 之間共享的常見例程的指令

    isolate_snapshot_data 和 isolate_snapshot_instr 跟業務相關,而 vm_snapshot_data 和 vm_snapshot_instr 則是跟 VM 相關,無關業務。

    咱們可使用 strings 查看下 isolate_snapshot_data 中內容:

    isolate_snapshot_data

    小結

    上面兩種模式相關的代碼以下:

    final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');                                              
    if (buildSharedLibrary || platform == TargetPlatform.ios) {                                                               
      // Assembly AOT snapshot. 
      outputPaths.add(assembly);                                                                                              
      genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');                                                                
      genSnapshotArgs.add('--assembly=$assembly');                                                                            
    } else {                                                                                                                  
      // Blob AOT snapshot. 
      final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data');                                         
      final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');                               
      final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr');                                
      final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');                      
      outputPaths.addAll(<String>[vmSnapshotData, isolateSnapshotData, vmSnapshotInstructions, isolateSnapshotInstructions]); 
      genSnapshotArgs.addAll(<String>[                                                                                        
        '--snapshot_kind=app-aot-blobs',                                                                                      
        '--vm_snapshot_data=$vmSnapshotData',                                                                                 
        '--isolate_snapshot_data=$isolateSnapshotData',                                                                       
        '--vm_snapshot_instructions=$vmSnapshotInstructions',                                                                 
    複製代碼

'--isolate_snapshot_instructions=$isolateSnapshotInstructions',
]);
}

從執行速度來看,app-aot-assemble 是要快於 app-aot-blobs 的,由於它不須要 Dart VM 環境,只須要 Dart Runtime 便可,而 snapshots 文件是須要 Dart VM 去加載執行的。但使用 snapshots 使得動態執行代碼變成可能。

iOS 默認使用 app-aot-assemble 模式,更可能是 App Store 自己的限制:

> 引用於[flutters-compilation-patterns](https://proandroiddev.com/flutters-compilation-patterns-24e139d14177)
>
> App Store does not allow dispatch binary executable code. 

而 Android 默認使用 app-aot-blobs 模式,可能更可能是從性能方面考慮,必須調用從 so 文件中調用 native 函數,須要用 JNI,有性能損耗,並且調用也比較麻煩,不過在 Flutter SDK 1.7.x 已經改爲 app-aot-assemble 模式。
#### Build Bundle

當執行 `flutter build bundle` 時,相關的代碼邏輯在 BuildBundleCommand 中,build bundle 主要作兩件事:第一,若是沒有添加 `--precompiled` 選項時,會先編譯 kernel;第二,生成 assets(圖片、字體等)。

1. 編譯 kernel。這個步驟和 build aot 的第一個步驟是同樣的,最終會生成 app.dill,這是業務代碼編譯後的產物。同時,這個會建立一個 kernelContent,這個在第二個步驟會講到:

   ``` dart
   kernelContent = DevFSFileContent(fs.file(compilerOutput.outputFilename));
   ```

2. 生成 assets。首先,會收集圖片資源、字體等:

   ``` dart
   final AssetBundle assets = await buildAssets(           
     manifestPath: manifestPath,                           
     assetDirPath: assetDirPath,                           
     packagesPath: packagesPath,                           
     reportLicensedPackages: reportLicensedPackages,       
   );                                                      
   ```

   其中包含有如下內容:

   ![assets](https://user-gold-cdn.xitu.io/2019/8/30/16ce1dca5d92427d?w=745&h=265&f=png&s=49891)

   接着,會將這些,包含上面生成 kernelContent,一塊兒收集到指定目錄,kernelContent 就是編譯生成的 app.dill,但這裏會拷貝並重命名爲 kernel_blob.bin:

   ``` dart
   const String _kKernelKey = 'kernel_blob.bin';                   
   const String _kVMSnapshotData = 'vm_snapshot_data';             
   const String _kIsolateSnapshotData = 'isolate_snapshot_data';   
   
   final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: buildMode);            
   final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: buildMode); 
   assetEntries[_kKernelKey] = kernelContent;                                                                    
   assetEntries[_kVMSnapshotData] = DevFSFileContent(fs.file(vmSnapshotData));                                   
   assetEntries[_kIsolateSnapshotData] = DevFSFileContent(fs.file(isolateSnapshotData));                         
   ```

   咱們注意到,這裏一樣會生成 vm_snapshot_data 和 isolate_snapshot_data,但並無生成相應的指令集。在這裏的 isolate_snapshot_data 中並不會包含咱們的業務代碼,業務代碼會存放在 kernel_blob.bin 文件中。可使用 `strings` 命令查看,並且這兩個文件的 MD5 值是一致的。

   ```
   MD5 (app.dill) = 3876e8c6f4b13a88cc3cfc3b9fd108c4
   MD5 (flutter_assets/kernel_blob.bin) = 3876e8c6f4b13a88cc3cfc3b9fd108c4
   ```
   
   關於 snapshot 生成相關能夠去看 **gen_snapshot**。

#### 小結

##### debug

debug 模式下,會執行一個命令:

``` shell
flutter build bundle
複製代碼

它會生成 assets、vm_snapshot_data、isolate_snapshot_data 和 kernel_bloc.bin。其中咱們的業務代碼存放在 kernel_bloc.bin 中,它是 app.dill 的拷貝。

release

release 模式下,會執行兩個命令:

flutter build aot
複製代碼

它會先生成 app.dill,並且這裏分爲兩種模式:app-aot-assemble 和 app-aot-blobs。iOS 或者 添加了 --build-shared-library 會使用 app-aot-assemble,這種模式會生成 App.Framework 或 app.so。Android 默認使用 app-aot-blobs,這種模式會生成 isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data 和 vm_snapshot_instr 四個文件。

flutter build bundle --precompiled
複製代碼

這裏只會生成 assets。

調試源碼

調試是閱讀源碼最好的幫手,調試 flutter_tools 的方式比較簡單,我用的是 IntelliJ,Android Studio 應該也相似,先導入源碼,源碼位於 sdk/packages/flutter_tools;新建一個 Dart Command Line App,設置 Dart file 爲 bin/flutter_tools.dart,Program arguments 設置你要調試的命令,好比 build aot,Working directory 則用 flutter create 建立個項目便可;最後打好斷點,debug 運行。

debug_tools

由於源碼中有不少異步代碼,用 await 修飾的,可使用 Force run to Cursor 直接執行到下一行便可。

結尾

爲了不篇幅太長,文章儘可能避免貼不少的代碼,只是貼了關鍵函數,感興趣的讀者能夠去閱讀相關的源碼。

Android 在 release 模式下,使用 app-aot-blobs 模式,用的是 snapshot 文件,這裏要實現動態下發代碼,應該仍是有可能的,須要進一步研究。下一步應該會研究下熱加載的實現,這對於實現動態化應該頗有幫助。

最後,Flutter 是一個很是好玩的事物,Dart 也是一個很是優秀的語言,enjoy it。

由於我用習慣了 Typora 寫 Markdown 了,但又須要用到掘金的圖牀,因此寫了個小腳本用來上傳圖片。

import requests
import sys

if __name__ == "__main__":
    file_path = sys.argv[1]
    upload_file = open(file_path,mode='rb')
    data = requests.post('https://cdn-ms.juejin.im/v1/upload?bucket=gold-user-assets',files={'file': upload_file}).json()
    print('upload url: ' + data['d']['url']['https'])
複製代碼
相關文章
相關標籤/搜索