Slide 1

Slide 1 text

Glideをもっと深くまで カスタマイズしてもっと便利に Tamaki Hidetsugu / @r_ralph_h Software Engineer @ LINE Corporation After DroidKaigi # 2021/10/28

Slide 2

Slide 2 text

自己紹介 Tamaki Hidetsugu (@r_ralph_h) • LINE Platform Development センター2 LINEコミュニケーションプラットフォーム開発室 LINEコミュニケーションサービス開発チーム • LINE アプリ(Android)のチャット周り担当 • 2018年 新卒入社

Slide 3

Slide 3 text

Glide • 著名な画像読み込みライブラリの一つ https://github.com/bumptech/glide • 他にはPicassoやcoilなど

Slide 4

Slide 4 text

Glide • load関数に読み込む画像のデータ • Into関数に読み込み先のImageView • +α で読み込み時のオプション シンプルな使い方 GlideApp.with(activity) .load("https://example.com/image.jpg") .circleCrop() .into(imageView)

Slide 5

Slide 5 text

GlideとLINEアプリ • 2018年頃から本格的に使用を開始 • 今では様々な画面で使用されている • チャット画面(画像メッセージなど) • プロフィールアイコン • グループアイコン • アルバム • タイムライン • etc

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

GlideとLINEアプリ • できる限りもともとのGlideの使用感のまま使えるようにする • 独自のリクエストオブジェクトを渡すだけで色々やってくれる GlideApp.with(activity) .load(ChatImageRequest(chatId, messageId, …)) .circleCrop() .into(imageView)

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Glideの内部構造 Drawableができるまで String ModelLoader Drawable InputStream ResourceDecoder → ↓ → → DataFetcher Model Data Resource

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

消えないディスクキャッシュの作り方 • 全体のコードは以下のリポジトリで紹介 https://github.com/r-ralph/glide-sample-after-droidkaigi

Slide 27

Slide 27 text

LocalModelLoader LocalFetcher ↑ ファイルがある時 消えないディスクキャッシュの作り方 CustomRequest RemoteModelLoader InputStream → RemoteFetcher Model Data ファイルが無い時 ダウンロードした上で ファイルに書き込む

Slide 28

Slide 28 text

消えないディスクキャッシュの作り方 • URLとファイルパスを生成可能なデータを持つ CustomRequest data class CustomRequest( val id: Long )

Slide 29

Slide 29 text

消えないディスクキャッシュの作り方 • 型パラメータでDataを表す • 1リクエスト毎に1インスタンスが作られる RemoteFetcher class RemoteFetcher( private val url: String, private val file: File ) : DataFetcher { override fun loadData(…) { … } }

Slide 30

Slide 30 text

消えないディスクキャッシュの作り方 • 取得に成功したらcallbackにInputStreamを渡す • 必ず、一時ファイルに書き込んでから移動する(1敗) RemoteFetcher#loadData override fun loadData(…, callback: DataCallback) { val bodyInputStream = callRequest(url) // HTTPリクエストをする val tempFile = …… // 一旦、一時ファイルに書き込む bodyInputStream.copyTo(tempFile) tempFile.renameTo(file) callback.onDataReady(file.inputStream()) }

Slide 31

Slide 31 text

消えないディスクキャッシュの作り方 • 型パラメータでModelとDataを表す • handles関数では、このModelLoaderで処理可能かを返す RemoteModelLoader class RemoteModelLoader : ModelLoader { override fun buildLoadData( model: CustomRequest, … ): LoadData override fun handles(model: CustomRequest): Boolean = true }

Slide 32

Slide 32 text

消えないディスクキャッシュの作り方 • buildLoadData関数でURLと保存先のファイルを計算する RemoteModelLoader#buildLoadData override fun buildLoadData( model: CustomRequest, … ): LoadData { val fetcher = RemoteFetcher(createUrl(model), createFile(model)) return LoadData(ObjectKey(model), fetcher) }

Slide 33

Slide 33 text

ファイルが無い時 RemoteFetcher RemoteModelLoader → ダウンロードした上で ファイルに書き込む 消えないディスクキャッシュの作り方 CustomRequest InputStream Model Data LocalModelLoader LocalFetcher ↑ ファイルがある時

Slide 34

Slide 34 text

消えないディスクキャッシュの作り方 • ダウンロード処理は無いので、ファイルだけ受け取る LocalFetcher class LocalFetcher( private val file: File ) : DataFetcher { override fun loadData(…) { … } }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

消えないディスクキャッシュの作り方 • ローカルファイルがないときは処理をしてはいけない LocalModelLoader class LocalModelLoader : ModelLoader { override fun buildLoadData( model: CustomRequest, … ): LoadData override fun handles(model: CustomRequest): Boolean = … }

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

消えないディスクキャッシュの作り方 • Glideを使うときに作るアノテーション付きのクラスを使う • registryに対してModelLoaderを登録する Glideに登録する @GlideModule class MyAppGlideModule : AppGlideModule() { override fun registerComponents( context: Context, glide: Glide, registry: Registry ) { … } }

Slide 40

Slide 40 text

消えないディスクキャッシュの作り方 • 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() )

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

まとめ • Glideの内部構造はいくつかの層に分かれている • 読み込みまでの挙動は自由にカスタマイズできる • 独自のリクエストクラスを使う • 独自の追加ロジックを入れる

Slide 47

Slide 47 text

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