Fresco三级缓存原理分析&应用

890 阅读5分钟

插图解释:Fresco 意大利语是“新鲜”,实际是指一种十分耐久的壁画,泛指在铺上灰泥的墙壁及天花板上绘画的画作,14-16世纪流行于意大利。

1、起因

书籍章节内容的加载(大量字符串)和图片加载总有着异曲同工的感觉,体积都不小,都可以用到三级缓存,所以看看能否临摹下Fresco的框架。(Fresco官网

2、代码分析

SimpleDraweeView源码看起,目标是找到加载图片的核心逻辑

先看代码调用路径,再概括对象功能,方法调用过程如下图,从右上角看起,触发图片加载的源头方法有两个:setimageURI & onAttachedToWindow,最终走到右下角的ImagePipeline内:

image.png

关键对象:

  • PipelineDraweeControllerBuilder: 只用于创建对象,创建了Controller、一些Supplier对象
  • DraweeHolder: 持有ControllerHierarchy,持有核心代码,是核心代码层
  • PipelineDraweeController: 流程控制,其中submitRequest中将一个订阅者传入了mDataSource中,如下图:

image.png

这个订阅者最终会将数据转化为Drawable,递交给GenericDraweeHierarchy进行图片展示

再看进入ImagePipeline之后,从下图左上角看起:

image.png

关键对象:

  • ProducerSequenceFactory: 产生一个生产者序列,用于获取、加工数据,三级缓存就在其中
  • ClosableProducerToDataSourceAdapter: 顾名思义,适配器模式,将Producer适配成DataSource并返回对象,在这个对象创建之时即触发了Producer的生产逻辑:

image.png

生产结束后,会触发我们一开始讲到的订阅者,将数据转换为Drawable再递交给GenericDraweeHierarchy进行图片展示

OK,加载逻辑到此大致走完,我们可以参考的就是Producer的结构。

Producer传递数据全是各种回调,每个步骤都可以使用异步处理,每个骤都可以随意拼接。

3、仿「Producer」「Consumer」结构

结构特点:

  1. 当前Producer持有下一个Producer,当前produceResults失败则调用下一个produceResults
  2. produceResults传入一个Consumer,这个Consumer使用装饰模式,每层Producer都有自己的装饰,用于层层回调

章节内容加载代码的改造(代码已简化):

定义运行上下文:

// 某本书的某个章节
class DataContext(
    val book: Book,
    val chapter: Chapter
) {
    companion object {
        const val PRODUCER_MEM = 0
        const val PRODUCER_DISK = 1
        const val PRODUCER_NET = 2
    }
 
    var readCache: Boolean = true // 是否读缓存
    var writeCache: Boolean = true //是否写缓存
    var useNetwork: Boolean = true //是否从网络获取
   
   var fromProducer: Int = PRODUCER_MEM //数据由哪个生产者产生
}

定义接口:

interface Producer<Data> {
    fun produceData(context: DataContext, consumer: Consumer<Data>)
}

interface Consumer<Data> {
    fun consumeData(context: DataContext, data: Data?)
}

内存生产者:

class MemoryProducer(
    private val nextProducer: Producer<ChapterContent>? = null
) : Producer<ChapterContent> {
 
    companion object {
        private val lruCache = LruCache<String, ChapterContent>(MAX_CACHE_CHAPTER * MAX_CACHE_BOOK)
        /**
         * 生产:从内存中取
         */
        fun produce(bookId: Long, chapterId: Long): ChapterContent? {
            return lruCache.get(cacheKey(bookId, chapterId))
        }
        /**
         * 消费:内存缓存
         */
        fun consume(bookId: Long, chapterId: Long, chapterContent: ChapterContent) {
            lruCache.put(cacheKey(bookId, chapterId), chapterContent)
        }
    }
 
    override fun produceData(context: DataContext, consumer: Consumer<ChapterContent>) {
        if (context.readCache) {
            produce(
                context.book.bookId,
                context.chapter.chapterId
            )?.also {
                // 从内存中获取到内容
                IKLog.d(TAG, "Memory Produce: ${context.book.bookId}, ${context.chapter.chapterId}")
                context.fromProducer = DataContext.PRODUCER_MEM
                consumer.consumeData(context, it)
                return
            }
        }
        // 未获取到内容,下一个生产者,并包装一层「当前层」的消费逻辑
        nextProducer?.produceData(context, WrapConsumer(consumer))
    }
 
    private class WrapConsumer(
        private val nextConsumer: Consumer<ChapterContent>
    ) : Consumer<ChapterContent> {
        // 内存层的消费逻辑
        override fun consumeData(context: DataContext, data: ChapterContent?) {
            if (context.writeCache && data != null) {
                IKLog.d(TAG, "Memory Consume: ${context.book.bookId}, ${context.chapter.chapterId}")
                consume(context.book.bookId, context.chapter.chapterId, data)
            }
            nextConsumer.consumeData(context, data)
        }
    }
}

磁盘生产者:

class DiskProducer(
    private val nextProducer: Producer<ChapterContent>? = null
) : Producer<ChapterContent> {
 
    companion object {
        /**
         * 生产:从磁盘中取
         */
        fun produce(chapter: Chapter): ChapterContent? {...}
        /**
         * 消费:存入磁盘
         */
        fun consume(chapter: Chapter, chapterContent: ChapterContent) {...}
    }
 
    override fun produceData(context: DataContext, consumer: Consumer<ChapterContent>) {
        if (context.readCache) {
            produce(context.chapter)?.also {
                // 从磁盘中获取到内容
                IKLog.d(TAG, "Disk Produce: ${context.book.bookId}, ${context.chapter.chapterId}")
                context.fromProducer = DataContext.PRODUCER_DISK
                consumer.consumeData(context, it)
                return
            }
        }
        // 未获取到内容,下一个生产者,并包装一层「当前层」的消费逻辑
        nextProducer?.produceData(context, WrapConsumer(consumer))
    }
 
    private class WrapConsumer(
        private val nextConsumer: Consumer<ChapterContent>
    ) : Consumer<ChapterContent> {
        // 磁盘层的消费逻辑
        override fun consumeData(context: DataContext, data: ChapterContent?) {
            if (context.writeCache && data != null) {
                IKLog.d(TAG, "Disk Consume: ${context.book.bookId}, ${context.chapter.chapterId}")
                consume(context.chapter, data)
            }
            nextConsumer.consumeData(context, data)
        }
    }
}

网络生产者:

class NetworkProducer : Producer<ChapterContent> {
    companion object {
        /**
         * 生产:从网络获取,同步返回(网络是最后一层)
         */
        fun produce(bookId: Long, chapterId: Long, contentUrl: String?): ChapterContent? {
            ...
        }
    }
 
    override fun produceData(context: DataContext, consumer: Consumer<ChapterContent>) {
        if (context.useNetwork) {
            produce(
                context.book.bookId,
                context.chapter.chapterId,
                context.chapter.encUrl
            )?.also {
                // 从网络获取到内容,直接消费
                consumeProduce(context, consumer, it)
                return
            }
        }
        // 获取失败,或者不允许使用网络层,直接调用上层的消费方法
        consumer.consumeData(context, null)
    }
 
    // 最后一层不定义包装类,因为不用传入下一层了,直接调用当前层的消费逻辑,再层层回调
    private fun consumeProduce(
        context: DataContext,
        consumer: Consumer<ChapterContent>,
        chapterContent: ChapterContent
    ) {
        IKLog.d(TAG, "Network Produce: ${context.book.bookId}, ${context.chapter.chapterId}")
        // 写缓存控制:某些情况不进行缓存,合并一下原本的写缓存值
        context.writeCache = ... && context.writeCache
        context.fromProducer = DataContext.PRODUCER_NET
        consumer.consumeData(context, chapterContent)
    }
}

获取章节内容入口:

// 三级套娃
private val normalSequence = MemoryProducer(DiskProducer(NetworkProducer()))
 
/**
 * 获取单章内容
 */
fun fetchChapterContent(
    book: Book,
    chapter: Chapter,
    success: (ChapterContent, Int) -> Unit // 成功回调
) {
    // 构造加载数据上下文,设置是否需要缓存
    val context = DataContext(book, chapter)
    context.readCache = ...
    context.writeCache = ...
    // 获取数据,传入consumer对象
    normalSequence.produceData(context, FetchConsumer(success))
}
private class FetchConsumer(
    private val success: (ChapterContent?, Int) -> Unit,
) : Consumer<ChapterContent> {
    override fun consumeData(context: DataContext, data: ChapterContent?) {
        success(data, context.fetchType) // 回调内容 & 获取方式
    }
}

PS:这里异步执行的核心逻辑是:将代码打包成对象,随方法一路传递,在异步执行结束时调用该对象中的方法。适当的结构可能会带来更多的代码,但是极大地增加了可读性。

4、三级缓存中的两个细节设计

(1)防止内存抖动(MemoryChunkPool)

NetworkFetchProducer对象内,网络加载数据后,读取的Response数据流会使用MemoryPooledByteBufferOutputStream保存,该类使用MemoryChunkPool,它可以回收复用同样大小的内存空间,防止内存抖动malloc分配一块指定大小的内存,并返回指向这块内存开始地址的指针,这块内存在native heap中)

可以复用到章节内容下载上,尤其是批量下载

(2)将文件写入变为原子操作(BufferedDiskCache)

DiskCacheWriteProducer内,将数据写入磁盘时使用了BufferedDiskCacheBufferedDiskCache内操作文件的接口是FileCache,实现类实现是DiskStorageCache

image.png

看到DiskStorageCache的插入文件方法

image.png

看到这个注释有个疑问,为什么插入文件要先写一个缓存文件,再进行move?并且这样可以执行很多的并行写入?

实际上这种操作将写入文件变成了一个 原子操作 ,使得写入文件允许并发写入(复习一下并发编程三大特性:原子性、可见性、有序性)。

使用临时文件进行写入的好处:

  1. 将写入文件操作变为原子操作
  2. 多线程/进程读文件时,总是可以读取到完整的文件(不必担心写入异常中断,或者写入速度不够快而造成的文件不完整)
  3. 临时文件放在系统的临时目录,方便清理,不必担心残缺的垃圾文件