画像を扱う場合は、注意しないとパフォーマンスの問題が起こりやすくなります。JPG や PNG などの圧縮形式の小さなグラフィックでも、表示用にデコードすると大きなビットマップになることがあります。グラフィックを効率的に使用しないと、メモリの問題が発生し、アプリやデバイス上の他のアプリのパフォーマンスが低下する可能性があります。アプリのパフォーマンスを最大限に高めるには、以下のおすすめの方法を実施してください。
画像読み込みライブラリを使用する
Coil(Kotlin ファースト プロジェクトの場合)や Glide(Java プロジェクトの場合)などの画像読み込みライブラリを使用すると、アプリの効率を改善できます。これらのライブラリは、画像のキャッシュ、必要に応じたグラフィックのダウンサンプリング、グラフィック オブジェクトの再利用などを行うことで、アプリのメモリ使用量を削減します。
画像をダウンサンプリングする
ニーズに適した画像サイズを使用してください。高解像度の大きな画像を小さなコンテナ(サムネイルなど)に読み込むことは避けてください。代わりに、ダウンサンプリングを使用して画像を縮小してから、メモリにデコードします。
クライアントサイドのダウンサンプリング
Coil や Glide などの画像読み込みライブラリは、ダウンサンプリングを自動的に処理します。ダウンサンプリング戦略は、ImageLoader(Coil の場合)または DownsampleStrategy(Glide の場合)を使用して構成できます。ビットマップを手動で管理している場合は、inSampleSize を使用して、より小さいバージョンをデコードできます。これを安全に行うには、まず inJustDecodeBounds を true に設定して、メモリを割り当てずに画像のサイズを読み取り、サンプルサイズを計算して、inSampleSize をその値に設定し、inJustDecodeBounds を false に設定してから、画像をデコードします。
サーバーサイドのサイズ変更を優先する
可能であれば、必要な正確な画像サイズをバックエンド サーバーから直接リクエストします。これにより、ネットワーク使用量とディスク キャッシュのフットプリントが削減されるとともに、デバイスでの画像のサイズ変更によるメモリ オーバーヘッドが回避され、メモリ使用量が軽減されます。
ターゲット ビューサイズを画像 URL に動的に追加するようにライブラリを構成できます。たとえば、Coil ではカスタム インターセプターを使用してこれを実現でき、Glide ではカスタムモデル ローダー(BaseGlideUrlLoader など)を使用してこれをサポートしています。
制約のないレイアウト サイズを避ける
画像ローダが(クライアントサイドまたはサーバーサイドで)効果的にダウンサンプリングするには、リクエストを実行する前にターゲット サイズを把握する必要があります。
リモート画像を読み込むコンポーザブルで wrapContentSize を使用したり、ディメンションを制約のないままにしたりしないでください。これらのライブラリがターゲット境界を推測できない場合は、元のフルサイズ画像を読み込むようにフォールバックします。これにより、必要以上に大きな画像が読み込まれ、メモリ使用量とレイテンシが増加する可能性があります。
代わりに、画像コンポーザブルに明示的なサイズを設定するか(Modifier.size を使用するなど)、アスペクト比を定義します。これにより、レイアウト エンジンは正確なピクセル ターゲットを事前に計算できます。画像ローダーは、このターゲットを使用して、正しいサイズのアセットをリクエストしてデコードできます。
各種の画面サイズ向けに代替リソースを供給する
アプリで画像を提供する場合は、異なるデバイス解像度に合わせて異なるサイズのアセットを供給することを検討してください。これにより、デバイス上のアプリのダウンロード サイズを削減できます。また、低解像度のデバイスでは低解像度の画像が読み込まれるため、パフォーマンスが向上します。各種のデバイスサイズ向けに代替ビットマップを提供する方法の詳細については、代替ビットマップ ドキュメントを確認してください。
パディングを直接適用しない
場合によっては、画像にパディングを追加する必要があります。たとえば、レターボックス表示のために、画像の周囲に透明な枠線を表示したい場合があります。このような場合、画像にパディングを直接追加して画像のサイズを変更しないでください。代わりに、画像のサイズはそのままにして、InsetDrawable を使用して画面上の画像の位置を調整します。または、画像を含むコンポーザブルまたはビューにパディングを追加することもできます。
適切なピクセル形式を選択する
適切なピクセル形式を選択して、メモリと品質のバランスを取ります。透明度が不要な場合は RGB_565 を使用します。この形式では、デフォルトの ARGB_8888 形式の半分のメモリを使用します。
Glide では、DecodeFormat を使用してこれを構成できます。Coil では、bitmapConfig プロパティを使用できます。
可能な場合はベクターを使用する
幾何学的図形で構成される画像の場合、ベクター グラフィックはビットマップよりもはるかに小さく、あらゆるディスプレイ密度に合わせてスムーズにスケーリングされます。必要に応じて、ShapeDrawable などの要素を使用してグラフィックを表現します。
可能な場合はビットマップを解放して再利用する
サイズの大きいグラフィック ファイルは、大量のメモリを消費する可能性があります。その影響を軽減するには、可能な限りグラフィック オブジェクトを解放または再利用する必要があります。
画像読み込みライブラリを使用する場合は、ビットマップが不要になった時点で、ライブラリのマネージド プールにビットマップを解放するようにします。ライブラリは、必要に応じてオブジェクトを再利用し、将来のニーズに備えてメモリバッファを保持できます。
グラフィックを手動で管理している場合は、ガベージ コレクションに依存するのではなく、Bitmap.recycle を呼び出して Bitmap 参照をすぐに破棄することで、ビットマップの使用が終了したときにビットマップを解放する必要があります。
その他のヒントとコツ
このセクションでは、グラフィックを処理する際のアプリのパフォーマンスを向上させるその他の方法をいくつか紹介します。
大きい画像を AAB/APK ファイルと一緒にパッケージ化しない
アプリのダウンロード サイズが大きくなる主な原因の一つとして、AAB または APK ファイル内にパッケージ化されるグラフィックがあります。APK Analyzer ツールを使用して、必要な画像ファイルよりも大きいパッケージを作成しないようにします。サイズを小さくするか、または画像をサーバーに配置して必要なときだけダウンロードすることを検討してください。
冗長なビットマップを検出する
同じ画像のコピーが複数あると、メモリが無駄になります。Android Studio プロファイラを使用すると、冗長なグラフィックを特定できます。ヒープダンプ アナライザを使用してヒープダンプをキャプチャし、[duplicate bitmaps] 設定を選択して結果をフィルタします。
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) {
}
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- ImageBitmap と ImageVector の比較 {:#bitmap-vs-vector}
- Compose で UI 状態を保存する
- Jetpack Compose のフェーズ