跳到主內容

Android 和 Web 的延遲元件

如何建立延遲元件以提高下載效能。

介紹

#

使用 Flutter,Android 和 Web 應用能夠下載延遲元件(額外的程式碼和資源),而應用已經在執行。如果您的應用很大,並且只想在使用者需要時才安裝元件,這將很有幫助。

雖然 Flutter 支援 Android 和 Web 上的延遲載入,但實現方式不同。兩者都需要 Dart 的延遲匯入

  • Android 的 動態功能模組 將延遲元件作為 Android 模組打包提供。

    在構建 Android 應用時,雖然您可以延遲載入模組,但必須構建整個應用並將其作為單個 Android 應用包 (AAB) 上傳。Flutter 不支援重新上傳整個應用程式的新 Android 應用包而不進行部分更新。

    當您在 釋出或分析模式 下編譯 Android 應用時,Flutter 會執行延遲載入,但除錯模式會將所有延遲元件視為常規匯入。

  • Web 將延遲元件建立為單獨的 *.js 檔案。

要深入瞭解此功能的具體技術細節,請參閱 Deferred ComponentsFlutter wiki 上的內容。

如何為 Android 專案設定延遲元件

#

以下說明介紹瞭如何為延遲載入設定 Android 應用。

步驟 1:依賴項和初始專案設定

#
  1. 將 Play Core 新增到 Android 應用的 build.gradle 依賴項中。在 android/app/build.gradle 中新增以下內容

    android/app/build.gradle.kts
    kotlin
    ...
    dependencies {
      ...
      implementation("com.google.android.play:core:1.8.0")
      ...
    }
    
    android/app/build.gradle
    groovy
    ...
    dependencies {
      ...
      implementation "com.google.android.play:core:1.8.0"
      ...
    }
    
  2. 如果使用 Google Play 商店作為動態功能的釋出模型,則該應用必須支援 SplitCompat 並提供 PlayStoreDeferredComponentManager 的例項。這兩個任務都可以透過在 android/app/src/main/AndroidManifest.xml 中將應用程式的 android:name 屬性設定為 io.flutter.embedding.android.FlutterPlayStoreSplitApplication 來完成。

    xml
    <manifest ...
      <application
         android:name="io.flutter.embedding.android.FlutterPlayStoreSplitApplication"
            ...
      </application>
    </manifest>
    

    io.flutter.app.FlutterPlayStoreSplitApplication 會為您處理這兩個任務。如果您使用 FlutterPlayStoreSplitApplication,則可以跳到步驟 1.3。

    如果您的 Android 應用程式很大或很複雜,您可能希望單獨支援 SplitCompat 並手動提供 PlayStoreDynamicFeatureManager

    要支援 SplitCompat,有三種方法(如 Android 文件 中所述),任何一種方法都是有效的

    • 使您的應用程式類擴充套件 SplitCompatApplication

      java
      public class MyApplication extends SplitCompatApplication {
          ...
      }
      
    • attachBaseContext() 方法中呼叫 SplitCompat.install(this);

      java
      @Override
      protected void attachBaseContext(Context base) {
          super.attachBaseContext(base);
          // Emulates installation of future on demand modules using SplitCompat.
          SplitCompat.install(this);
      }
      
    • SplitCompatApplication 宣告為應用程式子類,並將 Flutter 相容性程式碼從 FlutterApplication 新增到您的應用程式類

      xml
      <application
          ...
          android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
      </application>
      

    嵌入器依賴於注入的 DeferredComponentManager 例項來處理延遲元件的安裝請求。透過將以下程式碼新增到您的應用初始化中,為 Flutter 嵌入器提供 PlayStoreDeferredComponentManager

    java
    import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDeferredComponentManager;
    import io.flutter.FlutterInjector;
    ...
    PlayStoreDeferredComponentManager deferredComponentManager = new
      PlayStoreDeferredComponentManager(this, null);
    FlutterInjector.setInstance(new FlutterInjector.Builder()
        .setDeferredComponentManager(deferredComponentManager).build());
    
  3. 透過將 deferred-components 條目新增到應用程式的 pubspec.yaml 檔案下的 flutter 條目中,選擇加入延遲元件

    yaml
    ...
    flutter:
      ...
      deferred-components:
      ...
    

    flutter 工具會在 pubspec.yaml 中查詢 deferred-components 條目,以確定是否應將應用構建為延遲應用。現在可以將其留空,除非您已經知道所需的元件以及進入每個元件的 Dart 延遲庫。您將在 步驟 3.3 中填寫此部分,一旦 gen_snapshot 生成載入單元。

步驟 2:實現延遲的 Dart 庫

#

接下來,在您的應用的 Dart 程式碼中實現延遲載入的 Dart 庫。實現不必立即完成。本頁面的其餘部分中的示例添加了一個新的簡單延遲小部件作為佔位符。您還可以透過修改匯入並在延遲程式碼的使用前使用 loadLibrary() Future 來轉換現有程式碼為延遲載入。

  1. 建立一個新的 Dart 庫。例如,建立一個新的 DeferredBox 小部件,可以在執行時下載。此小部件可以具有任意複雜性,但為了本指南的目的,建立一個簡單的框作為替代。要建立一個簡單的藍色框小部件,請建立 box.dart 並新增以下內容

    box.dart
    dart
    import 'package:flutter/material.dart';
    
    /// A simple blue 30x30 box.
    class DeferredBox extends StatelessWidget {
      const DeferredBox({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Container(height: 30, width: 30, color: Colors.blue);
      }
    }
    
  2. 使用 deferred 關鍵字在您的應用中匯入新的 Dart 庫,並呼叫 loadLibrary()(請參閱 延遲載入庫)。以下示例使用 FutureBuilder 等待 loadLibrary Future(在 initState 中建立)完成,並顯示一個 CircularProgressIndicator 作為佔位符。當 Future 完成時,它將返回 DeferredBox 小部件。然後,可以在應用中使用 SomeWidget,並且永遠不會嘗試訪問延遲的 Dart 程式碼,直到它成功載入為止。

    dart
    import 'package:flutter/material.dart';
    import 'box.dart' deferred as box;
    
    class SomeWidget extends StatefulWidget {
      const SomeWidget({super.key});
    
      @override
      State<SomeWidget> createState() => _SomeWidgetState();
    }
    
    class _SomeWidgetState extends State<SomeWidget> {
      late Future<void> _libraryFuture;
    
      @override
      void initState() {
        super.initState();
        _libraryFuture = box.loadLibrary();
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder<void>(
          future: _libraryFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              if (snapshot.hasError) {
                return Text('Error: ${snapshot.error}');
              }
              return box.DeferredBox();
            }
            return const CircularProgressIndicator();
          },
        );
      }
    }
    

    loadLibrary() 函式返回一個 Future<void>,當庫中的程式碼可用時,該函式將成功完成,否則將完成並出現錯誤。應將對延遲庫中所有符號的使用都保護在完成的 loadLibrary() 呼叫之後。必須將庫的所有匯入標記為 deferred,才能將其編譯為適當的形式,以便在延遲元件中使用。如果元件已經載入,則對 loadLibrary() 的額外呼叫將快速完成(但不同步)。也可以提前呼叫 loadLibrary() 以觸發預載入,以幫助掩蓋載入時間。

    您可以在 Flutter Gallery 的 lib/deferred_widget.dart 中找到另一個延遲匯入載入示例。

步驟 3:構建應用

#

使用以下 flutter 命令構建延遲元件應用

flutter build appbundle

此命令透過驗證您的專案是否已正確設定以構建延遲元件應用來幫助您。預設情況下,如果驗證程式檢測到任何問題,構建將失敗,並指導您進行建議的更改以解決這些問題。

  1. flutter build appbundle 命令執行驗證程式並嘗試使用指示 gen_snapshot 生成拆分 AOT 共享庫作為單獨的 SO 檔案的應用。首次執行時,驗證程式可能會失敗,因為它檢測到問題;該工具會提出建議,說明如何設定專案並解決這些問題。

    驗證程式分為兩個部分:預構建驗證和生成快照後驗證。這是因為任何引用載入單元的驗證都必須在 gen_snapshot 完成並生成最終的載入單元集後才能執行。

    驗證程式檢測 gen_snapshot 生成的任何新的、更改的或刪除的載入單元。當前生成的載入單元在您的 <projectDirectory>/deferred_components_loading_units.yaml 檔案中跟蹤。應將此檔案簽入原始碼控制,以確保其他開發人員對載入單元的更改可以被捕獲。

    驗證程式還在 android 目錄中檢查以下內容

    • <projectDir>/android/app/src/main/res/values/strings.xml
      為每個延遲元件對映鍵 ${componentName}Name${componentName} 的條目。此字串資源由每個功能模組的 AndroidManifest.xml 用於定義 dist:title property。例如

      xml
      <?xml version="1.0" encoding="utf-8"?>
      <resources>
        ...
        <string name="boxComponentName">boxComponent</string>
      </resources>
      
    • <projectDir>/android/<componentName>
      為每個延遲元件存在 Android 動態功能模組,幷包含 build.gradlesrc/main/AndroidManifest.xml 檔案。這僅檢查是否存在,並且不驗證這些檔案的內容。如果檔案不存在,它將生成一個預設的推薦檔案。

    • <projectDir>/android/app/src/main/res/values/AndroidManifest.xml
      包含編碼載入單元與關聯載入單元元件名稱之間對映的元資料條目。嵌入器使用此對映將 Dart 的內部載入單元 ID 轉換為要安裝的延遲元件的名稱。例如

      xml
      ...
      <application
          android:label="MyApp"
          android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
          android:icon="@mipmap/ic_launcher">
          ...
          <meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="2:boxComponent"/>
      </application>
      ...
      

    gen_snapshot 驗證程式在預構建驗證程式透過之前不會執行。

  2. 對於這些檢查中的每一個,該工具都會生成修改或新的檔案,以透過檢查。這些檔案放置在 <projectDir>/build/android_deferred_components_setup_files 目錄中。建議透過複製和覆蓋專案 android 目錄中的相同檔案來應用更改。在覆蓋之前,應將當前專案狀態提交到原始碼控制,並應審查建議的更改以確保其適當。該工具不會自動對您的 android/ 目錄進行任何更改。

  3. 一旦生成了可用的載入單元並記錄在 <projectDirectory>/deferred_components_loading_units.yaml 中,就可以完全配置 pubspec.yamldeferred-components 部分,以便將載入單元分配給所需的延遲元件。繼續使用框示例,生成的 deferred_components_loading_units.yaml 檔案將包含

    yaml
    loading-units:
      - id: 2
        libraries:
          - package:MyAppName/box.Dart
    

    載入單元 ID(在本例中為“2”)由 Dart 內部使用,可以忽略。基礎載入單元(ID 為“1”)未列出,包含未明確包含在其他載入單元中的所有內容。

    現在,您可以將以下內容新增到 pubspec.yaml

    yaml
    ...
    flutter:
      ...
      deferred-components:
        - name: boxComponent
          libraries:
            - package:MyAppName/box.Dart
      ...
    

    要將載入單元分配給延遲元件,請將載入單元中的任何 Dart 庫新增到功能模組的 libraries 部分。請記住以下準則:

    • 載入單元不應包含在多個元件中。

    • 包含來自載入單元的一個 Dart 庫表示整個載入單元已分配給延遲元件。

    • 未分配給延遲元件的所有載入單元都包含在基礎元件中,基礎元件始終隱式存在。

    • 分配給相同延遲元件的載入單元將一起下載、安裝和交付。

    • 基礎元件是隱式的,無需在 pubspec 中定義。

  4. 還可以透過在延遲元件配置中新增 assets 部分來包含資源。

    yaml
      deferred-components:
        - name: boxComponent
          libraries:
            - package:MyAppName/box.Dart
          assets:
            - assets/image.jpg
            - assets/picture.png
              # wildcard directory
            - assets/gallery/
    

    可以將資源包含在多個延遲元件中,但安裝這兩個元件會導致資源複製。也可以透過省略 libraries 部分來定義僅包含資源的元件。這些僅包含資源的元件必須使用 DeferredComponent 服務類中的實用程式類進行安裝,而不是 loadLibrary()。由於 Dart 庫與資源一起打包,因此如果使用 loadLibrary() 載入 Dart 庫,則元件中的所有資源也會載入。但是,透過元件名稱和 services 實用程式進行安裝不會載入元件中的任何 Dart 庫。

    您可以自由地將資源包含在任何元件中,只要在首次引用時安裝並載入它們即可,儘管通常,資源和使用這些資源的 Dart 程式碼最好打包在同一個元件中。

  5. 手動將您在 pubspec.yaml 中定義的所有延遲元件新增到 android/settings.gradle 檔案中作為 includes。例如,如果在 pubspec 中定義了三個延遲元件,名為 boxComponentcircleComponentassetComponent,請確保 android/settings.gradle 包含以下內容:

    android/settings.gradle.kts
    kotlin
    include(":app", ":boxComponent", ":circleComponent", ":assetComponent")
    ...
    
    android/settings.gradle
    groovy
    include ':app', ':boxComponent', ':circleComponent', ':assetComponent'
    ...
    
  6. 重複步驟 3.1 到 3.6(此步驟),直到處理完所有驗證器建議並且工具執行沒有進一步的建議為止。

    成功後,此命令將在 build/app/outputs/bundle/release 中輸出一個 app-release.aab 檔案。

    成功的構建並不總是意味著應用程式是按預期構建的。確保所有載入單元和 Dart 庫都包含在您預期的方式中,這取決於您。例如,一個常見的錯誤是意外地匯入一個沒有 deferred 關鍵字的 Dart 庫,導致延遲庫被編譯為基礎載入單元的一部分。在這種情況下,Dart 庫將正確載入,因為它始終存在於基礎中,並且庫不會被拆分。可以透過檢查 deferred_components_loading_units.yaml 檔案來驗證生成的載入單元是否按預期描述。

    在調整延遲元件配置或進行新增、修改或刪除載入單元的 Dart 更改時,您應該預計驗證器會失敗。按照步驟 3.1 到 3.6(此步驟)應用任何建議的更改以繼續構建。

在本地執行應用

#

一旦您的應用程式成功構建了 AAB 檔案,請使用 Android 的 bundletool 使用 --local-testing 標誌進行本地測試。

要在測試裝置上執行 AAB 檔案,請從 github.com/google/bundletool/releases 下載 bundletool jar 可執行檔案並執行

java -jar bundletool.jar build-apks --bundle=<your_app_project_dir>/build/app/outputs/bundle/release/app-release.aab --output=<your_temp_dir>/app.apks --local-testing

java -jar bundletool.jar install-apks --apks=<your_temp_dir>/app.apks

其中 <your_app_project_dir> 是應用程式專案目錄的路徑,而 <your_temp_dir> 是用於儲存 bundletool 輸出的任何臨時目錄。這將您的 AAB 檔案解包到一個 APK 檔案並在裝置上安裝它。所有可用的 Android 動態功能都載入到裝置上本地,並且延遲元件的安裝被模擬。

在再次執行 build-apks 之前,請刪除現有的應用程式 APK 檔案

rm <your_temp_dir>/app.apks

Dart 程式碼庫的更改需要增加 Android 構建 ID 或解除安裝並重新安裝應用程式,因為 Android 不會在檢測到新版本號之前更新功能模組。

釋出到 Google Play 商店

#

構建的 AAB 檔案可以直接上傳到 Play 商店,如正常操作一樣。當呼叫 loadLibrary() 時,包含 Dart AOT 庫和資源的所需 Android 模組將由 Flutter 引擎使用 Play 商店的交付功能下載。