方法

パフォーマンスに関するその他の考慮事項

所要時間: 8 分

パフォーマンスに関する背景についてご説明します。

パフォーマンス スポットライト ウィークの 3 日目です。今日も、アプリのパフォーマンスの重要な領域に関する詳細とガイダンスをお届けします。今回は、プロファイルに基づく最適化、Jetpack Compose のパフォーマンスの改善、舞台裏での処理に関する考慮事項について説明します。詳しく見ていきましょう。

プロファイルに基づく最適化

ベースライン プロファイル起動プロファイルは、Android アプリの起動時と実行時のパフォーマンスを改善するための基盤となります。これらは、プロファイルに基づく最適化と呼ばれるパフォーマンス最適化のグループの一部です。

アプリがパッケージ化されると、d8 dexer はクラスとメソッドを取得し、アプリの classes.dex ファイルにデータを入力します。ユーザーがアプリを開くと、これらの dex ファイルがアプリを起動できるまで順に読み込まれます。起動プロファイル を指定すると、最初の classes.dex ファイルにどのクラスとメソッドをパックするかを d8 に伝えることができます。この構造により、アプリが読み込むファイルの数が減り、起動速度が向上します。

ベースライン プロファイルを使用すると、ジャストインタイム(JIT)コンパイルの手順をユーザー デバイスからデベロッパー マシンに効果的に移行できます。生成された Ahead Of Time(AOT)コンパイル済みコードは、起動時間とレンダリングの問題の両方を軽減することが証明されています。

Trello とベースライン プロファイル

Trello アプリのエンジニアに、ベースライン プロファイルがアプリのパフォーマンスにどのような影響を与えたか尋ねました。メインのユーザー ジャーニーにベースライン プロファイルを適用したところ、Trello ではアプリの起動時間が 25 % 大幅に短縮されました。

image.png

Trello は、ベースライン プロファイルを使用することで、アプリの起動時間を 25 % 改善できました。

Meta におけるベースライン プロファイル

また、Meta のエンジニアは最近、ベースライン プロファイルを使用して Android アプリを高速化する方法に関する記事を公開しました

image.png

Meta のアプリ全体で、ベースライン プロファイルを適用した後、さまざまな重要な指標が最大 40 % 改善されました。

このような技術的な改善は、ユーザー満足度とビジネスの成功にもつながります。この情報をプロダクト オーナー、CTO、意思決定者と共有することで、アプリのパフォーマンスをさらに向上させることができます。

ベースライン プロファイルを使ってみる

ベースライン プロファイルまたは起動プロファイルを生成するには、アプリを実行するMacrobenchmark テストを作成します。テスト中にプロファイル データが収集され、アプリのコンパイル時に使用されます。テストは、明日説明する新しい UiAutomator API を使用して記述します。

このようなベンチマークの作成は簡単です。完全なサンプルは GitHub で確認できます。

  @Test

fun profileGenerator() {

    rule.collect(

        packageName = TARGET_PACKAGE,

        maxIterations = 15,

        stableIterations = 3,

        includeInStartupProfile = true

    ) {

        uiAutomator {

            startApp(TARGET_PACKAGE)

        }

    }


}

考慮事項

まず、ユーザーが最も頻繁に利用するパスのベースライン プロファイルと起動プロファイルの Macrobenchmark テストを作成します。つまり、ユーザーがアプリにアクセスするメインのエントリ ポイント(通常はログイン後)です。次に、ベースライン プロファイルのみを対象として、より完全な全体像を把握するためのテストケースをさらに作成します。ベースライン プロファイルですべてをカバーする必要はありません。最も使用されるパスに絞り、実際の環境でパフォーマンスを測定します。詳しくは明日の投稿で説明します。

プロファイルに基づく最適化を使ってみる

ベースライン プロファイルの仕組みについては、Android Developers Summit のこちらの動画をご覧ください。

また、プロファイルに基づく最適化に関する Android ビルド時間の記事では、さらに詳しく説明しています。

ベースライン プロファイルと起動プロファイルに関する詳細なガイダンスも用意していますので、ぜひご覧ください。

Jetpack Compose のパフォーマンスの改善

Android の UI フレームワークでは、エンジニアリング チームのパフォーマンスへの投資が成果を上げています。Jetpack Compose バージョン 1.9 以降では、内部の長時間スクロール ベンチマーク テストでスクロール ジャンクが 0.2 % に減少しました。

jankyFrames.png

これらの改善は、最新リリースに搭載されたいくつかの機能によって実現しました。

カスタマイズ可能なキャッシュ ウィンドウ

デフォルトでは、Lazy レイアウトはスクロール方向に 1 つのアイテムのみを事前にコンポーズし、画面外にスクロールしたアイテムは破棄されます。ビューポートまたは dp サイズの割合で、保持するアイテムの数をカスタマイズできるようになりました。これにより、アプリはより多くの処理を事前に行うことができ、フレーム間のコンポーズを一時停止できるため、利用可能な時間をより効率的に使用できます。

カスタマイズ可能なキャッシュ ウィンドウの使用を開始するには、LazyLayoutCacheWindow をインスタンス化して、Lazy リストまたは Lazy グリッドに渡します。さまざまなキャッシュ ウィンドウ サイズ(ビューポートの 50% など)を使用して、アプリのパフォーマンスを測定します。最適な値は、コンテンツの構造とアイテムのサイズによって異なります。

  val dpCacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp)

val state = rememberLazyListState(cacheWindow = dpCacheWindow)

LazyColumn(state = state) {

    // column contents

}

コンポーズの一時停止

この機能を使用すると、コンポーズを一時停止し、処理を複数のフレームに分割できます。この API は 1.9 でリリースされ、Lazy レイアウトのプリフェッチでは 1.10 でデフォルトで使用されるようになりました。コンポーズ時間が長い複雑なアイテムで最も効果を発揮します。

image.png

Compose のパフォーマンス最適化の強化

Compose のバージョン 1.9 と 1.10 では、チームはあまり目立たない最適化もいくつか行いました。

内部でコルーチンを使用するいくつかの API が改善されました。たとえば、DraggableClickable を使用する場合、デベロッパーは反応時間の短縮と割り当て数の改善を確認できます。

レイアウトの長方形のトラッキングの最適化により、onVisibilityChanged()onLayoutRectChanged() などの Modifier のパフォーマンスが向上しました。これらの API を明示的に使用しない場合でも、レイアウト フェーズが高速化されます。

もう 1 つのパフォーマンスの改善は、onPlaced() を介して位置を監視する際にキャッシュされた値を使用することです。

バックグラウンドでテキストをプリフェッチする

バージョン 1.9 以降、Compose ではバックグラウンド スレッドでテキストをプリフェッチできるようになりました。これにより、キャッシュをプリウォームしてテキスト レイアウトを高速化できます。これは、アプリのレンダリング パフォーマンスに関連します。レイアウト中、テキストは Android フレームワークに渡され、そこで単語キャッシュが作成されます。デフォルトでは、これは Ui スレッドで実行されます。プリフェッチと単語キャッシュの作成をバックグラウンド スレッドにオフロードすると、特に長いテキストの場合にレイアウトを高速化できます。バックグラウンド スレッドでプリフェッチするには、LocalBackgroundTextMeasurementExecutorCompositionLocalProvider に渡して、内部で BasicText を使用する任意のコンポーザブルにカスタム エグゼキュータを渡します。

  val defaultTextMeasurementExecutor = Executors.newSingleThreadExecutor()

CompositionLocalProvider(

    LocalBackgroundTextMeasurementExecutor provides DefaultTextMeasurementExecutor

) {

    BasicText("Some text that should be measured on a background thread!")


}

テキストによっては、テキスト レンダリングのパフォーマンスが向上します。アプリのレンダリング パフォーマンスが向上していることを確認するには、ベンチマークを実行して結果を比較します。

バックグラウンド処理のパフォーマンスに関する考慮事項

バックグラウンド処理は、多くのアプリに不可欠な要素です。WorkManager や JobScheduler などのライブラリを使用して、次のようなタスクを実行している場合があります。

  • 分析イベントの定期的なアップロード
  • バックエンド サービスとデータベース間のデータの同期
  • メディアの処理(画像のサイズ変更や圧縮など)

これらのタスクを実行する際の主な課題は、パフォーマンスと電力効率のバランスを取ることです。WorkManager を使用すると、このバランスを実現できます。電力効率を重視した設計で、指定した制約やシステムによって課せられた制約など、さまざまな要因によって影響を受ける最適な実行ウィンドウに処理を延期できます。

ただし、WorkManager は万能なソリューションではありません。Android には、特定の一般的なコア ユーザー ジャーニー(CUJ)を念頭に置いて設計された、電力最適化された API も多数用意されています。  

これらの API の一部については、ウィジェットの更新やバックグラウンドでの位置情報の取得など、バックグラウンド処理のランディング ページをご覧ください。

バックグラウンド処理のローカル デバッグツール: 一般的なシナリオ

バックグラウンド処理をデバッグし、タスクが遅延または失敗した理由を把握するには、システムがタスクをどのようにスケジュールしたかを把握する必要があります。

WorkManager には、ローカルでデバッグしてパフォーマンスを最適化するのに役立つ関連 ツールがいくつか用意されています(JobScheduler でも使用できるものもあります)。WorkManager を使用する際に発生する可能性のある一般的なシナリオと、デバッグに使用できるツールについて説明します。

スケジュールされた処理が実行されない理由をデバッグする

スケジュールされた処理が遅延したり、まったく実行されなかったりする原因は、指定された制約が満たされていない、システムによって制約が課せられたなど、さまざまな要因が考えられます。

スケジュールされた処理が実行されない理由を調査する最初の手順は、処理が正常にスケジュールされたことを確認する ことです。 スケジュール ステータスを確認したら、処理の実行を妨げる制約や前提条件が満たされていないかどうかを判断します。

このシナリオをデバッグするためのツールがいくつかあります。

Background Task Inspector

Background Task Inspector は、Android Studio に直接統合された強力なツールです。すべての WorkManager タスクと、それに関連付けられた状態(実行中、キューに追加済み、失敗、成功)を視覚的に表現します。

Background Task Inspector を使用して、スケジュールされた処理が実行されない理由をデバッグするには、表示されている処理のステータスを確認します。[キューに追加済み] ステータスは、処理がスケジュールされたものの、まだ実行を待機していることを示します。

メリット: このツールを使用すると、すべてのタスクを簡単に確認できるだけでなく、処理をチェーン化している場合に特に便利です。Background Task Inspector には、前のタスクの失敗が次のタスクの実行に影響したかどうかを視覚化できるグラフビューが用意されています。

image.png

Background Task Inspector のリストビュー

image.png

Background Task Inspector のグラフビュー

adb shell dumpsys jobscheduler

このコマンドは、指定された制約とシステムによって課せられた制約とともに、アクティブな JobScheduler ジョブ(WorkManager ワーカーを含む)のリストとジョブ履歴を返します。ジョブ履歴も返します。

スケジュールされた処理と関連する制約を別の方法で確認する場合は、これを使用します。WorkManager 2.10.0 より前の WorkManager バージョンでは、adb shell dumpsys jobscheduler は次の名前のワーカーのリストを返します。

  [package name]/androidx.work.impl.background.systemjob.SystemJobService

アプリに複数のワーカーがある場合、WorkManager 2.10.0 に更新すると、ワーカー名を確認してワーカーを簡単に区別できます。

  #WorkerName#@[package name]/androidx.work.impl.background.systemjob.SystemJobService

メリット: このコマンドは、Background Task Inspector では判断できない、システムによって課せられた制約 があるかどうかを把握するのに役立ちます。たとえば、アプリのスタンバイ バケットが返されます。これは、スケジュールされた処理が完了するウィンドウに影響する可能性があります。

デバッグ ロギングを有効にする

カスタム ロギングを有効にすると、詳細な WorkManager ログ(WM— が付加されます)を確認できます。

メリット: これにより、処理がスケジュールされるタイミング、制約が満たされるタイミング、ライフサイクル イベントを把握できます。アプリの開発中にこれらのログを参照できます。

WorkInfo.StopReason

特定のワーカーで予測できないパフォーマンスが発生する場合は、WorkInfo.getStopReason を使用して、前回の実行試行でワーカーが停止した理由をプログラムで確認できます。 

getWorkInfoByIdFlow を使用して WorkInfo を監視するようにアプリを構成し、処理がバックグラウンド制限、制約、頻繁なタイムアウト、またはユーザーによる停止の影響を受けているかどうかを特定することをおすすめします。

メリット: WorkInfo.StopReason を使用して、ワーカーのパフォーマンスに関するフィールド データを収集できます。

Android Vitals によってフラグが設定された WorkManager に起因する wake lock の時間が長いことをデバッグする

Android Vitals には、バッテリーの消耗の原因となる wake lock をハイライトする「過度の部分的な wake lock」という指標があります。WorkManager はタスクを実行するために wake lock を取得します。wake lock が Google Play で設定されたしきい値を超えると、アプリの可視性に影響する可能性があります。処理に起因する wake lock の時間が長い理由をデバッグするにはどうすればよいですか?次のツールを使用できます。

Android Vitals のダッシュボード

まず、Android Vitals の過度の wake lock ダッシュボードで、wake lock の時間が長い原因が WorkManager であり、アラームや他の wake lock ではないことを確認します。WorkManager が原因で保持されている wake lock を把握するには、他の API によって作成された wake lock を特定するのドキュメントをご覧ください。

Perfetto

Perfetto は、システム トレースを分析するためのツールです。WorkManager のデバッグに特に使用する場合は、[デバイスの状態] セクションで、処理の開始時刻、実行時間、消費電力への影響を確認できます。

[デバイスの状態: ジョブ] トラックには、実行されたワーカーとその関連付けられた wake lock が表示されます。

deviceState.png

Perfetto の [デバイスの状態] セクション。CleanupWorker と BlurWorker の実行を示しています。

リソース

発生する可能性のある他のシナリオで使用できるデバッグ方法の概要については、WorkManager のデバッグのページをご覧ください。

これらの方法を実際に試して、WorkManager のデバッグについて詳しくは、 高度な WorkManager とテストの Codelab をご覧ください。

次のステップ

今回は、コード圧縮から一歩進んで、Android ランタイムと Jetpack Compose が実際にアプリをレンダリングする方法について説明しました。ベースライン プロファイルを使用したクリティカル パスの事前コンパイル、新しい Compose 1.9 と 1.10 の機能を使用したスクロール状態の平滑化など、これらのツールはアプリの操作感に重点を置いています。また、バックグラウンド処理のデバッグに関する効果的な手法についても詳しく説明しました。

Android に質問する

金曜日に、パフォーマンスに関するライブ AMA を開催します。#AskAndroid を使用して今すぐ質問を投稿すると、エキスパートが回答します。

課題

月曜日に R8 を有効にするようお願いしました。今回は、アプリのベースライン プロファイルを 1 つ生成 してください。

Android Studio Otter を使用すると、ベースライン プロファイル ジェネレータ モジュール ウィザードでこれまで以上に簡単に生成できます。アプリの起動とログインのみでもかまいませんので、最も重要なクリティカル ユーザー ジャーニーを選択してプロファイルを生成してください。

生成したら、Macrobenchmark を実行して CompilationMode.NoneCompilationMode.Partial を比較します。

#optimizationEnabled を使用して、起動時間の改善をソーシャル メディアで共有してください。

明日の配信をお見逃しなく

R8 でアプリを縮小し、プロファイルに基づく最適化でランタイムを最適化しました。しかし、これらの成果をステークホルダーに証明するにはどうすればよいでしょうか?また、本番環境に影響を与える前に回帰を検出するにはどうすればよいでしょうか?

明日の第 4 回: パフォーマンス レベリング ガイドでは、Play Vitals のフィールド データから Perfetto を使用した詳細なローカル トレースまで、成功を測定する方法について詳しく説明します。

作成者:

続きを読む