Detailed explanation of Android's official Kotlin-First image loading library

Detailed explanation of Android's official Kotlin-First image loading library

Preface

Coil is a very young image loading library. It only released version 1.0.0 on October 22, 2020, but it has been promoted by Android officials. We talked about it in a blog post called Android Developers Backstage. The reason for the promotion is simple: on the one hand, this library is really well made, and on the other hand, this library is written entirely in Kotlin and uses a lot of Kotlin features, especially coroutines. So Google says it will not give up Java, but in fact we all know it.

Coil's name comes from the first letters of Coroutine Image Loader. It can be seen that images are loaded through Kotlin coroutines. The features are as follows:

  • Faster: Coil has many performance optimizations, including memory caching and disk caching, saving thumbnails in memory, recycling Bitmaps through BitmapPool, automatically pausing and canceling network requests, etc.
  • More lightweight: Coil has only 2,000 methods, which is about the same as Picasso. It is much lighter than Glide and Fresco.
  • Easier to use: Coil's API makes full use of Kotlin's new features and has rich extension functions, which simplifies and reduces a lot of boilerplate code.
  • More popular: Coil is developed in Kotlin and uses many popular open source libraries including Coroutines, okhttp, okio and AndroidX Lifecycles

From the features of Coil, we can see that it is a very suitable image loading library for personal apps, especially apps developed in pure Kotlin. Moreover, Coil uses a lot of new features and coroutines of Kotlin, which is of great value for us to learn Kotlin. Compared with glide and fresco, which have very complex structures and amazing amounts of code, Coil has only about 2,000 methods, so it is also very suitable for source code research and learning.

Basic Use

Coil can be downloaded from mavenCentral():

  1. implementation( "io.coil-kt:coil:1.1.1" )

Coil adds a lot of extension functions to ImageView, so we can load images with just one line of code:

  1. // URL
  2. imageView.load ( "https://www.example.com/image.jpg" )
  3.  
  4. // Resource
  5. imageView. load (R. drawable. image)
  6.  
  7. // File
  8. imageView.load (File( "/path/to/image.jpg" ))

At the same time, we can also use lambda syntax to easily configure image loading:

  1. imageView.load ( "https://www.example.com/image.jpg" ) {
  2. crossfade( true )
  3. placeholder(R.drawable.image)
  4. transformations(CircleCropTransformation())
  5. }

Commonly used APIs

ImageLoader

ImageLoader is the steward of image loading in Coil, responsible for handling cache, data acquisition, image decoding, request management, Bitmap cache pool, memory management, etc. It is generally recommended to create only one ImageLoader and share it in the App, so that the performance is optimal. This is because each ImageLoader has its own memory cache and Bitmap cache pool.

We can create and configure ImageLoader through the constructor.

  1. val imageLoader = ImageLoader.Builder(context)
  2. .availableMemoryPercentage(0.25)
  3. .crossfade( true )
  4. .build()

At the same time, since ImageLoader is an interface, it means that we can test it very conveniently. For example, we can inject a fake ImageLoader to return the same drawable every time.

  1. val fakeImageLoader = object : ImageLoader {
  2.  
  3. private val drawable = ColorDrawable(Color.BLACK)
  4.  
  5. override fun enqueue(request: ImageRequest): Disposable {
  6. request.target?.onStart(drawable)
  7. request.target?.onSuccess(drawable)
  8. return disposable
  9. }
  10.  
  11. override suspend fun execute (request: ImageRequest): ImageResult {
  12. return SuccessResult(
  13. drawable = drawable, request = request,
  14. metadata = ImageResult.Metadata(
  15. memoryCacheKey = MemoryCache.Key ( "" ),
  16. isSampled = false ,
  17. dataSource = DataSource.MEMORY_CACHE,
  18. isPlaceholderMemoryCacheKeyPresent = false  
  19. )
  20. )
  21. }
  22. }

ImageRequest

ImageRequest provides all the necessary information for ImageLoader to load images, and we can also use custom Target for processing.

  1. val request = ImageRequest.Builder(context)
  2. .data( "https://www.example.com/image.jpg" )
  3. .target { drawable ->
  4. // Handle the result.
  5. }
  6. .build()
  7. context.imageLoader.enqueue(request)

ImageRequest is created based on the Builder mode, which includes various configuration items for loading images. Here we focus on the most commonly used configuration items.

Disposable

Disposable is the return value after calling the load() method, which is mainly used to cancel image loading:

  1. interface Disposable {
  2.  
  3. /**
  4. * Returns true if the image loading request has been completed or canceled  
  5. */
  6. val isDisposed: Boolean
  7.  
  8. /**
  9. * Cancel the ongoing image loading request and release related resources, and this method is idempotent
  10. */
  11. fun dispose()
  12.  
  13. /**
  14. * Non-blocking wait for task completion
  15. */
  16. @ExperimentalCoilApi
  17. suspend fun await()
  18. }

Image Transformation

Image transformation is a very common function in image loading libraries. Coil abstracts it into a Transformation interface. You can see that there is a BitmapPool parameter in the transform() method. This is because a Bitmap is often required when implementing graphic transformation. In this case, it can be directly obtained from the BitmapPool, thereby reusing the existing Bitmap.

  1. interface Transformation {
  2. fun key (): String
  3. suspend fun transform(pool: BitmapPool, input: Bitmap, size : Size ): Bitmap
  4. }
  5.  
  6. imageView.load ( "https://www.example.com/image.jpg" ) {
  7. transformations(CircleCropTransformation())
  8. }

Coil mainly provides the following image transformation effects:

Functional expansion

Coil not only provides many necessary functions, but also reserves many extension points for developers to customize. Coil's image loading mainly includes four main modules:

Interceptors

Coil's Interceptor undoubtedly draws on the design ideas of okhttp, which greatly facilitates subsequent function expansion. For example, we can add a custom cache layer to Coil:

  1. class CustomCacheInterceptor(
  2. private val context: Context,
  3. private val cache: LruCache<String, Drawable>
  4. ) : Interceptor {
  5.  
  6. override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
  7. val value = cache.get(chain.request.data.toString())
  8. if (value != null ) {
  9. return SuccessResult(
  10. drawable = value.bitmap.toDrawable(context),
  11. request = chain.request,
  12. metadata = TODO()
  13. )
  14. }
  15. return chain.proceed(chain.request)
  16. }
  17. }

Mappers and Fetchers

When calling load() externally, the passed String parameter may point to a local resource file or a network image. Mappers and Fetchers can be used together to distinguish resource types. For example:

  1. imageView.load ( "android.resource://example.package.name/drawable/image " )
  2. imageView.load ( "https://www.example.com/image.jpg" )

StringMapper will convert the incoming String into the corresponding Uri.

  1. internal class StringMapper : Mapper<String, Uri> {
  2. override fun map(data: String) = data.toUri()
  3. }

ResourceUriFetcher will determine whether the scheme type of Uri is android.resource. If it is, it represents a local resource file, while HttpUriFetcher will determine whether the scheme of Uri is http or https. If it is, it represents a network image.

  1. internal class HttpUriFetcher(callFactory: Call.Factory) : HttpFetcher<Uri>(callFactory) {
  2. override fun handles(data: Uri) = data.scheme == "http" || data.scheme == "https"  
  3. override fun key (data: Uri) = data.toString()
  4. override fun Uri.toHttpUrl(): HttpUrl = HttpUrl.get(toString())
  5. }

Decoders

Android supports many image formats, but there are also many formats that it does not support (for example: Gif, SVG, video frames, etc.), so Coil provides a corresponding extension library.

① Gif (GifDecoder supports all API levels, but is slower, ImageDecoderDecoder loads faster, but is only available on API 28 and higher)

  1. implementation( "io.coil-kt:coil-gif:1.1.1" )
  2.  
  3. val imageLoader = ImageLoader.Builder(context)
  4. .componentRegistry {
  5. if (SDK_INT >= 28) {
  6. add (ImageDecoderDecoder())
  7. } else {
  8. add (GifDecoder())
  9. }
  10. }
  11. .build()

② SVG (If the requested MIME type is image/svg+xml, all SVGs will be automatically detected and decoded)

  1. implementation( "io.coil-kt:coil-svg:1.1.1" )
  2.  
  3. val imageLoader = ImageLoader.Builder(context)
  4. .componentRegistry {
  5. add (SvgDecoder(context))
  6. }
  7. .build()

③ Video frame (only supports File and Uri)

  1. implementation( "io.coil-kt:coil-video:1.1.1" )
  2.  
  3. val imageLoader = ImageLoader.Builder(context)
  4. .componentRegistry {
  5. add (VideoFrameFileFetcher())
  6. add (VideoFrameUriFetcher())
  7. }
  8. .build()

<<:  WeChat dedicated input method is here

>>:  There are really no new features to add, so Android 12 has to add a "Recycle Bin" feature

Recommend

Here is a new discovery!!!

On September 7, the reporter learned from the Xis...

Brand Marketing Carnival: Mobile Strategy for the World Cup

The World Cup is not only a brand carnival for gl...

iOS 16.4 Quick Security Response Update

Today, Apple released a quick security response u...

Event planning process summary and case studies!

1. Ideas for event planning 1. Five elements of e...

Introduction to the process of 4 common marketing promotion methods

Previously we talked about some core work ideas o...

I have been wrong for many years! Glasses cloth cannot be used to clean glasses

For people who wear glasses, it is easy for glass...