Firebase Performance Monitoring で読み込み時間と画面のレンダリングを測定する

1. はじめに

最終更新日: 2021 年 3 月 11 日

ビューのパフォーマンスを測定する必要があるのはなぜですか?

ビューは Android アプリの重要な要素であり、ユーザー エクスペリエンスに直接影響します。たとえば、アクティビティやフラグメントに、ユーザーが操作する View コンポーネントを保持する UI が含まれているとします。UI が画面に完全に描画されるまで、ユーザーは UI のコンテンツ全体を見ることができません。画面の速度が遅い場合や画面がフリーズすると、アプリのユーザー操作に直接支障をきたし、ユーザー エクスペリエンスが低下します。

Firebase Performance Monitoring では、このようなパフォーマンス指標が最初から用意されているわけではありませんか?

Firebase Performance Monitoring は、アプリの起動時間(最初のアクティビティのみの読み込み時間)や画面レンダリング パフォーマンス(アクティビティでは低速なフレームやフリーズしたフレームなど、フラグメントでは低速なフレームやフリーズしたフレームなど)など、一部のパフォーマンス データをすぐに自動的にキャプチャします。ただし、業界アプリは通常、アクティビティは多くなく、1 つの Activity と複数の Fragment を持ちます。また、多くのアプリでは、複雑なユースケース向けに独自のカスタムビューが実装されています。そのため、アプリでカスタムコード トレースを計測して、アクティビティとフラグメントの両方の読み込み時間と画面レンダリングのパフォーマンスを測定する方法を理解することは有用です。この Codelab を拡張することで、カスタムビュー コンポーネントのパフォーマンスを簡単に測定できます。

ラボの内容

  • Firebase Performance Monitoring を Android アプリに追加する方法
  • アクティビティまたはフラグメントの読み込みについて
  • カスタムコード トレースを計測してアクティビティやフラグメントの読み込み時間を測定する方法
  • 画面レンダリングと遅い/フリーズしたフレームについて
  • 遅い画面やフリーズした画面を記録する指標を使用してカスタムコード トレースを計測する方法
  • 収集された指標を Firebase コンソールで表示する方法

必要なもの

  • Android Studio 4.0 以降
  • Android デバイス/エミュレータ
  • Java バージョン 8 以降

2. 設定方法

コードを取得する

次のコマンドを実行して、この Codelab のサンプルコードのクローンを作成します。これにより、マシンに codelab-measure-android-view-performance というフォルダが作成されます。

$ git clone https://github.com/FirebaseExtended/codelab-measure-android-view-performance.git
$ cd codelab-measure-android-view-performance

お使いのマシンに git がない場合は、GitHub からコードを直接ダウンロードすることもできます。

measure-view-performance-start プロジェクトを Android Studio にインポートします。多くの場合、コンパイル エラーが表示されるか、google-services.json ファイルがないという警告が表示されます。このステップの次のセクションでこれを修正します。

この Codelab では、Firebase Assistant プラグインを使用して Android アプリを Firebase プロジェクトに登録し、必要な Firebase 構成ファイル、プラグイン、依存関係を Android プロジェクトに追加します。これらはすべて、Android Studio 内から行えます。

アプリを Firebase に接続する

  1. [Android Studio] / [Help] > [Check for updates] に移動して、Android Studio と Firebase Assistant の最新バージョンを使用していることを確認します。
  2. [Tools] > [Firebase] を選択して、[Assistant] ペインを開きます。

e791bed0999db1e0.png

  1. [Performance Monitoring] を選択してアプリに追加し、[Performance Monitoring を使ってみる] をクリックします。
  2. [Firebase に接続] をクリックして、Android プロジェクトを Firebase に接続します(これにより、ブラウザに Firebase コンソールが開きます)
  3. Firebase コンソールで [プロジェクトを追加] をクリックし、Firebase プロジェクト名を入力します(すでに Firebase プロジェクトがある場合は、代わりにその既存のプロジェクトを選択できます)。[続行] をクリックして利用規約に同意し、Firebase プロジェクトと新しい Firebase アプリを作成します。
  4. 次に、新しい Firebase アプリを Android Studio プロジェクトに接続するためのダイアログが表示されます。

42c498d28ead2b77.png

  1. Android Studio に戻ると、[Assistant] ペインに、アプリが Firebase に接続したことの確認が表示されます。

dda8bdd9488167a0.png

Performance Monitoring をアプリに追加する

Android Studio の [Assistant] ペインで、[Add Performance Monitoring to your app] をクリックします。

[Accept Changes] ダイアログが表示されます。この後、必要な依存関係がすべて追加されたことを確認するために、Android Studio がアプリを同期します。

9b58145acc4be030.png

最後に、Android Studio の [Assistant] ペインに、すべての依存関係が正しく設定されていることを示す成功メッセージが表示されます。

aa0d46fc944e0c0b.png

追加の手順として、「(省略可)デバッグ ロギングを有効にする」の手順に沿ってデバッグ ロギングを有効にします。同じ手順が一般公開ドキュメントにも記載されています。

3. アプリを実行する

アプリと Performance Monitoring SDK が正常に統合されていれば、プロジェクトはコンパイルされます。Android Studio で [Run] > [Run 'app'] をクリックし、接続された Android デバイスまたはエミュレータでアプリをビルドして実行します。

このアプリには、対応する Activity と Fragment に移動できる下記の 2 つのボタンがあります。

410d8686b4f45c33.png

この Codelab の以降のステップでは、アクティビティまたはフラグメントの読み込み時間と画面レンダリングのパフォーマンスを測定する方法を学びます。

4. アクティビティまたはフラグメントの読み込みについて

このステップでは、アクティビティまたはフラグメントの読み込み中にシステムが何を行っているかを確認します。

アクティビティの読み込みについて

Activity の場合、読み込み時間は、Activity オブジェクトが作成されてから、最初のフレームが画面に完全に描画されるまでの時間として定義されます(ユーザーにアクティビティの完全な UI が初めて表示されるとき)。アプリが完全に描画されたかどうかを測定するには、reportFullyDrawn() メソッドを使用して、アプリを起動してからすべてのリソースとビュー階層が完全に表示されるまでの経過時間を測定します。

大まかに言うと、アプリが startActivity(Intent) を呼び出すと、システムにより以下のプロセスが自動的に実行されます。各プロセスの完了には時間がかかります。そのため、アクティビティが作成されてから、ユーザーの画面にアクティビティの UI が表示されるまでの時間が長くなります。

c20d14b151549937.png

Fragment の読み込みについて

Activity と同様に、Fragment の読み込み時間は、Fragment がホスト Activity にアタッチされてから、Fragment View の First Frame が完全に画面に描画されるまでの時間として定義されます。

5. アクティビティの読み込み時間を測定する

最初のフレームで遅延が発生すると、ユーザー エクスペリエンスが低下する可能性があります。そのため、初期読み込みの遅延がユーザーにどの程度発生しているかを把握することが重要です。この読み込み時間を測定するには、カスタム コード トレースを使用します。

  1. Activity オブジェクトが作成されるとすぐに、Activity クラスでカスタム コード トレース(TestActivity-LoadTime)を開始します。

TestActivity.java

public class TestActivity extends AppCompatActivity {   
    // TODO (1): Start trace recording as soon as the Activity object is created.
    private final Trace viewLoadTrace = FirebasePerformance.startTrace("TestActivity-LoadTime");

    // ...

}
  1. onCreate() コールバックをオーバーライドし、setContentView() メソッドで追加された View を取得します。
@Override     
public void onCreate(Bundle savedInstanceState) {    
    super.onCreate(savedInstanceState);          

    // Current Activity's main View (as defined in the layout xml file) is inflated after this            
    setContentView(R.layout.activity_test);          

    // ...

    // TODO (2): Get the View added by Activity's setContentView() method.         
    View mainView = findViewById(android.R.id.content);     

    // ...
}
  1. onDrawingStart()onDrawingFinish() の 2 つのコールバックを持つ FistDrawListener の実装が含まれています(FirstDrawListener とそのパフォーマンスに影響する要素について詳しくは、以下のセクションをご覧ください)。アクティビティの onCreate() コールバックの最後に FirstDrawListener を登録します。onDrawingFinish() コールバックで viewLoadTrace を停止する必要があります。

TestActivity.java

    // TODO (3): Register the callback to listen for first frame rendering (see
    //  "OnFirstDrawCallback" in FirstDrawListener) and stop the trace when View drawing is
    //  finished.
    FirstDrawListener.registerFirstDrawListener(mainView, new FirstDrawListener.OnFirstDrawCallback() {              
        @Override             
        public void onDrawingStart() {       
          // In practice you can also record this event separately
        }

        @Override             
        public void onDrawingFinish() {
            // This is when the Activity UI is completely drawn on the screen
            viewLoadTrace.stop();             
        }         
    });
  1. アプリを再実行し、logcat を [Logging trace metric] でフィルタします。LOAD ACTIVITY ボタンをタップして、次のようなログを探します。
I/FirebasePerformance: Logging trace metric: TestActivity-LoadTime (duration: XXXms)

🎉? おめでとうございます!アクティビティの読み込み時間を測定し、そのデータを Firebase Performance Monitoring に報告することができました。記録された指標は、この Codelab の後半で Firebase コンソールで確認します。

FirstDrawListener の目的

先ほどのセクションで FirstDrawListener を登録しました。FirstDrawListener の目的は、最初のフレームの描画の開始と終了のタイミングを測定することです。

これは ViewTreeObserver.OnDrawListener を実装し、ビューツリーが描画される際に呼び出される onDraw() コールバックをオーバーライドします。次に、結果をラップして、2 つのユーティリティ コールバック onDrawingStart()onDrawingFinish() を提供します。

FirstDrawListener の完全なコードは、この Codelab のソースコードにあります。

6. Fragment の読み込み時間を測定する

Fragment の読み込み時間の測定は、Activity の測定方法と似ていますが、若干の違いがあります。ここでも、カスタム コード トレースをインストルメント化します。

  1. onAttach() コールバックをオーバーライドして、fragmentLoadTrace の記録を開始します。このトレースに Test-Fragment-LoadTime という名前を付けます。

前のステップで説明したように、Fragment オブジェクトはいつでも作成できますが、ホスト アクティビティにアタッチされている場合にのみアクティブになります。

TestFragment.java

public class TestFragment extends Fragment {

   // TODO (1): Declare the Trace variable.
   private Trace fragmentLoadTrace;

   @Override
   public void onAttach(@NonNull Context context) {
       super.onAttach(context);

       // TODO (2): Start trace recording as soon as the Fragment is attached to its host Activity.
       fragmentLoadTrace = FirebasePerformance.startTrace("TestFragment-LoadTime");
   }
  1. onViewCreated() コールバックで FirstDrawListener を登録します。次に、Activity の例と同様に、onDrawingFinish() でトレースを停止します。

TestFragment.java

@Override
public void onViewCreated(@NonNull View mainView, Bundle savedInstanceState) {
   super.onViewCreated(mainView, savedInstanceState);

   // ...

   // TODO (3): Register the callback to listen for first frame rendering (see
   //  "OnFirstDrawCallback" in FirstDrawListener) and stop the trace when view drawing is
   //  finished.
   FirstDrawListener.registerFirstDrawListener(mainView, new FirstDrawListener.OnFirstDrawCallback() {

       @Override
       public void onDrawingStart() {
           // In practice you can also record this event separately
       }

       @Override
       public void onDrawingFinish() {
           // This is when the Fragment UI is completely drawn on the screen
           fragmentLoadTrace.stop();
       }
   });
  1. アプリを再実行し、logcat を [Logging trace metric] でフィルタします。LOAD FRAGMENT ボタンをタップして、次のようなログを探します。
I/FirebasePerformance: Logging trace metric: TestFragment-LoadTime (duration: XXXms)

🎉? おめでとうございます!Fragment の読み込み時間を測定し、そのデータを Firebase Performance Monitoring に報告することができました。記録された指標は、この Codelab の後半で Firebase コンソールで確認します。

7. 画面レンダリングと遅い/フリーズしたフレームについて

UI レンダリングとは、アプリからフレームを生成して画面に表示することです。ユーザーのアプリ操作をスムーズにするためには、16 ミリ秒未満でフレームをレンダリングし、1 秒あたり 60 フレームを達成する必要があります(なぜ 60 fps が必要なのか)。アプリの UI レンダリングが遅い場合、システムはフレームをスキップせざるを得ず、ユーザーはアプリの動作がスムーズに動作しないと感じます。これをジャンクと呼びます。

同様に、フリーズしたフレームとは、レンダリングに 700 ミリ秒以上かかる UI フレームです。フレームのレンダリング中にアプリが停止しているように見え、ユーザー入力にほぼ 1 秒間応答しないため、この遅延は問題です。

8. Fragment の遅いフレーム/フリーズしたフレームを測定する

Firebase Performance Monitoring は、アクティビティの遅いフレームやフリーズしたフレームを自動的にキャプチャします(ただし、Hardware Accelerated の場合に限ります。ただし、この機能は現在、Fragment では使用できません。Fragment の遅いフレーム/フリーズしたフレームは、Fragment のライフサイクルの onFragmentAttached() コールバックと onFragmentDetached() コールバックの間のアクティビティ全体の遅いフレーム/フリーズしたフレームとして定義されます。

AppStateMonitor クラス(Activity の画面トレースの記録を担当する Performance Monitoring SDK の一部)に基づき、ScreenTrace クラス(この Codelab ソースコード リポジトリの一部)を実装しました。ScreenTrace クラスをアクティビティの FragmentManager のライフサイクル コールバックに接続して、遅いフレームやフリーズしたフレームをキャプチャできます。このクラスには、次の 2 つの公開 API が用意されています。

  • recordScreenTrace(): 画面トレースの記録を開始します。
  • sendScreenTrace(): 画面トレースの記録を停止し、カスタム指標を接続して合計フレーム数、低速フレーム数、フリーズしたフレーム数をログに記録します。

これらのカスタム指標を追加すると、Fragment の画面トレースをアクティビティの画面トレースと同じように処理し、Firebase コンソールの [Performance] ダッシュボードに他の画面レンダリング トレースとともに表示できます。

Fragment の画面トレースをログに記録する方法は次のとおりです。

  1. Fragment をホストするアクティビティ内で ScreenTrace クラスを初期化します。

MainActivity.java

// Declare the Fragment tag
private static final String FRAGMENT_TAG = TestFragment.class.getSimpleName();

// TODO (1): Declare the ScreenTrace variable.
private ScreenTrace screenTrace;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // TODO (2): Initialize the ScreenTrace variable.
    screenTrace = new ScreenTrace(this, FRAGMENT_TAG);

    // ...
}
  1. Fragment を読み込むときに、FragmentLifecycleCallbacks を登録し、onFragmentAttached() コールバックと onFragmentDetached() コールバックをオーバーライドします。これは Google がお客様に代わって行いました。onFragmentAttached() コールバックで画面トレースの記録を開始し、onFragmentDetached() コールバックで記録を停止する必要があります。

MainActivity.java

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
       new FragmentManager.FragmentLifecycleCallbacks() {

           @Override
           public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
               super.onFragmentAttached(fm, f, context);

               // TODO (3): Start recording the screen traces as soon as the Fragment is
               //  attached to its host Activity.
               if (FRAGMENT_TAG.equals(f.getTag()) && screenTrace.isScreenTraceSupported()) {
                   screenTrace.recordScreenTrace();
               }
           }

           @Override
           public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {
               super.onFragmentDetached(fm, f);

               // TODO (4): Stop recording the screen traces as soon as the Fragment is
               //  detached from its host Activity.
               if (FRAGMENT_TAG.equals(f.getTag()) && screenTrace.isScreenTraceSupported()) {
                   screenTrace.sendScreenTrace();
               }

               // Unregister Fragment lifecycle callbacks after the Fragment is detached
               fm.unregisterFragmentLifecycleCallbacks(fragmentLifecycleCallbacks);
           }
       };
  1. アプリを再実行します。次に、[LOAD FRAGMENT] ボタンをタップします。数秒待ってから、下部のナビゲーション バーにある [back button] をクリックします。

logcat を [Logging trace metric] でフィルタし、次のようなログを探します。

I/FirebasePerformance: Logging trace metric: _st_MainActivity-TestFragment (duration: XXXms)

logcat を「FireperfViews」でフィルタし、次のようなログを探します。

D/FireperfViews: sendScreenTrace MainActivity-TestFragment, name: _st_MainActivity-TestFragment, total_frames: XX, slow_frames: XX, frozen_frames: XX

🎉? おめでとうございます!Fragment で遅いフレーム/フリーズしたフレームを測定し、そのデータを Firebase Performance Monitoring に報告することができました。記録された指標は、この Codelab の後半で Firebase コンソールで確認できます。

9. Firebase コンソールで指標を確認する

  1. Logcat で Firebase コンソールの URL をクリックして、トレースの詳細ページに移動します。ceb9d5ba51bb6e89.jpeg

または、Firebase コンソールでアプリが含まれているプロジェクトを選択し、左側のパネルで [リリースとモニタリング] セクションを見つけ、[パフォーマンス] をクリックします。

  • メインの [ダッシュボード] タブでトレース テーブルまで下にスクロールし、[カスタム トレース] タブをクリックします。この表には、先ほど追加したカスタム コード トレースと、_app_start トレースなどのすぐに使えるトレースが表示されます。
  • 2 つのカスタム コード トレース TestActivity-LoadTimeTestFragment-LoadTime を見つけます。いずれかの [Duration] をクリックすると、収集されたデータの詳細が表示されます。

a0d8455c5269a590.png

  1. カスタムコード トレースの詳細ページには、トレースの継続時間に関する情報(測定された読み込み時間)が表示されます。

5e92a307b7410d8b.png

  1. カスタム画面トレースのパフォーマンス データを表示することもできます。
  • メインの [ダッシュボード] タブに戻り、トレース テーブルまで下にスクロールして、[画面のレンダリング] タブをクリックします。この表には、前に追加したカスタム画面トレースと、MainActivity トレースなどのすぐに使用できる画面トレースが表示されます。
  • カスタム画面トレース MainActivity-TestFragment を見つけます。トレース名をクリックすると、レンダリングが遅いフレームやフリーズしたフレームの集計データが表示されます。

ee7890c7e2c28740.png

10. 完了

お疲れさまでした。Firebase Performance Monitoring を使用して、アクティビティとフラグメントの読み込み時間と画面レンダリングのパフォーマンスを測定することができました。

これまでに達成したこと

次のステップ

Firebase Performance では、カスタム トレース以外にも、さまざまな方法でアプリのパフォーマンスを測定できます。アプリの起動時間、フォアグラウンドのアプリ、バックグラウンドのアプリのパフォーマンス データを自動的に測定します。これらの指標を Firebase コンソールで確認してみましょう。

また、Firebase Performance は、HTTP/S ネットワーク リクエストの自動モニタリングも提供しています。これにより、コードを 1 行も記述せずにネットワーク リクエストを簡単に計測できます。アプリからネットワーク リクエストを送信して、Firebase コンソールで指標をご確認いただけますか?

参考

カスタムコード トレースを使用してアクティビティやフラグメントの読み込み時間と画面レンダリングのパフォーマンスを測定する方法がわかったところで、Google のオープンソース コードベースを参照して、アプリの一部であるアクティビティ / フラグメントについて、すぐに指標を取得できるかどうか確認してみましょう。ご希望の場合は PR までお願いします。

11. ボーナス学習

アクティビティの読み込み中に何が起こっているかを理解すると、アプリのパフォーマンス特性をより深く理解できます。前のステップでは、アクティビティの読み込み中に何が起こるかを大まかに説明しましたが、次の図では各フェーズを詳細に説明します。

cd61c1495fad7961.png