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

Glideをもっと深くまでカスタマイズしてもっと便利に / Let’s customize Glide to make it more useful

Glideをもっと深くまでカスタマイズしてもっと便利に / Let’s customize Glide to make it more useful

Glideをもっとカスタマイズしてもっと便利に

玉木 英嗣
LINE株式会社 LINEコミュニケーションサービス開発チーム
2018年に新卒として入社。以来、LINEのAndroidアプリのチャット関連機能の新機能開発や内部改善などを担当している。

※以下のイベントの登壇資料です。
https://line.connpass.com/event/227233/

A3966f193f4bef226a0d3e3c1f728d7f?s=128

LINE Developers
PRO

October 28, 2021
Tweet

Transcript

  1. Glideをもっと深くまで カスタマイズしてもっと便利に Tamaki Hidetsugu / @r_ralph_h Software Engineer @ LINE

    Corporation After DroidKaigi # 2021/10/28
  2. 自己紹介 Tamaki Hidetsugu (@r_ralph_h) • LINE Platform Development センター2 LINEコミュニケーションプラットフォーム開発室

    LINEコミュニケーションサービス開発チーム • LINE アプリ(Android)のチャット周り担当 • 2018年 新卒入社
  3. Glide • 著名な画像読み込みライブラリの一つ https://github.com/bumptech/glide • 他にはPicassoやcoilなど

  4. Glide • load関数に読み込む画像のデータ • Into関数に読み込み先のImageView • +α で読み込み時のオプション シンプルな使い方 GlideApp.with(activity)

    .load("https://example.com/image.jpg") .circleCrop() .into(imageView)
  5. GlideとLINEアプリ • 2018年頃から本格的に使用を開始 • 今では様々な画面で使用されている • チャット画面(画像メッセージなど) • プロフィールアイコン •

    グループアイコン • アルバム • タイムライン • etc
  6. GlideとLINEアプリ • 様々なカスタマイズが必要 • 認証ヘッダの付与 • 新しい画像フォーマットのサポート • ダウンロード進捗状況の通知 •

    LRUではない(消えない)ディスクキャッシュ • etc • View側に画像読み込みのための共通ロジックを見せたくない • 認証情報の取得(必要であればリフレッシュ) • URL・ディスクキャッシュのパスの取得 • etc
  7. GlideとLINEアプリ • できる限りもともとのGlideの使用感のまま使えるようにする • 独自のリクエストオブジェクトを渡すだけで色々やってくれる GlideApp.with(activity) .load(ChatImageRequest(chatId, messageId, …)) .circleCrop()

    .into(imageView)
  8. アジェンダ 1. Glideの内部構造の(シンプルな)解説 2. 「LRUではない(消えない)ディスクキャッシュ」の作り方

  9. アジェンダ 1. Glideの内部構造の(シンプルな)解説 2. 「LRUではない(消えない)ディスクキャッシュ」の作り方

  10. Glideの内部構造 Drawableができるまで String Drawable

  11. Glideの内部構造 Drawableができるまで String ModelLoader Drawable →

  12. Glideの内部構造 Drawableができるまで String ModelLoader Drawable → ↓ DataFetcher

  13. Glideの内部構造 Drawableができるまで String ModelLoader Drawable InputStream → ↓ DataFetcher

  14. Glideの内部構造 Drawableができるまで String ModelLoader Drawable InputStream ResourceDecoder → ↓ →

    → DataFetcher
  15. Glideの内部構造 Drawableができるまで String ModelLoader Drawable InputStream ResourceDecoder → ↓ →

    → DataFetcher Model Data Resource
  16. Glideの内部構造 多種多様な形式をサポートするために Model Resource String Drawable ByteArray @DrawableRes Int Uri

    Bitmap GifDrawable
  17. Glideの内部構造 多種多様な形式をサポートするために Model Data Resource String Drawable InputStream ByteArray @DrawableRes

    Int Uri Bitmap @DrawableRes Int GifDrawable
  18. Glideの内部構造 多種多様な形式をサポートするために Model Data Resource String Drawable InputStream ByteArray @DrawableRes

    Int Uri Bitmap @DrawableRes Int GifDrawable ModelLoader ResourceDecoder
  19. Glideの内部構造 多種多様な形式をサポートするために Model Data Resource String Drawable InputStream ByteArray @DrawableRes

    Int Uri Bitmap @DrawableRes Int GifDrawable ModelLoader ResourceDecoder
  20. Glideの内部構造 多種多様な形式をサポートするために Model Data Resource String Drawable InputStream ByteArray @DrawableRes

    Int Uri Bitmap @DrawableRes Int GifDrawable ModelLoader ResourceDecoder
  21. Glideの内部構造 多種多様な形式をサポートするために Model Data Resource String Drawable InputStream ByteArray @DrawableRes

    Int Uri Bitmap @DrawableRes Int GifDrawable ModelLoader ResourceDecoder
  22. アジェンダ 1. Glideの内部構造の(シンプルな)解説 2. 「LRUではない(消えない)ディスクキャッシュ」の作り方

  23. 消えないディスクキャッシュの作り方 ModelLoaderの仕組みを活用する CustomRequest RemoteModelLoader InputStream → RemoteFetcher Model Data ファイルが無い時

    LocalModelLoader LocalFetcher ↑ ファイルがある時 ダウンロードした上で ファイルに書き込む
  24. LocalModelLoader LocalFetcher ↑ ファイルがある時 消えないディスクキャッシュの作り方 ModelLoaderの仕組みを活用する CustomRequest RemoteModelLoader InputStream →

    RemoteFetcher Model Data ファイルが無い時 ダウンロードした上で ファイルに書き込む
  25. ファイルが無い時 RemoteFetcher RemoteModelLoader → ダウンロードした上で ファイルに書き込む 消えないディスクキャッシュの作り方 ModelLoaderの仕組みを活用する CustomRequest InputStream

    Model Data LocalModelLoader LocalFetcher ↑ ファイルがある時
  26. 消えないディスクキャッシュの作り方 • 全体のコードは以下のリポジトリで紹介 https://github.com/r-ralph/glide-sample-after-droidkaigi

  27. LocalModelLoader LocalFetcher ↑ ファイルがある時 消えないディスクキャッシュの作り方 CustomRequest RemoteModelLoader InputStream → RemoteFetcher

    Model Data ファイルが無い時 ダウンロードした上で ファイルに書き込む
  28. 消えないディスクキャッシュの作り方 • URLとファイルパスを生成可能なデータを持つ CustomRequest data class CustomRequest( val id: Long

    )
  29. 消えないディスクキャッシュの作り方 • 型パラメータでDataを表す • 1リクエスト毎に1インスタンスが作られる RemoteFetcher class RemoteFetcher( private val

    url: String, private val file: File ) : DataFetcher<InputStream> { override fun loadData(…) { … } }
  30. 消えないディスクキャッシュの作り方 • 取得に成功したらcallbackにInputStreamを渡す • 必ず、一時ファイルに書き込んでから移動する(1敗) RemoteFetcher#loadData override fun loadData(…, callback:

    DataCallback<InputStream>) { val bodyInputStream = callRequest(url) // HTTPリクエストをする val tempFile = …… // 一旦、一時ファイルに書き込む bodyInputStream.copyTo(tempFile) tempFile.renameTo(file) callback.onDataReady(file.inputStream()) }
  31. 消えないディスクキャッシュの作り方 • 型パラメータでModelとDataを表す • handles関数では、このModelLoaderで処理可能かを返す RemoteModelLoader class RemoteModelLoader : ModelLoader<CustomRequest,

    InputStream> { override fun buildLoadData( model: CustomRequest, … ): LoadData<InputStream> override fun handles(model: CustomRequest): Boolean = true }
  32. 消えないディスクキャッシュの作り方 • buildLoadData関数でURLと保存先のファイルを計算する RemoteModelLoader#buildLoadData override fun buildLoadData( model: CustomRequest, …

    ): LoadData<InputStream> { val fetcher = RemoteFetcher(createUrl(model), createFile(model)) return LoadData(ObjectKey(model), fetcher) }
  33. ファイルが無い時 RemoteFetcher RemoteModelLoader → ダウンロードした上で ファイルに書き込む 消えないディスクキャッシュの作り方 CustomRequest InputStream Model

    Data LocalModelLoader LocalFetcher ↑ ファイルがある時
  34. 消えないディスクキャッシュの作り方 • ダウンロード処理は無いので、ファイルだけ受け取る LocalFetcher class LocalFetcher( private val file: File

    ) : DataFetcher<InputStream> { override fun loadData(…) { … } }
  35. 消えないディスクキャッシュの作り方 • loadDataの中身もシンプル LocalFetcher#loadData override fun loadData(…, callback: DataCallback<InputStream>) {

    callback.onDataReady(file.inputStream()) }
  36. 消えないディスクキャッシュの作り方 • ローカルファイルがないときは処理をしてはいけない LocalModelLoader class LocalModelLoader : ModelLoader<CustomRequest, InputStream> {

    override fun buildLoadData( model: CustomRequest, … ): LoadData<InputStream> override fun handles(model: CustomRequest): Boolean = … }
  37. 消えないディスクキャッシュの作り方 • handles関数でファイルの存在を確認する必要がある LocalModelLoader#handles override fun handles(model: CustomRequest): Boolean {

    return createFile(model).exists() }
  38. 消えないディスクキャッシュの作り方 • 同様にFetcherを生成する LocalModelLoader#buildLoadData override fun buildLoadData( model: CustomRequest, …

    ): LoadData<InputStream> { val fetcher = LocalFetcher(createFile(model)) return LoadData(ObjectKey(model), fetcher) }
  39. 消えないディスクキャッシュの作り方 • Glideを使うときに作るアノテーション付きのクラスを使う • registryに対してModelLoaderを登録する Glideに登録する @GlideModule class MyAppGlideModule :

    AppGlideModule() { override fun registerComponents( context: Context, glide: Glide, registry: Registry ) { … } }
  40. 消えないディスクキャッシュの作り方 • prepend関数を使用してModelLoader(のFactory)を登録する • Factoryの実装についてはサンプルリポジトリにて Glideに登録する registry.prepend( CustomRequest::class.java, InputStream::class.java, RemoteModelLoader.Factory()

    ) registry.prepend( CustomRequest::class.java, InputStream::class.java, LocalModelLoader.Factory() )
  41. 消えないディスクキャッシュの作り方 • 必ずLocalが優先されるようにRegistryに登録する • prepend関数は優先順位1位に挿入する関数 • なので、Remote→Localの順番で登録する Glideに登録する registry.prepend(…, RemoteModelLoader.Factory())

    registry.prepend(…, LocalModelLoader.Factory())
  42. 消えないディスクキャッシュの作り方 • あとは、CustomRequestを使用していつもどおり読み込むだけ • ただし、独自のディスクキャッシュを作ったのでGlideのは無効化した ほうが良い GlideApp.with(activity) .load(CustomRequest(model.id)) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(imageView)

  43. GlideとLINEアプリ • 様々なカスタマイズが必要 • 認証ヘッダの付与 • 新しい画像フォーマットのサポート • ダウンロード進捗状況の通知 •

    LRUではない(消えない)ディスクキャッシュ • etc • View側に画像読み込みのための共通ロジックを見せたくない • 認証情報の取得(必要であればリフレッシュ) • URL・ディスクキャッシュのパスの取得 • etc
  44. GlideとLINEアプリ • 様々なカスタマイズが必要 • 認証ヘッダの付与 • 新しい画像フォーマットのサポート • ダウンロード進捗状況の通知 •

    LRUではない(消えない)ディスクキャッシュ → ModelLoaderを使う • etc • View側に画像読み込みのための共通ロジックを見せたくない • 認証情報の取得(必要であればリフレッシュ) • URL・ディスクキャッシュのパスの取得 • etc
  45. GlideとLINEアプリ • 様々なカスタマイズが必要 • 認証ヘッダの付与 → RemoteFetcherを更にカスタマイズ • 新しい画像フォーマットのサポート →

    ResourceDecoderを作る • ダウンロード進捗状況の通知 → RemoteFetcherを更にカスタマイズ • LRUではない(消えない)ディスクキャッシュ → ModelLoaderを使う • etc • View側に画像読み込みのための共通ロジックを見せたくない • 認証情報の取得(必要であればリフレッシュ) → RemoteFetcherに隠せる • URL・ディスクキャッシュのパスの取得 → ModelLoaderに隠せる • etc
  46. まとめ • Glideの内部構造はいくつかの層に分かれている • 読み込みまでの挙動は自由にカスタマイズできる • 独自のリクエストクラスを使う • 独自の追加ロジックを入れる

  47. LINEアプリ iOS/Android エンジニア採用説明会 2021/11/17(水) 19:00~21:00 https://line.connpass.com/event/228970/