UI ジャンクの検出

Android は、アプリからフレームを生成して画面に表示することで、UI をレンダリングします。アプリの UI レンダリングが遅いと、システムはフレームをスキップせざるを得なくなり、ユーザーは画面のちらつきを感じます。これを「ジャンク」といいます。

ジャンクが発生する場合、通常は、UI スレッド(ほとんどのアプリではメインスレッド)でなんらかの速度低下やブロッキング非同期呼び出しが発生したことが原因です。システム トレースを使用すると、問題のある場所を特定できます。

Android 12 以降でジャンクを検出する

Android 12(API レベル 31)以降を搭載したデバイスの場合、キャプチャされたトレースは CPU Profiler の [Display] ペインにある [Janky frames] トラックに表示されます。

ジャンクを検出する手順は次のとおりです。

  1. Android Studio で、[View] > [Tool Windows] > [Profiler] を選択するか、ツールバーのプロファイル アイコン をクリックします。

    [Select Deployment Target] ダイアログが表示されたら、プロファイリング用にアプリをデプロイするデバイスを選択します。USB 経由でデバイスを接続しているにもかかわらずデバイスがリストに表示されない場合は、USB デバッグが有効になっているかを確認します。

  2. CPU タイムラインの任意の場所をクリックして CPU Profiler を開きます。

  3. CPU Profiler の構成メニューで [System Trace] を選択し、[Record] をクリックします。アプリの操作が完了したら、[Stop] をクリックします。

  4. [Display] の下に [Janky frames] トラックが表示されます。デフォルトでは、Profiler は調査対象の候補としてジャンクのあるフレームのみを表示します。ジャンクのあるフレーム内で赤くハイライト表示された部分は、レンダリング期限からの超過時間を表しています。 [Janky frames] トラックのスクリーンショット

  5. ジャンクのあるフレームが表示されたら、それをクリックします。M キーを押すと、選択したフレームを中心に表示倍率を調整できます。関連するイベントが、メインスレッド、[RenderThread]、[GPU completion] スレッドでハイライト表示されます。 [Janky frames] とメインスレッドが表示された Profiler のスクリーンショット

  6. [All Frames]、[Lifecycle] のチェックボックスをオンにすることで、それぞれ、すべてのフレーム、レンダリング時間の内訳を表示できます。 上と同じ Profiler 画面で [All Frames] と [Lifecycle] のチェックボックスをオンにした場合のスクリーンショット

Android 11 でジャンクを検出する

Android 11(API レベル 30)搭載デバイスの場合、キャプチャされたトレースは CPU Profiler の [Frame Lifecycle] セクションに表示されます。

さまざまなトラックが含まれる [Frame Lifecycle] セクション

[Frame Lifecycle] セクションには、レイヤ名と 4 つのトラックが表示されます。各トラックは、フレーム レンダリング パイプラインの 1 つのステージを表しています。Frame Lifecycle の要素は次のとおりです。

  1. Frame Lifecycle(レイヤ名): セクション タイトルとして、かっこ書きでレイヤ名が表示されます。レイヤは、単一の合成単位です。
  2. Application: このトラックは、アプリによってバッファがキューから除外されてから、再度キューに追加されるまでの時間を示します。これは通常、RenderThread のトレース イベントに対応します。
  3. Wait for GPU: このトラックは、GPU がバッファを保有していた期間を示します。これは、バッファが GPU に送信されてから、GPU がバッファに対する処理を終了するまでの時間です。GPU がこの期間にこのバッファのみを処理していたということではありません。特定の期間に GPU が処理していた内容の詳細な情報については、Android GPU Inspector を使用することをおすすめします。
  4. Composition: このトラックは、SurfaceFlinger がバッファをラッチして合成のために送信してから、バッファがディスプレイに送信されるまでの時間を示します。
  5. Frames on display: このトラックは、フレームが画面に表示されていた期間を示します。

[Frame Lifecycle] セクションは、フレーム バッファがレンダリング パイプラインのさまざまなステージ間をどのように移動するかを示します。フレームはフレーム番号ごとに色分けされているため、特定のフレームを簡単に追跡できます。

Android Studio では、トレース内のすべてのフレームを [All Frames] タブに表形式で表示することもできます。

[All Frames] タブにおけるトレース内の全フレームの表

[Frame #]、[Application]、[Wait for GPU]、[Composition] の各列には、上記の [Frame Lifecycle] セクション内のトラックと同じデータが表示されます。[Frame Duration] 列は、[Application] の開始から [Frames on Display] の開始までの時間を示します。これは基本的に、1 つのフレームをエンドツーエンドでレンダリングするためにかかる時間です。

フレームの表を任意の列で並べ替えると、最短または最長のフレームをすばやく見つけることができます。この表には、何百ものフレーム間を移動するのに役立つページネーション コントロールもあります。

Android 11 でジャンクを検出して調査する手順は次のとおりです。

  1. [Application] 列を基準にして [All Frames] 表を降順に並べ替えて、最も時間がかかるフレームから表示されるようにします。

    降順に並べ替えられた [Application] 列

  2. 最も時間がかかっているフレームを見つけて、表の行を選択します。これにより、左側のタイムライン ビューで、選択したフレームにズームインされます。

    タイムライン ビューとフレームの表

  3. [Frame Lifecycle] セクションと [Threads] セクションで、関連するスレッドを探します。

    [Frame Lifecycle] セクションと [Threads] セクション

Android 10 以前でジャンクを検出する

Android 10(API レベル 29)以前を搭載したデバイスの場合、関連する OS グラフィックス パイプライン情報が、CPU Profiler のシステム トレースで [Display] という単一のセクションに表示されます。

ディスプレイ UI ウィンドウ

  • Frames: このセクションは、アプリの UI スレッドと RenderThread トレース イベントを示します。16 ミリ秒より長いイベントは赤色で示され、60 フレーム/秒(FPS)でレンダリングするための期限を超えることから、ジャンクが発生した可能性としてハイライト表示されます。
  • SurfaceFlinger: このセクションは、SurfaceFlinger がフレーム バッファを処理するタイミングを示します。SurfaceFlinger は、表示するバッファの送信を行うシステム プロセスです。
  • VSYNC: このセクションは、ディスプレイ パイプラインを同期する信号である VSYNC を示します。トラックには VSYNC-app シグナルが表示されます。これはアプリの起動が遅すぎることを示します。通常は、UI スレッドがビジー状態であることが原因です。これにより、アニメーション中に画面にちらつきが発生し、アニメーションまたはスクロールが完了するまでの入力レイテンシが増えます。1 秒あたり 60 回を超える頻度で、または変動レートで発生する可能性があるため、リフレッシュ レートの高いディスプレイでの表示には VSYNC が特に重要です。
  • BufferQueue: このセクションは、キューに入れられ、SurfaceFlinger が使用するまで待機しているフレーム バッファの数を示します。Android 9(API レベル 28)以降を搭載したデバイスにデプロイされたアプリの場合、このトラックはアプリのサーフェス BufferQueue のバッファ数を示します(01、または 2)。BufferQueue は、Android グラフィックス コンポーネント間を移動する際の画像バッファの状態を把握するために役立ちます。たとえば値が 2 の場合は、アプリがトリプル バッファされていることを意味します。その結果、入力レイテンシが長くなります。

[Display] セクションでは、UI スレッドや RenderThread に 16 ミリ秒以上かかった場合など、起こり得るジャンクを検出するために役立つシグナルを確認できます。ジャンクの原因の詳細を正確に調査するには、UI レンダリングに関連するスレッドが表示される [Threads] セクションを調べます。

[Display] の [Threads] セクション

上の図では、[Threads] セクションに UI スレッド(java.com.google.samples.apps.iosched)、RenderThreadGPU completion スレッドが表示されています。これらは UI レンダリングに関連するスレッドであり、ジャンクを引き起こす可能性があります。

Android 10 以前のデバイスでジャンクを検出する手順は次のとおりです。

  1. [Display] の [Frames] トラックを確認します。赤いフレームは調査候補です。

    [Display] の [Frames] セクション

  2. ジャンクのある可能性のあるフレームが見つかったら、Ctrl キー(macOS では Command キー)を押しながら、W キーを押すかマウスホイールをスクロールしてズームインします。UI スレッドと RenderThread のトレース イベントを確認できるようになるまでズームインし続けます。

    UI スレッドと RenderThread のトレース イベント

    上の図の Choreographer#doFrame は、UI スレッドが Choreographer を呼び出して、アニメーション、ビュー レイアウト、画像描画、関連プロセスを調整していたタイミングを示しています。DrawFrames は、RenderThread が形成され、実際の描画コマンドが GPU に発行されたタイミングを示しています。

  3. 特に長いトレース イベントがあれば、さらにズームインして、レンダリングが遅くなった原因を確認できます。上の図は、UI スレッドの inflate を示しています。これは、アプリがレイアウトのインフレートに時間を費やしていることを意味します。inflate イベントのいずれかにズームインすると、下の図に示すように、各 UI コンポーネントにかかっている時間を正確に確認できます。

    UI コンポーネントの正確な継続時間を示すメニュー

詳細

ジャンクを減らす方法については、一般的なジャンク発生源をご覧ください。