🤖

Flutterのパフォーマンスの測定

2024/08/01に公開

パフォーマンスについて

基礎から学ぶ Flutterという2019年に出版された本ですが、日本語の解説で、Flutterのパフォーマンスについて解説されていた内容が勉強になって今回は、自分なりにですが、試してみました。Flutterには、パフォーマンス測定のプロファイリングツールがあります。このツールでできることは以下の内容です。

  1. フレームのレンダリング時間や秒間のフレーム数
  2. Widgetの再構築数
  3. UI ThreadとGPU Thread(線のことでしょうね。)
  4. Memory

GPU(Graphics Processing Unit)のThread(スレッド)とは、GPU内部のアーキテクチャで一次元~三次元で定義できる構造体です。すべてのThreadにはIDが割り当てられており、threadIdxやblockIdxなどの構造体で個々のThreadを識別します。

古いソースコードだったので、修正して使ってます。

環境 内容
Flutter 3.22.3 最新版
Android Studio ハリネズミなのでUIが新しい

Widgetの再構築数が問題の場合

スクロールするたびに、Widgetが再構築されて、フレームのレンダリングに、 16ms以上かかっている。測定するときで値が違うと思いますが、1秒間に29.8かかっていました。本来なら、60フレームでできます。これは相当パフォーマンスが悪い。スクロールすると動きがカクカクしてしまいます。

ListTileのText Widgetが大量に構築されてその他のWidgetも何度も再構築されてしまいます。この機能は、Android Studioでしか使えません。

画面上のHelpのメニューからFlutter Perfomanceと検索すると表示できます。



参考にしたコードが、 null safty前のものだったので、修正して使ってます。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ScrollController _scrollController = ScrollController();

  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      // ここが問題
      setState(() {
        _incrementCounter();
      });
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('無駄な再構築を防ぐ例'),
      ),
      body: ListView.builder(
        controller: _scrollController,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('counter $index'),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }
}


丸のところにチェックをつけると、Widgetを再構築しているか確認できるようになります。

改善策

pixels propertymaxScrollExtent propertyを使用して、スクロール位置が最下部に達したら再構築するように設定すると改善されました。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ScrollController _scrollController = ScrollController();

  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  // スクロール位置が最下部に達したかどうかを判定する
  bool shouldRefresh() {
    return _scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent;
  }

  
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      // スクロール位置が最下部に達したら再構築する
      if (shouldRefresh()) {
        print("rebuild");
        setState(() {
          _incrementCounter();
        });
      }
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('無駄な再構築を防ぐ例'),
      ),
      body: ListView.builder(
        controller: _scrollController,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('counter $index'),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }
}

通常の望ましい数値の60fpsとなりました。ローディングも減りました。

まとめ

アプリの動作が変だと思ったら、パフォーマンスの測定をしてみると良いかもしれません。どこに問題があるのかは、技術がないと見分けることは出来ないかもしれませんが💦
setStateを実行している場所が怪しかったりしますけどね。

Discussion