美文网首页
Android-Fresco系列8 读缓存数据

Android-Fresco系列8 读缓存数据

作者: cg1991 | 来源:发表于2019-06-10 16:08 被阅读0次

文章将会被同步至微信公众号:Android部落格

看流程图:


image

一、读取解码内存缓存

1. BitmapMemoryCacheProducer

之前加载图片资源的时候有说到过,从缓存取数据,讲的是从内存取,在AbstractDraweeController的submitRequest方法中,先从缓存取数据,getCachedImage方法:

//PipelineDraweeController
@Override
protected @Nullable CloseableReference<CloseableImage> getCachedImage() {
    CloseableReference<CloseableImage> closeableImage = mMemoryCache.get(mCacheKey);
    if (closeableImage != null && !closeableImage.get().getQualityInfo().isOfFullQuality()) {
        closeableImage.close();
        return null;
    }
    return closeableImage;
}

mMemoryCache对应的是ImagePipelineFactory中的InstrumentedMemoryCache<CacheKey, CloseableImage>,通过getBitmapMemoryCache方法初始化:

public InstrumentedMemoryCache<CacheKey, CloseableImage> getBitmapMemoryCache() {
    if (mBitmapMemoryCache == null) {
      mBitmapMemoryCache =
          BitmapMemoryCacheFactory.get(
              getBitmapCountingMemoryCache(),
              mConfig.getImageCacheStatsTracker());
    }
    return mBitmapMemoryCache;
}

而在InstrumentedMemoryCache类中,get方法是:

@Override
public CloseableReference<V> get(K key) {
    CloseableReference<V> result = mDelegate.get(key);
}

mDelegate对应的getBitmapCountingMemoryCache方法初始化:

public CountingMemoryCache<CacheKey, CloseableImage>
getBitmapCountingMemoryCache() {
    if (mBitmapCountingMemoryCache == null) {
      mBitmapCountingMemoryCache =
          BitmapCountingMemoryCacheFactory.get(
              mConfig.getBitmapMemoryCacheParamsSupplier(),
              mConfig.getMemoryTrimmableRegistry(),
              mConfig.getBitmapMemoryCacheTrimStrategy());
    }
    return mBitmapCountingMemoryCache;
}

2. CountingMemoryCache

mDelegate对应的是CountingMemoryCache类,看看这个类里面的get方法:

//CountingMemoryCache
@Nullable
public CloseableReference<V> get(final K key) {
    Preconditions.checkNotNull(key);
    Entry<K, V> oldExclusive;
    CloseableReference<V> clientRef = null;
    synchronized (this) {
      oldExclusive = mExclusiveEntries.remove(key);
      Entry<K, V> entry = mCachedEntries.get(key);
      if (entry != null) {
        clientRef = newClientReference(entry);
      }
    }
    maybeNotifyExclusiveEntryRemoval(oldExclusive);
    maybeUpdateCacheParams();
    maybeEvictEntries();
    return clientRef;
}
//CountingMemoryCache
private synchronized CloseableReference<V> newClientReference(final Entry<K, V> entry) {
    increaseClientCount(entry);
    return CloseableReference.of(
        entry.valueRef.get(),
        new ResourceReleaser<V>() {
          @Override
          public void release(V unused) {
            releaseClientReference(entry);
          }
        });
}

又回到了之前分析的内存缓存的类CountingMemoryCache中了,这个类掌控了内存缓存的写入和获取,以及缓存的清理。

在取缓存的时候先从等待清理的缓存列表中删除,然后从正式缓存列表中去读,如果读取到了缓存(实际是entry对象),就通过newClientReference封装,然后返回。返回之前先检查内存限制参数是否需要更新,接下来查看是否需要清理内存。

在DefaultImageDecoder的decodeJpeg方法中,我们返回了解码后的数据给到CloseableReference<Bitmap> bitmapReference对象,然后将这个对象封装到了CloseableStaticBitmap对象中。

return new CloseableStaticBitmap(
          bitmapReference,
          qualityInfo,
          encodedImage.getRotationAngle(),
          encodedImage.getExifOrientation());

实际我们在内存中缓存的解码之后的数据是CloseableStaticBitmap类型的,而PipelineDraweeController中getCachedImage函数返回的就是这个类型的数据。

二、读取编码内存缓存

1. BitmapMemoryCacheProducer

先执行这个类中的produceResults取编码内存缓存。

1) produceResults
@Override public void produceResults(final Consumer<EncodedImage> consumer, final ProducerContext producerContext) {
    final CacheKey cacheKey = mCacheKeyFactory.getEncodedCacheKey(imageRequest, producerContext.getCallerContext());
    if (cachedReference != null) {
        EncodedImage cachedEncodedImage = new EncodedImage(cachedReference);
        consumer.onNewResult(cachedEncodedImage, Consumer.IS_LAST);
        return;
    }
}

先去查找编码内存缓存,存在数据的话,就将Consumer的状态置为IS_LAST,然后向后传递数据,其实这个状态位一旦置为IS_LAST,预示着后续的Producer不用干活了,但是这个Producer之前的Producer中的Consumer则要开始干活了。

2. 图片处理

具体干什么活呢?之前文章提到过,编码内存缓存的数据是CloseableReference<MemoryPooledByteBuffer>类型的,还要经过伸缩变换,图片翻转,转码等操作,将数据解码成CloseableReference<CloseableStaticBitmap>,然后向上传递给控件展示,此处的流程就是文章 图片解码 里面的业务逻辑了,这里不再重复。

三、读取磁盘缓存

在加载图片之前,先从内存取,取不到的话再从磁盘取,看看从磁盘取数据的过程。

1. DiskCacheReadProducer

1) produceResults

在这个方法中定义了读取磁盘缓存的逻辑:

//DiskCacheReadProducer
public void produceResults(){
    final Task<EncodedImage> diskLookupTask = mDefaultBufferedDiskCache.get(cacheKey, isCancelled);
    final Continuation<EncodedImage, Void> continuation =
        onFinishDiskReads(consumer, producerContext);
        diskLookupTask.continueWith(continuation);
}
2) BufferedDiskCache

mDefaultBufferedDiskCache对应的是BufferedDiskCache类,看看里面的get方法:

//BufferedDiskCache
public Task<EncodedImage> get(CacheKey key, AtomicBoolean isCancelled) {
    try {
      final EncodedImage pinnedImage = mStagingArea.get(key);
      if (pinnedImage != null) {
        return foundPinnedImage(key, pinnedImage);
      }
      return getAsync(key, isCancelled);
    } finally {
    }
}

先从mStagingArea里面取,他维护了一个Map:
private Map<CacheKey, EncodedImage> mMap;,如果取到数据了执行foundPinnedImage函数再返回,否则的话要通过getAsync方法获取。分别看看这两个方法:

  • foundPinnedImage
//BufferedDiskCache
private Task<EncodedImage> foundPinnedImage(CacheKey key, EncodedImage pinnedImage) {
    mImageCacheStatsTracker.onStagingAreaHit(key);
    return Task.forResult(pinnedImage);
}

这里用到了外部sdk,返回一个Task对象,实际是在一个工作线程中返回数据,并回调中间过程包含了结果。

  • getAsync
//BufferedDiskCache
private Task<EncodedImage> getAsync(){
    return Task.call(
        new Callable<EncodedImage>() {
        @Override
        public @Nullable EncodedImage call() {
            EncodedImage result = mStagingArea.get(key);
            if (result != null) {
            } else {
                final PooledByteBuffer buffer = readFromDiskCache(key);
                CloseableReference<PooledByteBuffer> ref = CloseableReference.of(buffer);
                EncodedImage result = new EncodedImage(ref);
            }
            return result;
        }
    });
}

这里还是先从mStagingArea里面取,,取不到再去调用readFromDiskCache函数从磁盘读取:

private @Nullable PooledByteBuffer readFromDiskCache(final CacheKey key){
    final BinaryResource diskCacheResource = mFileCache.getResource(key);
    final InputStream is = diskCacheResource.openStream();
    PooledByteBuffer byteBuffer = mPooledByteBufferFactory.newByteBuffer(is, (int) diskCacheResource.size());
    return byteBuffer;
}

mFileCache对象对应的是DiskStorageCache类,看看getResource方法:

public @Nullable BinaryResource getResource(final CacheKey key) {
    BinaryResource resource = null;
    List<String> resourceIds = CacheKeyUtil.getResourceIds(key);
    for (int i = 0; i < resourceIds.size(); i++) {
        resourceId = resourceIds.get(i);
        resource = mStorage.getResource(resourceId, key);
        if (resource != null) {
            break;
        }
    }
    return resource;
}

mStorage对应的是DynamicDefaultDiskStorage类,看看getResource函数:

//DynamicDefaultDiskStorage
@Override
public BinaryResource getResource(String resourceId, Object debugInfo) throws IOException {
    return get().getResource(resourceId, debugInfo);
}

get函数返回一个DefaultDiskStorage类型的delegate对象,然后在这里调用getResource方法:

//DefaultDiskStorage
@Override
public @Nullable BinaryResource getResource(String resourceId, Object debugInfo) {
    final File file = getContentFileFor(resourceId);
    if (file.exists()) {
      file.setLastModified(mClock.now());
      return FileBinaryResource.createOrNull(file);
    }
    return null;
}

//DefaultDiskStorage
File getContentFileFor(String resourceId) {
    return new File(getFilename(resourceId));
}

//DefaultDiskStorage
private String getFilename(String resourceId) {
    FileInfo fileInfo = new FileInfo(FileType.CONTENT, resourceId);
    String path = getSubdirectoryPath(fileInfo.resourceId);
    return fileInfo.toPath(path);
}

上面三个函数取到了缓存文件,然后将其封装到FileBinaryResource,他实现了BinaryResource接口。

回到readFromDiskCache方法中,将BinaryResource包裹的file打开输入流InputStream,然后执行mPooledByteBufferFactory.newByteBuffer(InputStream),生成PooledByteBuffer对象返回。mPooledByteBufferFactory在ImagePipelineFactory的getMainBufferedDiskCache函数中初始化的:

//ImagePipelineFactory
mConfig.getPoolFactory().getPooledByteBufferFactory(mConfig.getMemoryChunkType()),

最终返回的是MemoryPooledByteBufferFactory对象,跟读取内存缓存时分配的对象是一样的:

//MemoryPooledByteBufferFactory
@Override
public MemoryPooledByteBuffer newByteBuffer(InputStream inputStream, int initialCapacity) {
    MemoryPooledByteBufferOutputStream outputStream =
        new MemoryPooledByteBufferOutputStream(mPool, initialCapacity);
    try {
      return newByteBuf(inputStream, outputStream);
    } finally {
      outputStream.close();
    }
}

先分配一个outputStream对象,探后执行newByteBuf函数,将inputStream数据copy到outputStream中:

//MemoryPooledByteBufferFactory
MemoryPooledByteBuffer newByteBuf(
  InputStream inputStream, MemoryPooledByteBufferOutputStream outputStream) {
    mPooledByteStreams.copy(inputStream, outputStream);
    return outputStream.toByteBuffer();
}

mPooledByteStreams对应的是PooledByteStreams类:

//PooledByteStreams
public long copy(final InputStream from, final OutputStream to) throws IOException {
    long count = 0;
    byte[] tmp = mByteArrayPool.get(mTempBufSize);
    
    try {
      while (true) {
        int read = from.read(tmp, 0, mTempBufSize);
        if (read == -1) {
          return count;
        }
        to.write(tmp, 0, read);
        count += read;
      }
    } finally {
      mByteArrayPool.release(tmp);
    }
}

最后toByteBuffer对应的就是MemoryPooledByteBuffer对象了。

刚才讲了从磁盘读取,读取到了就返回到getAsync方法中,将这个bytebuffer数据封装到EncodeImage中:

  • continueWith

get方法返回的Task,通过continueWith方法提交continuation这个任务,任务就是从磁盘读取数据。

//DiskCacheReadProducer
final Continuation<EncodedImage, Void> continuation =
    onFinishDiskReads(consumer, producerContext);
diskLookupTask.continueWith(continuation);
  • onFinishDiskReads

这个方法用于监听数据返回,在Task任务执行完毕之后就回调到then方法中了,通过getResult

//DiskCacheReadProducer
private Continuation<EncodedImage, Void> onFinishDiskReads(){
    return new Continuation<EncodedImage, Void>() {
        @Override
        public Void then(Task<EncodedImage> task){
        EncodedImage cachedReference = task.getResult();
            if (cachedReference != null) {
                consumer.onNewResult(cachedReference, Consumer.IS_LAST);
            }
        }
    };
}

到这里从磁盘读取的数据如果不为空,就继续往下传递,不过将消费状态修改为完结,后续其他的Consumer就不用继续干活了,回调到之前的Consumer则要处理图片,跟编码图片的处理流程是一样的。

微信公众号:


相关文章

  • Android-Fresco系列8 读缓存数据

    文章将会被同步至微信公众号:Android部落格 看流程图: 一、读取解码内存缓存 1. BitmapMemory...

  • 缓存随谈系列之三:动态缓存

    作为缓存系列的最后一篇,也是我重点想要介绍的。 缓存随谈系列之一:数据库缓存 缓存随谈系列之二:静态缓存(使用静态...

  • 解决缓存与数据库的数据一致性问题

    解决缓存与数据库的数据一致性问题 问题分析 多个写请求执行顺序不同导致脏数据 存在更新缓存请求和读缓存请求,读缓存...

  • 缓存与数据库一致性

    究竟先操作缓存,还是数据库? 读操作 (1)尝试从缓存get数据,结果没有命中;(2)从数据库获取数据,读从库,读...

  • 数据持久化

    数据持久化及数据更新缓存 常用的8种缓存机制:HTTP缓存, locationStorage, Session S...

  • 缓存数据一致性

    读数据的标准姿势:先查缓存,如果缓存中数据不存在时读数据库,然后再将数据写回缓存。 相信这种读缓存方案争议较小...

  • 如何保证双写一致性

    Cache Aside Pattern 1、读的时候,先读缓存,缓存没有的话,就读数据库,取出数据后放入缓存,同时...

  • HBase读流程

    Meta Cache是客户端缓存元数据的Block Cache是读缓存,缓存实际数据1)Clinet先访问Zook...

  • 缓存和数据库双写一致性

    读(查询)操作一致性 当读操作请求打到缓存上时,如果缓存里面存在数据,那么直接返回即可。如果缓存里面没有数据,那么...

  • 库存是先删缓存还是先改db

    库存是先删缓存还是先改db 先改db再淘汰缓存 缓存失效 A读db数据是旧值 B改数据并淘汰缓存 A写入旧值到缓存...

网友评论

      本文标题:Android-Fresco系列8 读缓存数据

      本文链接:https://www.haomeiwen.com/subject/npnzxctx.html