Coil源码解析(一)

838 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

介绍

Coil是一个使用Kotlin开发并使用协程(Coroutines)的图片加载库。得益于Kotlin的语法特性,使得Coil在使用上以及轻量化上优于常见的图片加载库(Glide、Fresco等)。但也正是由于依赖于Kotlin的语法特性,Java开发者可能享受不到Coil带来的优势。

基本用法

利用Kotlin的扩展方法语法特性,使得使用Coil将图片加载到ImageView上变得异常简单:

ImageView.load(url)

ImageView.load()

对于还在继续看这篇文章的读者,笔者默认聪明的你们已经认识了扩展方法这一Kotlin特性,就不作展开了。

直接上代码!

inline fun ImageView.load(
    data: Any?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable {
    val request = ImageRequest.Builder(context)
        .data(data)
        .target(this)
        .apply(builder)
        .build()
    return imageLoader.enqueue(request)
}

因为第三个入参builder是一个方法,所以load方法使用了inline关键字,标记为内联方法以提升性能表现。

  • data:图片资源的所在地(插播一个笔者OS:这个入参命名为data有些令人混淆,私以为取名source、location等较为贴切)。类型为Any,意味着该方法可以同时处理多种图片源,可选类型具体为:Uri(Android资源、Content、File、HTTP(S) schemes)、String(与Uri类似)、HttpUrl、File、DrawableRes、Drawable、Bitmap、ByteArray、ByteBuffer。
  • imageLoader:类型即为ImageLoader。ImageLoader是一个接口,负责ImageRequest管理、缓存、获取数据、图片解码、内存管理等。Coil利用扩展属性为Context内置了一个默认实现。
  • builder:ImageRequest的构造器。类型为以ImageRequest.Builder为接收者的无参无返回方法。用于配置ImageRequest。

load方法很简单,利用ImageRequest的构造器构造出一个Request并入队到ImageLoader。我们逐个分析。

ImageRequest.Builder()

创建ImageRequest的构造器,初始化参数。

data()

将data赋值给Builder中的data属性。

target()

fun target(imageView: ImageView) = target(ImageViewTarget(imageView))
​
fun target(target: Target?) = apply {
            this.target = target
            resetResolvedValues()
        }

将ImageView包装成ImageViewTarget,然后将其赋值给Builder中的target属性。target的类型为Target,数据目的地抽象出来的接口。ImageViewTarget为Target的一个实现类,以ImageView作为目的地。

resetResolvedValues()则是重置一些属性,确保每一次调用build都能重新计算这些属性的值。

apply()

apply是Kotlin的作用域方法之一,没啥好说的,就是将传入的Builder配置应用到实际的Builder中。

build()

构建ImageRequest。

ImageLoader.enqueue()

由于使用的是Coil默认提供的ImageLoader,所以我们先来看看Coil是怎么提供ImageLoader的:

sequenceDiagram
Context ->> Coil: .imageLoader
Coil ->> Coil : newImageLoader
Coil ->> ImageLoader.Builder:build()
ImageLoader.Builder ->> Context :RealImageLoader

从上面的流程图可以看出,实际上构建的是RealImageLoader。值得注意的是,这个RealImageLoader是internal标记的,所以我们无法直接构建出来,但可以使用ImageLoader.Buidler(context).build()来构建自己的RealImageLoader。

接下来看看RealImageLoader的enqueue方法:

override fun enqueue(request: ImageRequest): Disposable {
        //构建协程异步任务Deferred<ImageResult>
        val job = scope.async {
            executeMain(request, REQUEST_TYPE_ENQUEUE).also { result ->
                if (result is ErrorResult) logger?.log(TAG, result.throwable)
            }
        }
​
        //根据Target的类型创建不用的Disposable
        return if (request.target is ViewTarget<*>) {
            //①
            request.target.view.requestManager.getDisposable(job)
        } else {
            //②
            OneShotDisposable(job)
        }
    }
  • 首先构建一个协程的异步Job,执行的内容为executeMain()。不管从什么线程调用,都会被协程调度到主线程上。可能大家有疑惑,加载图片不是耗时操作吗?为什么在主线程上去执行?那是因为在真正需要执行耗时操作的时候,协程会将主线程“挂起”,然后将耗时操作调度到子线程上执行,执行完毕后再自动切换回主线程。而这时候的主线程“挂起”是不阻塞的。不了解协程的读者可能不太能理解,但这里就不展开了,请自行恶补!
  • 然后构建一个Disposable并返回。Disposable是Coil定义的一个接口,用于dispose(读者理解为丢弃)某个任务。Disposable有两个实现类:ViewTargetDisposable和OneShotDisposable。分别对应Target为ViewTarget(①处代码)和非ViewTarget(②处代码)的情况。三者关系如下图:
classDiagram
class Disposable{
+ Deferred<ImageResult> job
+ Boolean isDisposed
+ dispose()
}

<<interface>> Disposable

class OneShotDisposable
class ViewTargetDisposable{
+ View view
}

Disposable <|.. OneShotDisposable:实现
Disposable <|.. ViewTargetDisposable:实现

重点是executeMain方法:

private suspend fun executeMain(initialRequest: ImageRequest, type: Int): ImageResult {
    //①
    val requestDelegate = requestService.requestDelegate(
        initialRequest, 
        coroutineContext.job
    ).apply { assertActive() }
​
    //②
    val request = initialRequest.newBuilder().defaults(defaults).build()
​
    //③
    val eventListener = eventListenerFactory.create(request)
​
    try {
        if (request.data == NullRequestData) throw NullRequestDataException()
​
        //④
        requestDelegate.start()
​
        //⑤
        if (type == REQUEST_TYPE_ENQUEUE) request.lifecycle.awaitStarted()
​
        //⑥
        val placeholderBitmap = memoryCache?
                                .get(request.placeholderMemoryCacheKey)?
                                .bitmap
        val placeholder = placeholderBitmap?.toDrawable(request.context)
                            ?: request.placeholder
        request.target?.onStart(placeholder)
        eventListener.onStart(request)
        request.listener?.onStart(request)
​
        //⑦
        eventListener.resolveSizeStart(request)
        val size = request.sizeResolver.size()
        eventListener.resolveSizeEnd(request, size)
​
        val result = withContext(request.interceptorDispatcher) {
            //⑧
            RealInterceptorChain(
                initialRequest = request,
                interceptors = interceptors,
                index = 0,
                request = request,
                size = size,
                eventListener = eventListener,
                isPlaceholderCached = placeholderBitmap != null
            ).proceed(request)
        }
​
        //⑨
        when (result) {
            is SuccessResult -> onSuccess(result, request.target, eventListener)
            is ErrorResult -> onError(result, request.target, eventListener)
        }
        return result
    } catch (throwable: Throwable) {
        if (throwable is CancellationException) {
            onCancel(request, eventListener)
            throw throwable
        } else {
            val result = requestService.errorResult(request, throwable)
            onError(result, request.target, eventListener)
            return result
        }
    } finally {
        requestDelegate.complete()
    }
}
  • ①处代码。将Request和当前的协程Job包装成RequestDelegate,以便能够自动感应Lifecycle来执行Request的取消和重启。RequestDelegate实现了DefaultLifecycleObserver(可以观察LifecycleOwner的生命周期)。RequestDelegate有两个子类:BaseRequestDelegate和ViewTargetRequestDelegate。由于我们的Target是ViewTarget,所以使用的是ViewTargetRequestDelegate,我们来看看。

    internal class ViewTargetRequestDelegate(
        private val imageLoader: ImageLoader,
        private val initialRequest: ImageRequest,
        private val target: ViewTarget<*>,
        private val lifecycle: Lifecycle,
        private val job: Job
    ) : RequestDelegate() {
    ​
        @MainThread
        fun restart() {
            imageLoader.enqueue(initialRequest)
        }
    ​
        //目标View需要已经AttachedToWindow才允许执行
        override fun assertActive() {
            if (!target.view.isAttachedToWindow) {
                //每一个view都会有一个独立的RequestManger,用于管理该View的Request。
                //setRequest会将现有的Request丢弃后再赋值,保证一个View只有一个Request。
                target.view.requestManager.setRequest(this)
                throw CancellationException("'ViewTarget.view' must be attached to a window.")
            }
        }
    ​
        //开启后代理会观察Lifecycle
        override fun start() {
            lifecycle.addObserver(this)
            if (target is LifecycleObserver) lifecycle.removeAndAddObserver(target)
            target.view.requestManager.setRequest(this)
        }
    ​
        //丢弃则取消协程Job并移除Lifecycle监听。
        override fun dispose() {
            job.cancel()
            if (target is LifecycleObserver) lifecycle.removeObserver(target)
            lifecycle.removeObserver(this)
        }
    ​
        //Lifecycle走到Destroy生命周期时,自动丢弃该view的Request。
        override fun onDestroy(owner: LifecycleOwner) {
            target.view.requestManager.dispose()
        }
    }
    
  • ②处代码。将默认的RequestOptions传入Request

  • ③处代码。创建EventListener,由于本例没有指定,所以对应的是EventListener.NONE对象,是一个空实现。虽然如此,但还是可以看一下EventListener的结构:

    classDiagram
    class EventListener{
        +onStart()
        +resolveSizeStart()
        +resolveSizeEnd()
        +mapStart()
        +mapEnd()
        +keyStart()
        +keyEnd()
        +fetchStart()
        +fetchEnd()
        +decodeStart()
        +decodeEnd()
        +transformEnd()
        +transformStart()
        +transitionStart()
        +transitionEnd()
        +onError()
        +onCancel()
        +onSuccess()
        +NONE EventListener
    }
    class Factory{
        +Factory NONE
        +create()
    }
    
    <<interface>> Factory
    <<interface>> EventListener
    
    EventListener --> Factory
    

    可以看到,EventListener几乎将Coil的整个工作流程都包含了,假如有精细粒度控制的需求可以构建自己的EventListener。

  • ④处代码。启动RequestDelegate,开始感知Lifecycle。

  • ⑤处代码。如果是enqueue调用该方法,则需要等待Lifecycle处于Started状态才继续往下执行。(对于这里的处理方式笔者有点疑问,站在响应速度的角度来说,是不是——先请求了数据,再等待Lifecycle处于Started状态后然后执行显示——会比这里的处理方式好呢?)

  • ⑥处代码。获取占位图(如果有)。通知Target请求开始并传递占位图(Target自行处理占位图的显示逻辑);通知EventListener请求开始;通知Request的Listener请求开始。

  • ⑦处代码。计算Size。如无指定Size,宽高则为Undefined。

  • ⑧处代码。在interceptorDispatcher指定的线程进行拦截器链执行并得到结果。interceptorDispatcher如果未指定则为主线程。

  • ⑨处代码。判断result是成功结果还是失败结果,并通知各种Listener。

看到⑧处代码,相信不少人都会心一笑,心里默念:还得是你啊!

没错,这个InterceptorChain跟大名鼎鼎的OKhttp的设计如出一辙!

既然气氛都烘托到这了,那就来复习(也可能是预习)一下这种设计的结构吧。

classDiagram
class Interceptor{
	<<interface>>
	intercept(Chain chain) Result
}
class Chain{
	<<interface>>
	proceed(Request request) Result
	Request request
}
sequenceDiagram
Invoker ->> Chain : proceed()
Chain ->> First Interceptor : intercept()
First Interceptor ->> Chain : proceed()
Chain ->> Second Interceptor : intercept()
Second Interceptor ->> Chain : proceed()
Chain ->> ... : intercept()
... ->> Chain : proceed()
Chain ->> Last Interceptor : intercept()
Last Interceptor ->> ... : Result
... ->> Second Interceptor : Result
Second Interceptor ->> First Interceptor : Result
First Interceptor ->> Chain : Result
Chain ->> Invoker : Result

看完了InterceptorChain,很自然就会想到,核心的内容就在各个拦截器的Intercept方法中。正当我满怀希望地找寻“各个”拦截器的时候,我发现我只找到了一个拦截器……

也就是说Coil设计这个拦截器链自己没用上,只是为了让调用者使用的!(内心OS:其实我认为可能只是因为Coil作者仰慕OKhttp的设计而做出的致敬行为,纯属脱裤子放屁)

既然只有一个拦截器,那所有的操作都在那一个拦截器里,下一篇我们就从这个拦截器讲起!