Android的bitmap存储
Android 3.0以前bitmap存储在native层,需要在不使用时主动recycle,否则会内存泄漏。Android 3.0到Android 8.0,改到了java堆内存区域,不需要手动recycyle。而到了8.0以后,又改为native层存储,但会根据引用自动释放内存。
Fresco的内存优化原理
Fresco主要解决bitmap频繁gc导致的卡顿和OOM问题。但是Android系统存在一块匿名共享内存区域(ashmem),存放在其中的数据dalvik heap与native heap,因此不会导致OOM。如果将bitmap存放到ashmem区域就解决了bitmap创建与释放导致的gc问题。但在Android 5.0以后由于gc做了优化,gc导致的卡顿已经好了很多,所以将bitmap放到了java堆中。
通过在decode时设置inPurgeable可以使bitmap存储到ashmem中去。ashmem内存在btimap第一次draw时才去分配,分配操作时需要解码原始数据的,而且图片在被渲染后,ashmem内存会被自动unpin,等下次渲染时如果内存被释放又需要从encoded data中取出来重新解码,而此时是在ui线程,因此使用ashmem会造成渲染卡顿。Fresco为了解决此问题,通过在每次decode完bitmap后,调用native的lockPixels 方法为bitmap分配ashmem内存,并且不主动调用unlockPixels去释放内存,而是通过在java层的closeableReference去判断是否需要bitmap的引用,不需要时调用bitmap.recycle,该方法会主动清除ashmem区域占用的内存。
Fresco的内存缓存
Fresco保存了两个LRU队列:mCachedEntries存储了所有的缓存,mExclusiveEntries存储了当前引用为0的缓存(意思是如果内存不足就可被清理掉)。
entries中存储的数据对应了referenceCount,当引用计数为0时就会被存到mExclusiveEntries里,但是此时并未被删除,只是标识即将被清理。如果此时命中缓存又会从mExclusiveEntries中删掉
按照Fresco的规范,每一个closeReference在不用时都需要close掉,因此如果按照规范来操作,最后的bitmap一定会被马上recycle掉。那Fresco的内存缓存还怎么起作用呢?答案在于CountingMemoryCache中cache方法最后做的一次转换。
CountingMemoryCache存储的是Entry对象:
Entry<K,V>{
K key;
CloseableReference<V> valueRef;
int clientCount;
}
从Entry的结构可以看出,Entry单独对传入的CloseableReference做了计数,当clientCount计数为0时就将Entry放入mExclusiveEntries,等内存不足或trim时调用entry.valueRef的close方法清除数据。
如果是这样,直接存储valueRef就可以了,又何必再封装一层,原因在于bitmap回收的关键在于SharedReference创建时传入的ResourceReleaser对象,其代表当前SharedReference计数为0时需要做的操作。从底层传到CountingMemoryCache的SharedReference采用了默认的ResourceReleaser对象,
计数为0时直接调用bitmap.recycle方法。如果CountingMemoryCache存储的是原始的ref,当传到外部后使用完毕,如果按照规范层层close,最后必然调到bitmap.recycle导致bitmap被回收,此时内存缓存已失去意义。
Fresco在cache完毕后,返回一个新的CloseableReference,其value仍指向传入的CloseableReference中的value,但将其与生成的entry通过一个新的ResourceReleaser对象做了关联,这个覆写的ResourceReleaser对象在新的CloseableReference计数为0时,将关联的Entry中的clientCount计数减1,
同时判断clientCount是否为0,如果是的话就将其放入mExclusiveEntries。但此时还并未释放数据,而是等待内存不足时或trim时才去操作的。
此处还有一个关键点,在Entry中存储的valueRef是对原始传入的CloseableReference进行clone操作生成的,否则外部close后会导致存储的SharedReference计数为0,导致bitmap被回收。
所以,本质上等同于将传入CountingMemoryCache的reference clone后,存到map中。外部引用全部解除后,此时存到cache中的reference的count为1。在内存不足或trim时调用close方法导致refcount为0后释放内存。
DataSource
dataSource相当于一个结果的存储器,生成datasource时请求已发送出去,结果会传入datasource的onNewResultImpl方法,异步保存在datasource中的result上。注意此处,如果datasource是CloseableProducerToDataSourceAdapter,会将传入的reference clone后保存在result中,因此datasource不再需要时应主动调用close方法关闭引用。当调用datasource的subscribe方法时,会将结果回调给subscriber的onNewResultImpl方法。如果是实现的BaseDataSubscriber,Fresco在回调完成后会主动调用datasource.close方法去关闭引用,以防止调用者忘记close而导致的内存泄漏。
因此,使用datasource的流程为,datasource保存的result的引用计数为1,当调用subscriber的onNewResultImpl时,传入的reference应进行clone后再使用,使用完毕后主动close,此时datasource保存的引用已变为2,当调用完subscriber后,datasource.close后,引用变为1。当主动调reference的close方法后,引用变为0,资源被释放。