はじめまして、アプリ開発グループの大場です。 2021年10月に中途入社し、ニコニコ漫画と読書メーターのAndroidアプリを主に担当しています。
ニコニコ漫画では以前より画像ライブラリにPicasso、Glideの二種を並行利用していましたが、先日Glideに完全移行しました。今回はGlide移行時において理解に時間のかかった部分である、独自形式の画像読み込みについて記載していきます。
これからGlideについてキャッチアップする方の参考になれば幸いです。
なお、記事中のライブラリ、言語のバージョンは以下の通りです。
ライブラリ名 | バージョン |
---|---|
Kotlin | 1.6.10 |
Picasso | 2.71828 |
Glide | 4.12.0 |
目次:
Picassoでの実装例
ニコニコ漫画ではセキュリティ上の観点から、漫画の画像(いわゆる原稿)にはDRMが付与されています。DRM画像は暗号化されているため、画像ライブラリの標準読み込み関数でそのまま表示することは出来ません。その対処としてアプリではPicassoのDownloader
インターフェースを実装することで、DRM画像の復号化を行っていました。
Picassoを利用した実装例は以下です。
class DecryptDownloader(context: Context) : Downloader { private val downloader: OkHttp3Downloader = OkHttp3Downloader(context) override fun load(request: Request): Response { val response = downloader.load(request) return decryptDrm(response) } fun decryptDrm(response: Response): Response { /* 画像の復号化 */ } override fun shutdown() { downloader.shutdown() } } // このように使う Picasso.Builder(context) .downloader(DecryptDownloader(context)) .build() .load(/* Image Url */) .into(/* ImageView */))
前述の通りPicassoではDownloader
インターフェースの実装を行うことによって、標準の読み込み処理の実装を差し替えることができます。
Glideではこの仕組みがModel
, ModelLoader
, DataFetcher
インターフェースによってより細かく設定できるようになっています。
Picassoでの実装をGlideに置き換えた例を見ていきましょう。
Glideでの実装例
1. Modelの定義
サーバー上の画像ファイルを特定のアルゴリズムで復号するためのリクエストとしてDecryptRequest
を定義します。 ここで定義するModel
の型はなんでもよいのですが、String
だと味気ないので今回は独自にDecryptRequest
という名前で定義しています。
data class DecryptRequest(val url: GlideUrl)
2.DataFetcherの実装
GlideではDataFetcher
というインターフェースを実装することで、どのようにデータを取得して、どの型で返すかを決めることが出来ます。Glideの標準画像デコーダーはInputStream
またはByteBuffer
に対応しているため、今回はInputStream
を最終的なデータ型として返すようにしました。
class DrmImageFetcher(private val decryptRequest: DecryptRequest) : DataFetcher<InputStream> { private val urlFetcher: HttpUrlFetcher = HttpUrlFetcher(decryptRequest.url, 5000) override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) { urlFetcher.loadData(priority, object : DataFetcher.DataCallback<InputStream> { override fun onDataReady(data: InputStream?) { callback.onDataReady(decrypt(data)) } override fun onLoadFailed(e: Exception) { callback.onLoadFailed(e) } }) } fun decrypt(rawData: InputStream?): InputStream? { // 実際の復号化処理を記述します。今回はそのまま返します。 return rawData } override fun cleanup() { urlFetcher.cleanup() } override fun cancel() { urlFetcher.cancel() } override fun getDataClass(): Class<InputStream> { return InputStream::class.java } override fun getDataSource(): DataSource { return DataSource.REMOTE } }
3.ModelLoaderの実装
作成したModel
とDataFetcher
を紐付けるためのModelLoader
インターフェースを実装します。コード中のhandles
関数は今回DecryptRequest
とDrmImageFetcher
が必ず1:1で紐付いているので 強制的にtrue
を返しています。もし1:nの関係になる場合はhandles
関数で条件分岐を行いましょう。
class DrmImageLoader : ModelLoader<DecryptRequest, InputStream> { override fun buildLoadData( model: DecryptRequest, width: Int, height: Int, options: Options ): LoadData<InputStream> { return LoadData( model.url, DrmImageFetcher(model) ) } override fun handles(model: DecryptRequest): Boolean { // 今回は必ずDecryptRequestとDrmImageFetcherが紐づくので強制的にtrueを返す return true } class Factory : ModelLoaderFactory<DecryptRequest, InputStream> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<DecryptRequest, InputStream> = DrmImageLoader() override fun teardown() = Unit } }
4.Glide Moduleへの登録
最後にGlide ModuleへModelLoader
を登録します。
@GlideModule public final class AppGlideModule: AppGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { registry.prepend(DecryptRequest::class.java, InputStream::class.java, DrmImageLoader.Factory()) } }
使用例
GlideModuleを利用してDRM画像を読み込む実装例です。Picassoでの実装例と同等のコードになったことがわかるかと思います。
GlideApp .with(this) .load(DecryptRequest(GlideUrl(/* 画像URL */))) .into(/* Target ImageView */)
まとめ
Glideを使って独自形式の画像読み込みに対応する実装例を紹介しました。
その他にもGlideにはいろいろな機能がありますので、興味のある方はドキュメントを調べてみてください。
これからもよりよいユーザー体験を目指して開発面でも様々な改善を行っていきますので、どうぞよろしくお願いいたします。
- 参考文献
- Glide v4 : Fast and efficient image loading for Android