flutter driverでassetsが原因で画像が表示されなくなった件 #flutter
flutter driverでE2Eテストをゴリゴリ書こうとした時にハマった内容と暫定的な対処方法を共有します。
※ 2020/10/21 追記あり
ある日突然flutter driverで画像が表示されなくなった
flutterではflutter driverというIntegration test用のFWが用意されています。
公式を参考にそれっぽいシナリオを実行していたのですが、ある日突然今まで成功していたシナリオが失敗するようになりました。
あれれと思ってエミュレータを見てみると、画像が一切表示されていませんでした。
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を発見。
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
対象方法
えーどうしよう...と思ってflutterのソースを眺めてたらそこの制御をしているコードを発見。
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くらいええやん...。
最新の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テストが再び通るようになりました🎉🎉🎉
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());