Jetpack Compose のパフォーマンス

Jetpack Compose は、初期設定の状態で優れたパフォーマンスを実現することを目的としています。このページでは、パフォーマンス向上のためのアプリの作成と構成方法と、避けるべきパターンについて説明します。

まず、Compose の思想で Compose の主要なコンセプトを理解しておくことをおすすめします。

アプリを適切に構成する

アプリのパフォーマンスが低い場合は、構成に問題がある可能性があります。まず、次の構成オプションを確認することをおすすめします。

リリースモードでビルドし、R8 を使用する

パフォーマンスの問題が発生した場合は、アプリをリリースモードで実行してみてください。デバッグモードは、多くの問題の検出に役立ちますが、パフォーマンス コストが大幅に低下し、パフォーマンスを低下させる可能性のある他のコードの問題を特定するのが困難になる可能性があります。リリースビルドのパフォーマンスと効率性を高めるため、R8 コンパイラを使用して最適化と圧縮を有効にすることをおすすめします。

ベースライン プロファイルを使用する

ベースライン プロファイルにより、含まれるコードパスの解釈とジャストインタイム(JIT)コンパイルの手順を行う必要がなくなるため、初回起動からのコード実行速度が約 30% 向上します。ベースライン プロファイルをアプリまたはライブラリに配布することで、Android ランタイム(ART)は事前(AOT)コンパイルによって含まれるコードパスを最適化し、新しいアプリのインストールとアップデートのたびにパフォーマンスを改善できます。このプロファイルに基づく最適化(PGO)を使用すると、起動の最適化、インタラクション ジャンクの削減、エンドユーザーの全体的なランタイム パフォーマンスの向上がアプリの初回起動時から可能になります。

Compose は、Android プラットフォームの一部ではなく、ライブラリとして配布されています。このアプローチにより、Compose チームは Compose を頻繁にアップデートし、古い Android バージョンをサポートできます。ただし、Compose をライブラリとして配布するとコストがかかります。Android プラットフォーム コードがすでにコンパイルされ、デバイスにインストールされています。ただし、ライブラリはアプリの起動時に読み込み、機能が必要な場合に JIT を解釈する必要があります。これにより、起動時やライブラリ機能を初めて使用するときに、アプリの速度が低下する可能性があります。

ベースライン プロファイルを定義することで、パフォーマンスを改善できます。これらのプロファイルは、クリティカル ユーザー ジャーニーに必要なクラスとメソッドを定義し、アプリの APK または AAB とともに配布されます。アプリのインストール中に、ART はこの重要なコードを AOT コンパイルし、アプリの起動時に使用できるようにします。

適切なベースライン プロファイルを定義するのは必ずしも容易ではありません。そのため、Compose にはデフォルトでベースライン プロファイルが付属しています。何もしなくても、このメリットが得られます。ただし、Compose に付属のベースライン プロファイルには、Compose ライブラリ内のコードの最適化のみが含まれています。最適な最適化を行うには、Macrobenchmark を使用してクリティカル ユーザー ジャーニーをカバーするアプリのベースライン プロファイルを作成することもおすすめします。独自のプロファイルを定義する場合は、プロファイルをテストして、有効性を検証する必要があります。そのためには、アプリの Macrobenchmark テストを作成し、ベースライン プロファイルの作成と変更を行うときにテスト結果を確認することをおすすめします。Compose UI の Macrobenchmark テストを作成する方法の例については、Macrobenchmark Compose のサンプルをご覧ください。

リリースモード、R8、ベースライン プロファイルの影響の詳細については、ブログ投稿「リリースで常に Compose のパフォーマンスをテストすべき理由」をご覧ください。

3 つの Compose フェーズがパフォーマンスに与える影響

Jetpack Compose のフェーズで説明したように、Compose がフレームを更新する際、次の 3 つのフェーズを経由します。

  • コンポジション: Compose は表示する内容を決定します。コンポーズ可能な関数を実行し、UI ツリーを構築します。
  • レイアウト: Compose は、UI ツリー内の各要素のサイズと配置を決定します。
  • 描画: Compose は実際に個々の UI 要素をレンダリングします。

Compose は、必要ない場合にこれらのフェーズのいずれかをインテリジェントにスキップできます。たとえば、1 つのグラフィック要素が、同じサイズの 2 つのアイコン間で入れ替わるとします。この要素はサイズが変更されず、UI ツリーの要素が追加または削除されていないため、Compose はコンポジション フェーズとレイアウト フェーズをスキップして、この 1 つの要素を再描画できます。

ただし、コーディング上の誤りがあると、Compose が安全にスキップできるフェーズを判別するのが難しくなる場合があります。疑わしい場合、Compose は 3 つのフェーズすべてを実行するため、UI の速度が低下する可能性があります。そのため、パフォーマンスに関するベスト プラクティスの多くは、Compose が不要なフェーズをスキップできるようにすることです。

一般的に、パフォーマンスを向上させるには、次の 2 つの大原則に従う必要があります。

  • 可能な限り、コンポーズ可能な関数から計算を移動します。 UI が変更されるたびに、コンポーズ可能な関数の再実行が必要になる場合があります。コンポーザブルに配置したコードは、おそらくアニメーションのフレームごとに再実行されます。コンポーザブルのコードは、UI の作成に必要なもののみに制限します。

  • 状態の読み取りを可能な限り延期する。状態の読み取りを子コンポーザブルまたは後のフェーズに移動することで、再コンポーズを最小限に抑えるか、コンポジション フェーズを完全にスキップできます。これを行うには、頻繁に変化する状態については状態値の代わりにラムダ関数を渡し、頻繁に変化する状態を渡すときはラムダベースの修飾子を優先します。この手法の例については、可能な限り読み取りを延期するのセクションをご覧ください。