この記事は Takeshi Hagikura による Android Developers - Medium の記事 " Spot your UI jank using CPU profiler in Android Studio " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android はアプリからフレームを生成し、それを画面に表示することによってユーザー インターフェース(UI)をレンダリングしています。アプリでスムーズなユーザー インタラクションを実現するには、各フレームがリフレッシュ レート以下でレンダリングされなければなりません。リフレッシュ レートはデバイスによって決まり、たとえば Pixel 6 は最大 90 フレーム / 秒をレンダリングできます。その場合、1 フレームを 11 ミリ秒以下でレンダリングする必要があります。アプリの UI レンダリングが遅いと、Android フレームワークはフレームをスキップせざるを得ません。これが起こると、ユーザーは視覚的な違和感を感じることになります。この現象は、ジャンクとも呼ばれます。
ジャンクの原因は、アプリや SurfaceFlinger (英語) など、さまざまです。この記事では、アプリに起因するジャンクと、それを見つけて修正するための Android Studio のツールに注目します。こういったツールを利用すると、リアルタイムにアプリを調査したり、記録したトレースを開いたりすることによって、アプリのパフォーマンスの問題を解決できます。
注 : この記事では、Android Studio Chipmunk 以降のアップデートされたジャンク検出 UI と、Android 12(API レベル 31)以降を実行する物理デバイスまたは Android エミュレータを使います。
以下では、GitHub のパフォーマンス サンプル リポジトリに含まれている JankStatsSample アプリを例に、CPU profiler を使ってジャンクの原因を見つける方法を説明します。
JankStatsSample
1. JankStatsSample を開き、アプリを実行します。
2. Android Studio 下部の Profiler タブを開きます。
Profiler
3. プロファイラ左側の + アイコンをクリックして新しいプロファイリング セッションを開始します。次に、プロファイラを実行するデバイス名とアプリのプロセスを選択します。
注 : デバッグ可能なアプリをプロファイルすることもできますが、プロファイル可能なアプリをプロファイルすることをお勧めします。デバッグ可能なアプリをプロファイルする場合、副作用としてパフォーマンスの負荷が非常に大きくなります。詳細については、 ドキュメント でプロファイル可能なアプリについてご確認ください。
4. CPU 列をクリックします。
CPU
5. System Trace Recording を選択して Record をクリックします。
System Trace Recording
Record
6. アプリを操作してデータを収集し、Stop ボタンをクリックします。
Stop
そうすると、Android Studio にジャンクが発生したフレームを含む Display セクションが表示されます。
Display
All Frames チェックボックスをオンにすると、ジャンクが発生していないフレームも表示するかどうかを切り替えることができます。
All Frames
マウスカーソルをフレームに合わせるか、フレームをクリックすると、詳細なフレーム情報を確認できます。All Frames チェックボックスをオンにすると、3 種類のフレームが表示されます。
Lifecycle チェックボックスをオンにすると、4 つの追加トラックの表示と非表示を切り替えることができます。
Lifecycle
次の 4 つのトラックがあります。
Application
Wait for GPU
Composition
Frames on display
これらは、Android Studio Bumblebee 以降で利用できます。各トラックの詳しい説明は、ドキュメントをご覧ください。
では、何がフレームのジャンクを起こしているのかを診断する方法を見てみましょう。
1. Janky frames トラックでジャンクが起きているフレームを選択します。すると、Display セクションで対応するライフサイクル データが、Threads セクションで対応するスレッドデータがそれぞれハイライト表示されます。
Janky frames
Threads
点線の Deadline は期限を示します。フレームの処理にかかる時間がこの期限を過ぎると、フレームでジャンクが発生したと見なされます。
Deadline
Android Studio の右ペインには、詳しい分析情報も表示されます。
2. アプリのメインスレッドの対応するトレースのセクションを見ると、「View#draw」に長い時間がかかっていることがわかります。
また、詳しい分析情報のペインでメインスレッドの状態を見てみると、スレッドの大半の時間がスリープ状態であることがわかります。
3. コードで View#draw を呼び出している場所を確認してみましょう。
View#draw
JankyView クラスで View#onDraw がオーバーライドされていることがわかります。
View#onDraw
override fun onDraw(canvas: Canvas) { simulateJank() super.onDraw(canvas)}
override fun onDraw(canvas: Canvas) {
simulateJank()
super.onDraw(canvas)
}
onDraw で呼ばれている simulateJank メソッドは、次のように定義されています。
onDraw
simulateJank
fun simulateJank( jankProbability: Double = 0.3, extremeJankProbability: Double = 0.02) { val probability = nextFloat() if (probability > 1 - jankProbability) { val delay = if (probability > 1 - extremeJankProbability) { nextLong(500, 700) } else { nextLong(32, 82) } try { // Make jank easier to spot in the profiler through tracing. trace("Jank Simulation") { Thread.sleep(delay) } } catch (e: Exception) { } }}
fun simulateJank(
jankProbability: Double = 0.3,
extremeJankProbability: Double = 0.02
) {
val probability = nextFloat()
if (probability > 1 - jankProbability) {
val delay = if (probability > 1 - extremeJankProbability) {
nextLong(500, 700)
} else {
nextLong(32, 82)
try {
// Make jank easier to spot in the profiler through tracing.
trace("Jank Simulation") {
Thread.sleep(delay)
} catch (e: Exception) {
ここで、simulateJank メソッドの中で Thread.sleep が呼ばれていることがわかります。これがわかりやすいのは、JankStatsSample アプリが意図的にジャンクをシミュレーションするように作られているからです。しかし重要なのは、ジャンクが起きているフレームの概要から詳しい分析情報までを確認でき、そこから実際のコードを見つけられることです。
Thread.sleep
注 : この例では Thread.sleep を呼び出していることが問題なのは明らかですが、実際にアプリのコードを最適化する際には、もっと難しい決断を迫られることになるでしょう。Microbenchmark ライブラリ (英語) は、行う変更が意図した効果を発揮するかどうかを測定する際に役立ちます。
注 : システム トレースには、アプリを構成するプラットフォームのコードやライブラリによってキャプチャされたさまざまなセクションが表示されます。多くの場合、ここに表示される情報は十分ではありません。この点を改善するには、カスタム トレースを追加します。カスタム トレースを追加する方法の 1 つは、 trace(“MySection”) { /* トレースに含める内容 */ } を追加することです。これは AndroidX Tracing (英語) ライブラリに含まれる命令です。
trace(“MySection”) { /* トレースに含める内容 */ }
たとえば、この例で使われている trace(“Jank Simulation”) { … } は、対応するスレッドのトレース セクションに表示されます。
trace(“Jank Simulation”) { … }
トレースの読み方とカスタム トレースを追加する方法の詳細は、 システム トレースの概要をご覧ください。
4. コードを変更し、onDraw で simulateJank メソッドを呼ばないようにしてみましょう。その後、まだフレームのジャンクが起きるかどうかを確認してみます。
override fun onDraw(canvas: Canvas) { // simulateJank() super.onDraw(canvas)}
// simulateJank()
今回は、システム トレースの記録を実行し直してアプリを操作しても、フレームのジャンクは確認できません。
トレースを保存して後から読み込むこともできます。これを行うには、次の手順に従います。トレースの保存と読み込みは、別のバージョンのトレースと比較したり、トレースを別の人と共有したりする際に便利です。
注 : システム トレースは、Macrobenchmark ライブラリ (英語) を使用して取得することもできます。
1. リアルタイム インタラクションでトレースを記録する方法については、手順 1 から 6 と同じ手順に従います。
2. 保存アイコンをクリックして記録をエクスポートします。
3. 後ほど、+ -> Load from file… を開き、前の手順でエクスポートしたファイルを選択して、保存したシステム トレース記録を読み込みます。
+
Load from file…
その後、保存したトレースを読み込み、リアルタイム インタラクションの記録と同じ方法でジャンクが起きているフレームを確認できます。
最後になりましたが、Android Studio Chipmunk 以降ではさらに精密なプロファイリング データを確認できるので、アプリのジャンクが一層見つけやすくなります。
CPU profiler の詳しい使い方は、ドキュメントに記載されています。Android Studio で Help -> Submit Feedback を開き、ツールの改善にご協力ください。
また、Google I/O のアプリ パフォーマンスの新機能セッション (動画/英語) では、フレームのジャンクを回避する方法をはじめ、パフォーマンスに関連するさまざまなトピックを取り上げています。
さらに、ユーザーのデバイスで実行されている本番アプリでジャンクを検出して報告する新しいライブラリもあります。詳しく確認してみたい方は、JankStats のドキュメントをご覧ください。
Reviewed by Mari Kawanishi - Developer Marketing Manager, Google Play