つばくろぐ @takamii228

知は力なり

flutter driverでassetsが原因で画像が表示されなくなった件 #flutter

f:id:takamii228:20190829204538j:plain

flutter driverでE2Eテストをゴリゴリ書こうとした時にハマった内容と暫定的な対処方法を共有します。

※ 2020/10/21 追記あり

ある日突然flutter driverで画像が表示されなくなった

flutterではflutter driverというIntegration test用のFWが用意されています。

flutter.dev

公式を参考にそれっぽいシナリオを実行していたのですが、ある日突然今まで成功していたシナリオが失敗するようになりました。

あれれと思ってエミュレータを見てみると、画像が一切表示されていませんでした。

flutter doctorはこんな感じ。

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel unknown, v1.5.4-hotfix.2, on Mac OS X 10.14.6 18G87, locale ja-JP)
[] Android toolchain - develop for Android devices (Android SDK version 29.0.0)
[] iOS toolchain - develop for iOS devices (Xcode 10.2.1)
[] Android Studio (version 3.4)
[!] VS Code (version 1.37.1)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[] Connected device (2 available)

assetsの数が多すぎるのが原因っぽい

おかしいなと思ってコミットの差分を追ってみると、どうやら画像を大量に追加した前後で挙動がおかしくなっていた。

でも普通にflutter runすると正しく画像は表示されていた。おかしいなぁと思っていろいろググってみるとgithubのIssueを発見。

github.com

After days of investigation, I've found that it may have the same cause with #24703. When the AssetManifest.json file size larger than 10 * 1024, it will call compute, then Isolate.spawn will be called which won't work for flutter drive. So images can NOT be displayed. However when AssetManifest.json file size smaller than 10*1024, then It works well. That's why image assets can be displayed for some certain apps.

なんと、AssetManifest.jsonのファイルサイズが 10 * 1024を超えると新しくIsolate.spawnが呼ばれて、画像が読めなくなってflutter driverが動かなくなる模様。

AssetManifest.jsonはassetsのファイル名とパスをkey / valueでまとめたjsonファイルで、iosはApp.Frameworkの中に、androidはbuild配下にビルド時に自動生成されるファイルのようです。

assetsはpubspec.ymlでフォルダごと読み込むようにしていました。

...
flutter:
  uses-material-design: true

  assets:
    - assets/
...

ファイルのサイズを確認すると、確かに10 * 1024は超えていてflutterのObservatory URLを見に行ってみるとspawnのisolateが起動されていました。

$ ls -la AssetManifest.json 
-rw-r--r--  1 takamii228  staff  10905  8 28 22:11 AssetManifest.json

f:id:takamii228:20190829201850p:plain

対象方法

えーどうしよう...と思ってflutterのソースを眺めてたらそこの制御をしているコードを発見。

github.com

  Future<String> loadString(String key, { bool cache = true }) async {
    final ByteData data = await load(key);
    if (data == null)
      throw FlutterError('Unable to load asset: $key');
    if (data.lengthInBytes < 10 * 1024) {
      // 10KB takes about 3ms to parse on a Pixel 2 XL.
      // See: https://github.com/dart-lang/sdk/issues/31954
      return utf8.decode(data.buffer.asUint8List());
    }
    return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"');
  }

Pixel 2XLだと、10 * 1024を超えるとparseに3msかかるらしい。3msくらいええやん...。

github.com

最新のmasterでもこのソースのままだったので、アドホックですがflutter driverの実行前後でこの上限を緩和するpatchを当てるようなシェルを組みました。

asset_bundle.patch

--- a/development/flutter/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/development/flutter/packages/flutter/lib/src/services/asset_bundle.dart
@@ -67,7 +67,7 @@ abstract class AssetBundle {
     final ByteData data = await load(key);
     if (data == null)
       throw FlutterError('Unable to load asset: $key');
-    if (data.lengthInBytes < 10 * 1024) {
+    if (data.lengthInBytes < 1000 * 1024) {
       // 10KB takes about 3ms to parse on a Pixel 2 XL.
       // See: https://github.com/dart-lang/sdk/issues/31954
       return utf8.decode(data.buffer.asUint8List());

flutter-driver.sh

#!/bin/sh

set -x

# 実行前にパッチを当てる
CURRENT_DIR=`pwd`
cd $HOME
patch -p1 -N < ${CURRENT_DIR}/asset_bundle.patch || true
cd ${CURRENT_DIR}

# flutter driverを実行
flutter driver --debug --flavor develop --target test_driver/app.dart  -d xxxxxx

# 実行後は元に戻す
cd $HOME
patch -p1 -N -R < ${CURRENT_DIR}/asset_bundle.patch || true
cd ${CURRENT_DIR}

パッチを当ててから実行するようにしたら無事spawnのIsolateが消えてflutter driverでも画像が表示され、E2Eテストが再び通るようになりました🎉🎉🎉

f:id:takamii228:20190829203547p:plain

flutter driverは公式の情報がまだまだ少なく、この後もいろいろ踏みそうですがなんとか食らいついて頑張っていこうと思います。

【追記 2020/10/21】パッチファイルの修正が必要

flutter 1.22.0よりasset_ bundle.dartに修正が入ったためパッチファイルも修正が必要になっています。画像が読み込まれなくなる事象はまだ発生するようなので引き続きご注意ください。

asset_bundle.patch

--- a/development/flutter/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/development/flutter/packages/flutter/lib/src/services/asset_bundle.dart
@@ -71,7 +71,7 @@ abstract class AssetBundle {
     // that the null-handling logic is dead code).
     if (data == null)
       throw FlutterError('Unable to load asset: $key'); // ignore: dead_code
-    if (data.lengthInBytes < 10 * 1024) {
+    if (data.lengthInBytes < 1000 * 1024) {
       // 10KB takes about 3ms to parse on a Pixel 2 XL.
       // See: https://github.com/dart-lang/sdk/issues/31954
       return utf8.decode(data.buffer.asUint8List());