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

Uncoiling the COIL Vol 2 - Droidcon Italy

Uncoiling the COIL Vol 2 - Droidcon Italy

Image loading is hard but luckily this problem has already been addressed on Android. There are many libraries out there that handle image loading seamlessly. COIL is the new kid to the club and you may be wondering why do we need another image loading library?

COIL is Kotlin first library backed by Kotlin Coroutines. It gives you an easy and concise API to deal with image loading. Oh and also it takes less space! In a nutshell, it's an easy to use, fast, lightweight, and modern library.

This talk will address
- Why you should consider this library for image loading?
- Understanding its API.
- How it works? by covering its entire image loading pipeline.

By the end, you'll walk away with the advantages of using COIL and its image loading pipeline.

Sagar Viradiya

November 11, 2021
Tweet

More Decks by Sagar Viradiya

Other Decks in Programming

Transcript

  1. Agenda • What is COIL? • Why COIL? • How

    COIL? • Image loading pipeline • Caching
  2. What is COIL? • COroutine Image Loader • An image

    loading library for android (Another? 🤔)
  3. API val imageLoader = context.imageLoader OR val imageLoader = ImageLoader.Builder(context)

    .availableMemoryPercentage(0.25) .crossfade(true) .build()
  4. Imperative style // Coroutine scope val request = ImageRequest.Builder(context) .data("https://www.example.com/image.jpg")

    .build() val result = imageLoader.execute(request) // Suspends the execution val drawable = result.drawable
  5. Jetpack Compose Support Image( painter = rememberImagePainter( data = "https://www.example.com/image.jpg",

    builder = { transformations(CircleCropTransformation()) crossfade(true) } ), contentDescription = null, modifier = Modifier.size(128.dp) )
  6. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult ErrorResult data: T data: T data: M FetchResult DecodeResult
  7. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult data: T data: T data: M FetchResult DecodeResult ErrorResult
  8. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult data: T data: T data: M FetchResult DecodeResult ErrorResult
  9. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult data: T data: T data: M FetchResult DecodeResult ErrorResult
  10. ImageLoader • A service class responsible for executing and managing

    entire pipeline. • Takes ImageRequest. • COIL provides singleton (Recommended)
  11. Interceptor • Allows you to observe, transform, short circuit or

    retry request • You can wrap the pipeline with your custom logic
  12. Interceptor • Allows you to observe, transform, short circuit or

    retry request • You can wrap the pipeline with your custom logic • Inspired from OKHttp’s interceptor
  13. class CustomCacheInterceptor( private val context: Context, private val cache: LruCache<String,

    Drawable> ) : Interceptor { override suspend fun intercept(chain: Interceptor.Chain): ImageResult { val value = cache.get(chain.request.data.toString()) if (value != null) { return SuccessResult( drawable = value.bitmap.toDrawable(context), request = chain.request, metadata = TODO() ) } return chain.proceed(chain.request) } }
  14. class CustomCacheInterceptor( private val context: Context, private val cache: LruCache<String,

    Drawable> ) : Interceptor { override suspend fun intercept(chain: Interceptor.Chain): ImageResult { val value = cache.get(chain.request.data.toString()) if (value != null) { return SuccessResult( drawable = value.bitmap.toDrawable(context), request = chain.request, metadata = TODO() ) } return chain.proceed(chain.request) } }
  15. class CustomCacheInterceptor( private val context: Context, private val cache: LruCache<String,

    Drawable> ) : Interceptor { override suspend fun intercept(chain: Interceptor.Chain): ImageResult { val value = cache.get(chain.request.data.toString()) if (value != null) { return SuccessResult( drawable = value.bitmap.toDrawable(context), request = chain.request, metadata = TODO() ) } return chain.proceed(chain.request) } }
  16. class CustomCacheInterceptor( private val context: Context, private val cache: LruCache<String,

    Drawable> ) : Interceptor { override suspend fun intercept(chain: Interceptor.Chain): ImageResult { val value = cache.get(chain.request.data.toString()) if (value != null) { return SuccessResult( drawable = value.bitmap.toDrawable(context), request = chain.request, metadata = TODO() ) } return chain.proceed(chain.request) } }
  17. class CustomCacheInterceptor( private val context: Context, private val cache: LruCache<String,

    Drawable> ) : Interceptor { override suspend fun intercept(chain: Interceptor.Chain): ImageResult { val value = cache.get(chain.request.data.toString()) if (value != null) { return SuccessResult( drawable = value.bitmap.toDrawable(context), request = chain.request, metadata = TODO() ) } return chain.proceed(chain.request) } }
  18. Mapper - Example data class Item( val id: Int, val

    imageUrl: String, val price: Int, val weight: Double ) class ItemMapper : Mapper<Item, String> { override fun map(data: Item, options: Options) = data.imageUrl }
  19. Mapper - Example data class Item( val id: Int, val

    imageUrl: String, val price: Int, val weight: Double ) class ItemMapper : Mapper<Item, String> { override fun map(data: Item, options: Options) = data.imageUrl }
  20. Fetcher • Translates data into either ImageSource or Drawable •

    Built-in fetchers ◦ AssetUriFetcher ◦ ContentUriFetcher ◦ FileFetcher ◦ HttpFetcher
  21. Custom Fetcher interface Fetcher<T : Any> { suspend fun fetch():

    FetchResult? fun interface Factory<T : Any> { fun create(data: T, options: Options, imageLoader: ImageLoader): Fetcher? } }
  22. Decoder • Converts ImageSource to Drawable • Built-in format support

    ◦ JPEG ◦ PNG ◦ WebPs • Extensions ◦ SVG ◦ GIFs/HEIFs ◦ Video frame
  23. Custom Decoder interface Decoder { suspend fun decode(): DecodeResult? fun

    interface Factory { fun create( result: SourceResult, options: Options, imageLoader: ImageLoader ): Decoder? } }
  24. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult ErrorResult data: T data: T data: M FetchResult DecodeResult
  25. Transformation • Allows you to change the pixel data. •

    Built-in transformations ◦ Circle crop ◦ Rounded corner
  26. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult ErrorResult data: T data: T data: M FetchResult DecodeResult
  27. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult ErrorResult data: T data: T data: M FetchResult DecodeResult
  28. The pipeline ImageLoader Interceptor ImageRequest Mapper Fetcher Decoder Transformation ImageResult

    SuccessResult ErrorResult data: T data: T data: M FetchResult DecodeResult
  29. Thread Interceptor Mapper Fetcher Decoder Transformation Transition Main Thread Default

    - I/O Thread OR Based on custom dispatchers Main Thread
  30. Disk cache • Coil 2.0 has its own disk cache

    • Prior versions relies on OkHttp disk cache
  31. HttpUriFetcher URL Disk Cache Snapshot? Snapshot != null Pass result

    to decoder No Yes Fetch image from source N/W
  32. HttpUriFetcher URL Disk Cache Snapshot? Snapshot != null Pass result

    to decoder No Yes Fetch image from source N/W Image data
  33. HttpUriFetcher URL Disk Cache Snapshot? Snapshot != null Pass result

    to decoder No Yes Fetch image from source N/W Write Image data
  34. HttpUriFetcher URL Disk Cache Snapshot? Snapshot != null Pass result

    to decoder No Yes Fetch image from source N/W Write Image data Read
  35. HttpUriFetcher URL Disk Cache Snapshot? Snapshot != null Pass result

    to decoder No Yes Fetch image from source N/W Write Image data Pass result to decoder Read
  36. HttpUriFetcher URL Disk Cache Snapshot? Snapshot != null Pass result

    to decoder No Yes Fetch image from source N/W Write Image data Pass result to decoder Read
  37. Disk Cache interface DiskCache { operator fun get(key: String): Snapshot?

    fun edit(key: String): Editor? fun remove(key: String): Boolean fun clear() interface Snapshot : Closeable interface Editor }
  38. Disk Cache interface DiskCache { operator fun get(key: String): Snapshot?

    fun edit(key: String): Editor? fun remove(key: String): Boolean fun clear() interface Snapshot : Closeable interface Editor }
  39. Disk Cache interface DiskCache { operator fun get(key: String): Snapshot?

    fun edit(key: String): Editor? fun remove(key: String): Boolean fun clear() interface Snapshot : Closeable interface Editor }
  40. Disk Cache interface DiskCache { operator fun get(key: String): Snapshot?

    fun edit(key: String): Editor? fun remove(key: String): Boolean fun clear() interface Snapshot : Closeable interface Editor }
  41. Disk Cache interface DiskCache { operator fun get(key: String): Snapshot?

    fun edit(key: String): Editor? fun remove(key: String): Boolean fun clear() interface Snapshot : Closeable interface Editor }
  42. Snapshot • Result of the cache read operation • Encapsulates

    cache entry • Gives access to the cache file object
  43. LinkedHashMap<String, Entry> • In memory representation of a disk cache

    • Entry is an object holding cache entry files ◦ Clean file ◦ Dirty file
  44. LinkedHashMap<String, Entry> • In memory representation of a disk cache

    • Entry is an object holding cache entry files ◦ Clean file ◦ Dirty file • Keeps track of cache entry status
  45. libcore.io.DiskLruCache 1 100 2 CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY 335c4c6028171cfddfbaae1a9c313c52

    CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 Journal file
  46. libcore.io.DiskLruCache 1 100 2 CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY 335c4c6028171cfddfbaae1a9c313c52

    CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 Journal file
  47. libcore.io.DiskLruCache 1 100 2 CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY 335c4c6028171cfddfbaae1a9c313c52

    CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 Journal file
  48. libcore.io.DiskLruCache 1 100 2 CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY 335c4c6028171cfddfbaae1a9c313c52

    CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 Journal file
  49. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Cache read flow
  50. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Key Cache read flow
  51. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Entry? Key Cache read flow
  52. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Entry? Key Cache read flow
  53. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Entry? Journal cleanup req? Key Cache read flow
  54. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Entry? Journal cleanup req? No Return Snapshot? Key Cache read flow
  55. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Entry? Journal cleanup req? Yes Run cleanup task No Return Snapshot? Key Cache read flow
  56. Key Get Initialised ? Yes No Journal File Initialisation Get

    the Entry from cache LinkedHashMap <String, Entry> Entry? Journal cleanup req? Yes Run cleanup task No Return Snapshot? Key Cache read flow
  57. Initialised ? Yes No Journal File Initialisation Copy dirty file

    to clean file LinkedHashMap <String, Entry> Commit Editor Cache write flow
  58. Initialised ? Yes No Journal File Initialisation Copy dirty file

    to clean file LinkedHashMap <String, Entry> Commit Editor Cache write flow Key
  59. Initialised ? Yes No Journal File Initialisation Copy dirty file

    to clean file LinkedHashMap <String, Entry> Commit Editor Cache write flow Key Entry
  60. Initialised ? Yes No Journal File Initialisation LinkedHashMap <String, Entry>

    Copy dirty file to clean file Commit Editor Cache write flow Key Entry
  61. Cache full? Initialised ? Yes No Journal File Initialisation LinkedHashMap

    <String, Entry> Copy dirty file to clean file Commit Editor Cache write flow Key Entry
  62. Cache full? Initialised ? Yes No Journal File Initialisation LinkedHashMap

    <String, Entry> Copy dirty file to clean file Commit Editor Yes Run cleanup task Cache write flow Key Entry
  63. Engine Interceptor Is in strong memory? Key Yes Return Bitmap

    and short circuit image loading pipeline
  64. Engine Interceptor Is in strong memory? Key Yes Return Bitmap

    and short circuit image loading pipeline No Is in weak memory?
  65. Engine Interceptor Is in strong memory? Key Yes Return Bitmap

    and short circuit image loading pipeline No Is in weak memory? Yes Return Bitmap and short circuit image loading pipeline
  66. Engine Interceptor Is in strong memory? Key Yes Return Bitmap

    and short circuit image loading pipeline No Is in weak memory? No Execute Image loading pipeline Yes Return Bitmap and short circuit image loading pipeline
  67. Strong Memory Cache • Backed by LRU cache • Size

    of the cache is computed dynamically
  68. Weak Memory Cache • Backed by HashMap • Periodically do

    cleanups after 10 read or write operations
  69. Summary • COIL - Coroutine Image Loader • Modern, Easy,

    Lightweight and Fast • API • Image loading pipeline • Caching