Flutter UXを支えるパフォーマンス向上Tips

Anycloudの青木です。
プロダクト(Flutterアプリ)が成長し、機能が増えるにつれ、どんどん大きくなるコードベース。気づけば動作が重くなり、ユーザー体験が損なわれていく...。そんな問題に直面したくありませんよね…?
今回は、Flutter公式ドキュメントにあるPerformance best practicesをもとに、パフォーマンス向上に役立つTipsをまとめてみました!
16ミリ秒でフレームを構築して表示せよ
フレームを描画するには、ビルドとレンダリングの2つの独立したスレッドがある
そして60fpsのデバイスでは、およそ16ミリ秒ごとで更新する
なぜ、フレームをできるだけ速くビルドしてレンダリングすることを目指すのか?
- Jankが発生する
- Jankとは、画面の動き(アニメーションやスクロール)がカクついたり、一瞬止まったりしてスムーズに見えない現象です。画面の描画がフレーム落ちしている状態と言えます。
- バッテリーの寿命や発熱の問題が発生する
⭐️コラム
- なぜ60fpsなのか
- どう検証する?
- ターゲットとする最も低スペックのデバイスでのパフォーマンスを考慮する
- Flutter mode: Profileを使用する
flutter run --profile
- DevToolにおけるPerfomance view

- Android StudioにおけるFlutter Performance
- Show widget rebuild informationを有効にする
build( ) のコスト制御せよ
- 状態をなるべく必要なWidgetに閉じ込める
- 頻繁に更新される部分は専用のStatefulWidgetにして、そのWidgetだけを再描画する
- 無駄なコンテナや余分な階層を削減して、Widget ツリーをシンプル に保つ
buildメソッドでの Widget の作りすぎはパフォーマンスダウンの原因になる
- 変わらないサブツリーはキャッシュして使い回す
- 可能な限りconst widgetsを使う
- final 変数にWidgetを保持する
- 例)
StatefulWidgetのStateクラス内でfinal _cachedWidget = SomeWidget(...);のように初期化しておく
- 例)
- サブツリーの一部だけが変わるなら、その部分を別のStatefulWidgetに切り出すことで、変化しない部分をキャッシュする
- 例)
AnimatedBuilderのbuilder関数内でアニメーションに依存しないサブツリーを含めない- 毎フレームで再ビルドされるため、アニメーションに依存しない部分を一度だけビルドし、
AnimatedBuilderのchildパラメータとして渡すことで、再ビルドを防ぎ、効率的なアニメーションを実現できる - https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html#performance-optimizations
AnimatedBuilder( animation: myAnimation, child: MyStaticWidget(), // アニメーションに依存しないサブツリー builder: (context, child) { return Transform.rotate( angle: myAnimation.value, child: child, // 再ビルドされないサブツリーを再利用 ); }, ); - 毎フレームで再ビルドされるため、アニメーションに依存しない部分を一度だけビルドし、
- 例)
- ツリーの深さや種類を頻繁に変えず、できるだけ同じ構造のままでプロパティだけ変える
- ツリー構造が少しでも変化すると、フレームワークが再ビルドや再レイアウト、再ペイントを行う可能性があるため
- 例)
IgnorePointerをラップし、プロパティを切り替える- Bad
Widget build(BuildContext context) { if (someCondition) { // 子をそのまま返す return child; } else { // 子を IgnorePointer で包む return IgnorePointer(child: child); } } - Good
Widget build(BuildContext context) { return IgnorePointer( ignoring: someCondition, // true or false を切り替える child: child, ); }
- Bad
- 例)
- Widget構造を変えなければならないとき は、共通部分を
GlobalKey付きのWidgetで包むと、Flutter はその部分を「同一インスタンス」として扱い、不要な再構築・状態リセットを避けられるKeyedSubtreeWidgetは、他のWidgetにキーを割り当てることができない場合に便利である
- ツリー構造が少しでも変化すると、フレームワークが再ビルドや再レイアウト、再ペイントを行う可能性があるため
- UI を再利用するときはヘルパーメソッドではなく、新しいWidgetクラスとして切り出す
- Keyやコンストラクタのパラメータを使って、Widgetを識別できるようになるため
- 変更が一切ないウィジェットであれば、
constを使うと最も高速に描画・再利用できる - Bad
Widget buildOptionRow(String title) { return Row( children: [ Icon(Icons.star), Text(title), ], ); } - Good
class OptionRow extends StatelessWidget { final String title; const OptionRow({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { return Row( children: [ const Icon(Icons.star), Text(title), ], ); } } - https://youtu.be/IOyq-eTRhvo
参考にした公式ドキュメント
saveLayer()は慎重に使え
UIに様々な視覚効果を実装するために、高価な操作であるsaveLayer()を使うものがある
ただし、saveLayer() の過剰な呼び出しはJankの原因になる
なぜ、saveLayer()は高価なのか?
saveLayer()を呼び出すと、オフスクリーンバッファ(裏側の作業用スペース) が作られる- そこに描画した内容を最後に画面(オンスクリーン)に合成するため、GPU は「メイン画面 → オフスクリーンバッファ → 再びメイン画面」 と描画先を切り替える
- 特にスマホのようなモバイルGPU は、描画先を切り替えるとパフォーマンスが落ちやすい
- できるだけ画面に直接描画して切り替え回数を減らすと、アプリが軽く動きやすい
検証方法
- DevToolsにおけるPerformanceOverlayLayer.checkerboardOffscreenLayersをチェックする
- FlutterにおけるMaterialAppのcheckerboardOffscreenLayersをtrueにする
MaterialApp( checkerboardOffscreenLayers: true, ... )
⚠️要注意
明示的にsaveLayer()を呼び出していなくても、使っている他のWidgetやパッケージが裏でsaveLayer()を呼び出している可能性がある
- 内部で
saveLayer()を使用しているWidgets- ShaderMask
- ColorFilter
- Chip(
disabledColorAlpha != 0xffの場合) - Text(
overflowShaderを使用している場合)
OpacityとClippingの使用は最小限にせよ
OpacityとClippingも同様に高価な操作である
Opacity代用Widget
- 単純に、0.0 - 1.0の間の不透明度を合成する画像や色が一つだけの場合
Color.withOpacity,Image.withOpacityを使う
- 画像のフェードイン効果を実装したい場合
FadeInImageを使う
- アニメーションで不透明度を変えたい場合
AnimatedOpacity,FadeTransitionを使う
Clipping代用Widget
- 多くのWidgetクラスで提供されている
borderRadiusを使う- 例)BoxDecoration
参考にした公式ドキュメント
GridsやListsを慎重に実装せよ
GridViewやListViewは小さなリストではうまく機能するが、多くのアイテムを含むリストを扱うには、GridView.builderやListView.builderを使用する
標準のGridViewやListViewでは、すべてのアイテムを一度に作成する必要があるのに対し、GridView.builderやListView.builderでは、画面にスクロールされるたびにアイテムを作成する
まとめ
アプリのパフォーマンスを意識して、不要な再ビルドや高価な描画処理を避けることが重要で、フレームワークの仕組みを理解しつつ、Widgetの構成や再利用方法を工夫するだけで大きく変わるのでぜひ実践してみてください!
そしてその結果、パフォーマンスが向上し、ユーザー体験につなげていきましょう!
Anycloudでは一緒に働くメンバーを募集しています!
Anycloudは、ユーザーの心を動かす体験を届けることを大切にしています。フルリモート・フルフレックスの環境のもと、ライフスタイルに合わせた働き方を実現しながら挑戦したい方を歓迎します。詳細はこちらをご覧ください。





