ビットマップ画像を最適化する

画像を扱う場合は、注意しないとパフォーマンスの問題が起こりやすくなります。JPG や PNG などの圧縮形式の小さなグラフィックでも、表示用にデコードされると大きなビットマップになることがあります。グラフィックの使用方法が効率的でないと、メモリの問題が発生し、アプリやデバイス上の他のアプリのパフォーマンスが低下する可能性があります。アプリのパフォーマンスを最大限に高めるには、以下のおすすめの方法を実施してください。

画像読み込みライブラリを使用する

Coil(Kotlin ファーストのプロジェクトの場合)や Glide(Java プロジェクトの場合)などの画像読み込みライブラリを使用すると、アプリの効率を高めることができます。これらのライブラリは、画像のキャッシュ保存、必要に応じたグラフィックのダウンサンプリング、グラフィック オブジェクトの再利用などを行うことで、アプリのメモリ使用量を削減します。

画像をダウンサンプリングする

ニーズに合った適切な画像サイズを使用してください。大きな高解像度画像を小さなコンテナ(サムネイルなど)に読み込むことは避ける必要があります。代わりに、ダウンサンプリングを使用して、画像をメモリにデコードする前に縮小します。

クライアントサイドのダウンサンプリング

Coil や Glide などの画像読み込みライブラリは、ダウンサンプリングを自動的に処理します。ImageLoader(Coil の場合)または DownsampleStrategy(Glide の場合)を使用して、ダウンサンプリング戦略を構成できます。ビットマップを手動で管理する場合は、inSampleSize を使用して小さいバージョンをデコードできます。これを安全に行うには、まず inJustDecodeBoundstrue に設定して、メモリを割り当てずに画像の寸法を読み取り、サンプルサイズを計算し、inSampleSize をその値に設定し、inJustDecodeBoundsfalse に設定してから、画像をデコードします。

サーバーサイドでのサイズ変更を優先する

可能な場合は、必要な画像の正確な寸法をバックエンド サーバーから直接リクエストします。これにより、ネットワーク使用量とディスク キャッシュのフットプリントが削減され、デバイスで画像のサイズを変更する際のメモリ オーバーヘッドが回避されるため、メモリ使用量が軽減されます。

ライブラリを構成して、ターゲット ビューのサイズを画像 URL に動的に追加できます。たとえば、Coil ではカスタム インターセプタを使用してこれを行うことができ、Glide ではカスタムモデル ローダー(BaseGlideUrlLoader など)を使用してこれを行うことができます。

制約のないレイアウト サイズを避ける

画像ローダーが(クライアントサイドまたはサーバーサイドで)効果的にダウンサンプリングするには、リクエストを実行する前にターゲット サイズを把握している必要があります。

リモート画像を読み込むコンポーザブルで wrapContentSize を使用したり、サイズを制約なしにしたりすることは避けてください。これらのライブラリでターゲットの境界を推測できない場合、元のフルサイズの画像の読み込みにフォールバックします。これにより、必要以上に大きな画像が読み込まれ、メモリ使用量とレイテンシが増加する可能性があります。

代わりに、画像コンポーザブルに明示的なディメンションを設定するか(たとえば Modifier.size を使用)、アスペクト比を定義します。これにより、レイアウト エンジンは正確なピクセル ターゲットを事前に計算できるようになり、画像ローダーはそれを使用して正しいサイズの画像アセットをリクエストしてデコードできます。

各種の画面サイズ向けに代替リソースを供給する

アプリで画像を提供する場合は、異なるデバイス解像度に合わせて異なるサイズのアセットを供給することを検討してください。これにより、デバイス上のアプリのダウンロード サイズを削減できます。また、低解像度のデバイスでは低解像度の画像が読み込まれるため、パフォーマンスが向上します。各種のデバイスサイズ向けに代替ビットマップを提供する方法の詳細については、代替ビットマップ ドキュメントを確認してください

パディングを直接適用しない

画像にパディングを追加する必要がある場合があります。たとえば、レターボックス用に画像を透明な枠線で囲むことができます。このような場合は、画像のサイズを変更して、画像に直接パディングを追加しないでください。代わりに、画像の寸法はそのままにして、InsetDrawable を使用して画面上の画像の位置を調整します。または、画像を含む Composable または View にパディングを追加することもできます。

適切なピクセル形式を選択する

適切なピクセル形式を選択して、メモリと品質のバランスを取ります。透明度が必要ない場合は RGB_565 を使用します。この形式は、デフォルトの ARGB_8888 形式の半分のメモリを使用します。

Glide では、DecodeFormat を使用してこれを構成できます。Coil では、bitmapConfig プロパティを使用できます。

可能な場合はベクターを使用する

幾何学的図形で構成された画像の場合、ベクター グラフィックはビットマップよりもはるかに小さく、あらゆるディスプレイ密度でスムーズに拡大縮小できます。適切な場合は、ShapeDrawable などの要素を使用してグラフィックを表します。

可能な場合はビットマップをリリースして再利用する

大きなグラフィック ファイルは、多くのメモリを消費する可能性があります。影響を軽減するには、グラフィック オブジェクトを可能な限り解放または再利用する必要があります。

画像読み込みライブラリを使用する場合は、不要になったら必ずビットマップをライブラリの管理対象プールに解放してください。ライブラリは必要に応じてオブジェクトを再利用でき、将来のニーズに対応できるようにメモリバッファを確保します。

グラフィックを手動で管理している場合は、ガベージ コレクションに頼るのではなく、Bitmap.recycle を呼び出してビットマップを解放し、すぐに Bitmap 参照を破棄する必要があります。

その他のヒントとコツ

このセクションでは、グラフィックを処理する際のアプリのパフォーマンスを改善するその他の方法をいくつか紹介します。

大きい画像を AAB/APK ファイルと一緒にパッケージ化しない

アプリのダウンロード サイズが大きくなる主な原因の一つとして、AAB または APK ファイル内にパッケージ化されるグラフィックがあります。APK Analyzer ツールを使用して、必要な画像ファイルよりも大きいパッケージを作成しないようにします。サイズを小さくするか、または画像をサーバーに配置して必要なときだけダウンロードすることを検討してください。

冗長なビットマップを見つける

同じ画像のコピーが複数あると、メモリが無駄になります。Android Studio のプロファイラを使用して、冗長なグラフィックを特定できます。ヒープダンプ アナライザを使用してヒープダンプをキャプチャし、[重複するビットマップ] 設定を選択して結果をフィルタします。

ImageBitmap を使用する場合は描画前に prepareToDraw を呼び出す

ImageBitmap を使用する場合、GPU にテクスチャをアップロードするプロセスを開始するには、実際に描画する前に ImageBitmap#prepareToDraw() を呼び出します。これは GPU がテクスチャを準備するのに役立ち、画面上に画像を表示するパフォーマンスが向上します。ほとんどの画像読み込みライブラリはこの最適化をすでに行っていますが、ご自身で ImageBitmap クラスを扱う場合はこの点に留意してください。

Painter ではなく Int DrawableRes または URL をパラメータとしてコンポーザブルに渡す

画像の処理は複雑であるため(たとえば Bitmaps の equals 関数を記述すると計算コストが高くなります)、Painter API は @Stable アノテーションで明示的に安定としてマークされていません。安定していないクラスは、データが変更されたかどうかをコンパイラが容易に推測できないため、不要な再コンポジションを発生させる可能性があります。

したがって、Painter をパラメータとしてコンポーザブルに渡すのではなく、URL またはドローアブル リソース ID をパラメータとして渡すことをおすすめします。

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}