淺談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
把這些資料寫到哪裡了呢?在 Fresco
中 PooledByteBufferOutputStream
的唯一實現是 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
網路下載的圖片會儲存到 MemoryChunk
。 MemoryChunk
是一個介面,在 Fresco
中一共有兩個實現: BufferMemoryChunk
和 NativeMemoryChunk
。他們分別代表不同的記憶體區域。在繼續看之前我們先來回顧一下Android中應用記憶體相關知識:
Android應用記憶體
在Android中堆(heap)空間完全由程式設計師控制,堆空間可以細分為 java heap
和 native 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
實現。 ByteBuffer
是 java nio
中的一個類, IO
與 NIO
的區別是:
- 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進階計劃 看更多幹貨
歡迎關注我的微信公眾號:susion隨心

微信公眾號.jpeg