Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Coil3を内部実装から読み解く~キャッシュ戦略とAVIF画像の描画〜/nikkei-tech...

 Coil3を内部実装から読み解く~キャッシュ戦略とAVIF画像の描画〜/nikkei-tech-talk50

2026/09/24開催 NIKKEI Tech Talk #50 発表スライド #
Android 画像ライブラリを内部実装から読み解く 〜Coil3のキャッシュ戦略とAVIF画像の描画〜
というタイトルで日本経済新聞社の澤田廉平さんが登壇しました
https://nikkei.connpass.com/event/403186/

More Decks by 日本経済新聞社 エンジニア採用事務局

Other Decks in Technology

Transcript

  1. はじめに 〜日経電子版と Coil3〜 • Coil3[1]: Kotlin 向けの画像読み込みライブラリ ◦ 画像データのリクエスト、デコード、描画までを担う •

    利用箇所:記事本文画像の詳細画面、ウィジェット記事一覧のサムネイル、 etc... [1] https://github.com/coil-kt/coil 3
  2. 背景:画像のディスク使用量と転送量を抑えたい • 日経電子版では多くの画像を扱うため効果を得られやすい • 各々を抑える手段 ◦ ディスク使用量 ー キャッシュ構成を改善する (Part1)

    ◦ 転送量 ー 画像サイズそのものを小さくする (Part2) Network Fetcher ImageRequest Memory Cache URL → バイト列 デコード済み Bitmap Disk Cache Decoder 表⽰ バイト列 → Bitmap Image / Painter OkHttp ▲ ▲ Part 1 Part 2 キャッシュ構成 AVIF デコード 4
  3. Coil3 で登場しうるキャッシュ構成 1. Coil Memory Cache デコード済み Bitmap 各キャッシュの性質 管轄のライブラリ

    2. Coil Disk Cache 未デコードの画像バイト列 キャッシュ先 1. Coil Memory Cache Coil3 RAM 2. Coil Disk Cache Coil3 ファイルシステム 3. OkHttp Cache OkHttp ファイルシステム 3. OkHttp Cache HTTP レスポンス全体 (ヘッダー + ボディ) ファイルシステムに異なるライブラリから それぞれ画像をキャッシュする可能性がある 6
  4. 実際キャッシュはどう設定されていたのか? OkHttp ImageLoader(アプリ起動時に1回⽣成) Coil3 ImageRequest(画像1枚ごとに⽣成) Cache(cacheDir/"http_cache", 10MB) OkHttp Cache を有効にする

    ImageRequest.Builder().data(url) OkHttpClient .diskCachePolicy(...) その他の API通信と共⽤していた Coil Disk Cache を有効にする ImageLoader ImageRequest imageLoader.execute(imageRequest) キャッシュを順に探し、ヒットしなければネットワークリクエストにより取得する ディスク上の2箇所に同じ画像をキャッシュしたため余分なディスク使⽤が起きていた 7
  5. 対策: 画像専用の OkHttpClient を新設する OkHttp ImageLoader(アプリ起動時に1回⽣成) Coil3 ImageRequest(画像1枚ごとに⽣成) Cache() キャッシュを設定しない

    ImageRequest.Builder().data(url) OkHttpClient .diskCachePolicy(...) 画像⽤に新設 Coil Disk Cache を有効にする ImageLoader ImageRequest imageLoader.execute(imageRequest) キャッシュを順に探し、ヒットしなければネットワークリクエストにより取得する Coil Disk Cache のみに画像がキャッシュされるようになる 8
  6. Disk Cache のキャッシュ戦略を設定する • Coil CacheStrategy: Disk Cache への read/write

    の設定 DefaultCacheStrategy // read(): 鮮度関係なく全て読み取る override suspend fun read(...): ReadResult { // Always return the disk cache response. return ReadResult(cacheResponse) } // write(): レスポンスコード 304 ならヘッダーだけ更新 override suspend fun write(...): WriteResult { if (code == 304 && cacheResponse != null) return WriteResult(res.copy(body = null)) if (code in 200..299) return WriteResult(networkResponse) return WriteResult.DISABLED } CacheControlCacheStrategy // read(): 鮮度を計算してリクエスト判断 override suspend fun read(...): ReadResult { if (!responseCaching.noCache && age + minFresh < fresh + maxStale) return ReadResult(cacheResponse) return ReadResult(networkRequest.copy( headers = ...add("If-None-Match", etag))) } // write(): no-store を弾いて DEFAULT に委譲 override suspend fun write(...): WriteResult { if (!isCacheable(...)) return DISABLED return CacheStrategy.DEFAULT.write(...) } ◦ キャッシュがディスクにあれば取得できるため最速かつ通信量も 抑えられる ◦ Cache-Control / ETag に従って再検証する × 新しいキャッシュかどうかを⾒ないため画像が更新されても 古い画像が使われる ◦ URL は変わらないが中⾝が更新される画像に対応できる ◦ no-store ならディスクへの書き込みが⾏われない 9
  7. Part1 まとめ • Coil3 では二重キャッシュが起きうる ◦ ◦ Coil3 は OkHttp

    を内包しているため、HTTP Cache も利用しうる Coil3 のキャッシュ領域で一元管理するように設定 • キャッシュ戦略を設定しよう ◦ ◦ デフォルトのキャッシュ戦略では鮮度関係なく Disk Cache が必ず利用される キャッシュ期限のある画像なら CacheControlCacheStrategy を利用する 10
  8. そもそも... AVIF [2] とは? • AV1動画のキーフレーム技術をベースに静止画化した規格 • 従来の WebP や

    JPEG を上回る圧縮率とアルファチャンネル・HDR 対応を両立 特徴 採用するとどうなるか ・ JPEG / WebP と比較して圧縮効率が高い ◦ JPEG と比較してファイルサイズが約50%小さい ・ 高圧縮時でもブロックノイズや色にじみが発生しにくい ◦ 軽量化したことでレンダリングの待機時間も短縮 ・ αチャンネル(透過)・アニメーション・10/12-bit HDR 対応 × AV1ハードウェアデコーダを持たない端末ではソフトウェア デコードによる負荷が増加する ・ iOS 16 以降 / Android 12 以降でサポート済み × Android 11 以下にデコーダは含まれない [2] https://aomedia.org/specifications/avif/ 12
  9. 問題:Coil3 で AVIF を描画できないことがある • Android 12〜15 にて特定の AVIF画像を表示できない問題を観測 •

    AVIF画像をデコードする段階でエラーが発生 • 発生するのは高解像度の画像のみと判明 ◦ ◦ ◦ AV1 Level 4.0 (解像度 1920×1080 程度) 相当を超えるとデコードに失敗する 描画できなかった画像例は 3503×1658 = 約580万ピクセル Android 16 以上では描画できたため画像が壊れているわけではなかった • Coil3 は OS のプラットフォームデコーダに依存しているので、 OS差異が影響しているのかも...? java.io.IOException: getPixels failed with error invalid input 13
  10. OS バージョンで挙動が3つに分かれる OS バージョン AVIF の描画 理由 Android 11 以下

    (API 30 以下) × 表⽰できない プラットフォームに AVIF デコーダが含まれていないため Android 12 〜 15 (API 31 〜 35) △ ⾼解像度だけ失敗 プラットフォームデコーダが AV1 Level 4.0 の制約を受けるため Android 16 (API 36 〜) ◦ 表⽰できる プラットフォームデコーダが変わったため...? 🤔 14
  11. Q. なぜ Android16 以降で描画できるのか? A. プラットフォームデコーダが刷新されたため Android 12 〜 15[3]

    Android 16[4] 〜 ImageDecoder ImageDecoder SkHeifCodec SkCrabbyAvifCodec HeifDecoderImpl CrabbyAvif MediaMetadataRetriever MediaCodec MediaCodec ・ AVIF 専用のデコーダが含まれていない ・ AVIF 専用のデコーダ (CrabbyAvif) が登録された ・ 結果、AVIF画像は HEIF コーデックでデコードされていた ・ 結果、HEIFコーデックではデコードできなかった解像度の高い 画像もデコード可能になった [3] https://android.googlesource.com/platform/external/skia/+/refs/heads/android15-release/src/codec/SkCodec.cpp#112 [4] https://android.googlesource.com/platform/external/skia/+/refs/heads/android16-release/src/codec/SkCodec.cpp#112 15
  12. 対策:カスタムデコーダを登録する • Coil3 の Decoder インターフェースを実装し、ImageLoader に登録する ◦ ネイティブデコーダを同梱したライブラリを使えば、プラットフォームデコーダに依存しない •

    選択肢は2つ ◦ ◦ io.github.awxkee:avif-coder-coil[5] が提供する AVIF デコーダを登録する org.aomedia.avif.android:avif[6] (AOMedia公式) を元にソフトウェアデコーダを実装する • 副次効果:Android 11 以下でも AVIF が表示できるようになる ◦ プラットフォームデコーダが AVIF を知らなくても、同梱したカスタムデコーダで描画できるため • Coil3 が公式に AVIFデコーダを実装する動きはまだない ◦ ref: https://github.com/coil-kt/coil/issues/2742 [5] https://github.com/awxkee/avif-coder-coil [6] https://github.com/aomediacodec/libavif 16
  13. 結論: AVIFの導入を見送った • avif-coder-coil の導入 ◦ プロジェクトの継続性や導入実績を鑑みて、チームの OSS導入基準に合わなかったため導入を見送った • AOMedia

    公式の AVIFデコーダを参考にしてデコード処理を自作 ◦ ◦ デコード処理を自前で実装するため、デコードロジックの不具合は自チームで修正する必要がある 不具合時のサービスへの影響度に比べデコーダのメンテナンス負担が大きいと判断し、実装を見送った • 現在は WebP で運用中 17
  14. Part2 まとめ • AVIF: WebP/JPEG と比較して圧縮効率が高く画像を軽量化できる規格 • Coil3 で AVIF画像を表示できないケースがある

    ◦ ◦ Android 11 以下: プラットフォームデコーダが対応していない Android 12-15: 高解像度の画像でデコードに失敗する • カスタムデコーダを登録することで解決できる ◦ ◦ プラットフォームデコーダに依存しないため、Android11 以下でもデコード可能 デコーダを登録した場合の影響を考慮して導入の是非を考える必要あり 18
  15. まとめ • 背景:画像のディスク使用量と転送量を抑えたい ◦ ◦ 日経電子版は扱う画像が多く、改善の効果を得られやすい ディスク使用量はキャッシュ構成、転送量は画像フォーマットで対処する • Part1:キャッシュ構成を改善する ◦

    ◦ Coil3 と OkHttp による二重キャッシュを解消し、Coil Disk Cache に一元化した 画像の性質に合わせてキャッシュ戦略を設定する • Part2: Coil3 で AVIF画像を表示する ◦ ◦ プラットフォームデコーダ依存のため、Android15 以下では描画できない場合がある カスタムデコーダにより解決できるが、運用コストを踏まえ AVIF の導入は見送った 19