浅谈Fresco编码图片缓存

通过前面的分析我们了解到Fresco中的图片缓存分为3种: 解码图片内存缓存、编码图片内存缓存和磁盘缓存,在Fresco缓存架构分析一文中比较详细的分析了内存缓存和磁盘缓存。本文就来分析一下Fresco编码图片缓存(EncodeMemoryCache)的实现。

Fresco从网络获取的其实是图片的字节流,这个字节流的内容就是未解码的图片的数据:

NetworkFetchProducer.java

protected void onResponse(FetchState fetchState, InputStream responseData, int responseContentLength){
    final PooledByteBufferOutputStream pooledOutputStream;
    ...
    pooledOutputStream = mPooledByteBufferFactory.newOutputStream(responseContentLength);

    final byte[] ioArray = mByteArrayPool.get(READ_SIZE);
    int length;
    while ((length = responseData.read(ioArray)) >= 0) {
        if (length > 0) {
            pooledOutputStream.write(ioArray, 0, length);
            ...
        }
    }
}

即把从网路获取的未解码的图片数据写到了PooledByteBufferOutputStream中。那PooledByteBufferOutputStream把这些数据写到哪里了呢?在FrescoPooledByteBufferOutputStream的唯一实现是MemoryPooledByteBufferOutputStream:

MemoryPooledByteBufferOutputStream.java

public class MemoryPooledByteBufferOutputStream extends PooledByteBufferOutputStream {

    private final MemoryChunkPool mPool; // the pool to allocate memory chunks from
    private CloseableReference<MemoryChunk> mBufRef; // the current chunk that we're writing to

    public void write(byte[] buffer, int offset, int count) throws IOException {
        realloc(mCount + count);
        mBufRef.get().write(mCount, buffer, offset, count);
        mCount += count;
    }


    void realloc(int newLength) {
        if (newLength <= mBufRef.get().getSize()) {
            return;
        }
        MemoryChunk newbuf = mPool.get(newLength);
        mBufRef.get().copy(0, newbuf, 0, mCount);
        mBufRef.close();
        mBufRef = CloseableReference.of(newbuf, mPool);
    }
}

MemoryPooledByteBufferOutputStream实际上是把数据写到了MemoryChunkPool中。MemoryChunkPool负责管理MemoryChunk。一个MemoryChunk代表一个可用的内存块。所以Fresco网络下载的图片会保存到MemoryChunkMemoryChunk是一个接口,在Fresco中一共有两个实现: BufferMemoryChunkNativeMemoryChunk。他们分别代表不同的内存区域。在继续看之前我们先来回顾一下Android中应用内存相关知识:

Android应用内存

在Android中堆(heap)空间完全由程序员控制,堆空间可以细分为java heapnative heap。我们使用java方法申请的内存位于java heap,使用jni通过C/C++申请的内存位于native heap。Android系统对应用程序java heap大小做了硬性限制,当java进程申请的java heap空间超过阈值时,就会抛出OOM异常。不过对于native heap没有任何限制,只要手机还有内存,那应用程序就可以在native heap上一直分配空间。

所以对于一些由于内存不足而引发的OOM问题,可以通过在native heap上分配空间的方式来解决。Fresco中的EncodeMemoryCache就是基于native heap来缓存图片的。

MemoryChunk的分类

BufferMemoryChunk

BufferMemoryChunk.java

public class BufferMemoryChunk implements MemoryChunk, Closeable {

    private ByteBuffer mBuffer;

    @Override
    public synchronized int write(final int memoryOffset, final byte[] byteArray, final int byteArrayOffset, final int count) {
        final int actualCount = MemoryChunkUtil.adjustByteCount(memoryOffset, count, mSize);
        mBuffer.position(memoryOffset);
        mBuffer.put(byteArray, byteArrayOffset, actualCount);
        return actualCount;
    }
}

即它是基于ByteBuffer实现。ByteBufferjava nio中的一个类, IONIO的区别是:

  • IO是面向流的,NIO是面向缓冲区的
  • Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
  • NIO则能前后移动流中的数据,因为是面向缓冲区的
  • IO流是阻塞的,NIO流是不阻塞的
  • Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
  • Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

所以可以简单的理解:BufferMemoryChunk把未解码的图片保存在内存中,并可以方便的操作这块内存,当然这块内存位于java heap上。

NativeMemoryChunk

NativeMemoryChunk.java

public class NativeMemoryChunk implements MemoryChunk, Closeable {

    static {
        ImagePipelineNativeLoader.load(); //加载了一个so库, 用于分配native内存
    }

    public NativeMemoryChunk(final int size) {
        mSize = size;
        mNativePtr = nativeAllocate(mSize);  //通过 native so 库 来分配
        mIsClosed = false;
    }

    public synchronized int write(int memoryOffset, byte[] byteArray, int byteArrayOffset, int count) {
        final int actualCount = MemoryChunkUtil.adjustByteCount(memoryOffset, count, mSize);
        nativeCopyFromByteArray(mNativePtr + memoryOffset, byteArray, byteArrayOffset, actualCount); //把数据字节拷贝到native内存中
        return actualCount;
    }

}

NativeMemoryChunk所管理的内存是通过native方法来分配的:

NativeMemoryChunk.c

static jlong NativeMemoryChunk_nativeAllocate(JNIEnv* env,jclass clzz,jint size) {
    void* pointer = malloc(size);
    ...
    return PTR_TO_JLONG(pointer);
}

前面我们已经说了,通过jni c malloc分配的内存位于应用native内存。所以NativeMemoryChunk所管理的内存位于native heap上。

EncodeMemoryCache的大小

Fresco中编码图片的内存缓存大小最大是多次呢?

DefaultEncodedMemoryCacheParamsSupplier.java

    private int getMaxCacheSize() {
        final int maxMemory = (int) Math.min(Runtime.getRuntime().maxMemory(), Integer.MAX_VALUE);
        if (maxMemory < 16 * ByteConstants.MB) {
            return 1 * ByteConstants.MB;
        } else if (maxMemory < 32 * ByteConstants.MB) {
            return 2 * ByteConstants.MB;
        } else {
            return 4 * ByteConstants.MB;
        }
    }

对于目前大部分手机来说是4MB

编码图片默认的缓存位置

通过前面的分析我们知道编码图片既可以缓存在BufferMemoryChunk上,也可以缓存在NativeMemoryChunk,那默认是缓存在哪里呢?

ImagePipelineConfig.java

    private static int getMemoryChunkType(Builder builder, ImagePipelineExperiments imagePipelineExperiments) {
        if (builder.mMemoryChunkType != null) {
            return builder.mMemoryChunkType;
        } else if (imagePipelineExperiments.isNativeCodeDisabled()) { //没自定义配置的话这个值为false
            return MemoryChunkType.BUFFER_MEMORY;
        } else {
            return MemoryChunkType.NATIVE_MEMORY;
        }
    }

其实在我们没有配置的情况下,Fresco是把编码图片缓存在native memory的。

OK,本文到这里就大致了解Fresco编码图片的缓存方式。其实Fresco编码图片缓存的管理也很复杂,本文就不做分析了。

参考文章:

浅谈Android内存管理

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/18961,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?