1. 介绍: fresco,facebook开源的针对android应用的图片加载框架,高效和功能齐全。
支持加载网络,本地存储和资源图片;
提供三级缓存(二级memory和一级internal storage);
支持JPEGs,PNGs,GIFs,WEBPs等,还支持Progressive JPEG,优秀的动画支持;
图片圆角,scale,自定义背景,overlays等等;
优秀的内存管理, 在安卓低版本中会将缓存保存至特殊区域, 而不是java heap, 从而避免oom;
2. 主要组成部分
DraweeView:继承于ImageView,只是简单的读取xml文件的一些属性值和做一些初始化的工作,图层管理交由Hierarchy负责,图层数据获取交由ViewHolder负责。
DraweeHierarchy:由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角)。
DraweeController:控制数据的获取与图片加载,向pipeline发出请求,并接收相应事件,并根据不同事件控制Hierarchy,从DraweeView接收用户的事件,然后执行取消网络请求、回收资源等操作。
DraweeHolder:统筹管理Hierarchy与DraweeController。
ImagePipeline:Fresco的核心模块,用来以各种方式(内存、磁盘、网络等)获取图像。
Producer/Consumer:Producer也有很多种,是完成具体工作的类. 它用来完成网络数据获取,缓存数据获取、图片解码等多种工作,它产生的结果由Consumer进行消费。
IO/Data:这一层便是数据层了,负责实现内存缓存、磁盘缓存、网络缓存和其他IO相关的功能。
3. 发起图片请求的主要流程 3.1 流程图
3.2 源码分析 3.2.1 DraweeView 我们常用的类是SimpleDraweeView, 继承关系如下 SimpleDraweeView -> GenericDraweeView -> DraweeView -> ImageView注意: 虽然上述的类都是继承于ImageView的, 但是使用时最好不要调用ImageView本身的任何属性和方法, 不然的话使用不了Fresco的功能
DraweeView: 持有ViewHolder, ViewHolder管理DraweeController和DraweeHierarchy
GenericDraweeView: 解析xml属性, 创建DraweeHierarchy
SimpleDraweeView: 我们一般使用的类, 接受请求, 创建爱你DraweeController
SimpleDraweeView.setImageURI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void setImageURI (Uri uri, @Nullable Object callerContext) {DraweeController controller = mControllerBuilder .setCallerContext(callerContext) .setUri(uri) .setOldController(getController()) .build(); setController(controller); }
复制
mControllerBuilder在setUri方法中创建了ImageRequest, 在build的过程中构建好了请求, cache, 编码, 解码等流程. 最后setController启动请求流程
3.2.2 DraweeControllerBuilder.build 在DraweeControllerBuilder.build方法中创建了DataSource. 代表数据的来源,用于组装 produce 集合
1 2 3 4 5 6 7 -> AbstractDraweeControllerBuilder.build --> AbstractDraweeControllerBuilder.buildController ----> PipelineDraweeControllerBuilder.obtainController // 创建controller并return -----> AbstractDraweeControllerBuilder.obtainDataSourceSupplier ------> AbstractDraweeControllerBuilder.getDataSourceSupplierForRequest // 创建了Supplier<DataSource<IMAGE>>, 调用supplier.get方法就会创建DataSource -------> PipelineDraweeControllerBuilder.getDataSourceForRequest --------> ImagePipeline.fetchDecodedImage(...)
复制
3.2.3 setController 1 2 3 4 5 6 -> DraweeView.setController --> DraweeHolder.setController ----> DraweeController.setHierarchy ----> DraweeHolder.attachController -----> AbstractDraweeController.onAttach ------> AbstractDraweeController.submitRequest
复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 protected void submitRequest () { ... final T closeableImage = getCachedImage(); if (closeableImage != null ) { ... return ; } ... mDataSource = getDataSource(); final String id = mId; final boolean wasImmediate = mDataSource.hasResult(); final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { @Override public void onNewResultImpl (DataSource<T> dataSource) { boolean isFinished = dataSource.isFinished(); boolean hasMultipleResults = dataSource.hasMultipleResults(); float progress = dataSource.getProgress(); T image = dataSource.getResult(); if (image != null ) { onNewResultInternal( id, dataSource, image, progress, isFinished, wasImmediate, hasMultipleResults); } else if (isFinished) { onFailureInternal(id, dataSource, new NullPointerException(), true ); } } @Override public void onFailureImpl (DataSource<T> dataSource) { ... } @Override public void onProgressUpdate (DataSource<T> dataSource) { ... } }; mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); } @Override protected DataSource<CloseableReference<CloseableImage>> getDataSource() { DataSource<CloseableReference<CloseableImage>> result = mDataSourceSupplier.get(); return result; }
复制
还有一个问题, DataSource是什么时候启动的? 我们在DraweeController.build创建过程中发现创建了Supplier<DataSource<>>, controller的getDataSource实际上就是从Supplier获取的DataSource
1 2 3 4 5 -------> PipelineDraweeControllerBuilder.getDataSourceForRequest --------> ImagePipeline.fetchDecodedImage // 在这个方法中创建了producerSequence ---------> ImagePipeline.submitFetchRequest ----------> CloseableProducerToDataSourceAdapter<T>.craete -----------> new CloseableProducerToDataSourceAdapter
复制
featchDecodeImage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage( ImageRequest imageRequest, Object callerContext, ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, @Nullable RequestListener requestListener) { try { Producer<CloseableReference<CloseableImage>> producerSequence = mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); return submitFetchRequest( producerSequence, imageRequest, lowestPermittedRequestLevelOnSubmit, callerContext, requestListener); } catch (Exception exception) { return DataSources.immediateFailedDataSource(exception); } }
复制
CloseableProducerToDataSourceAdapter的构造方法 这个构造方法只是简单的调用父类的构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected AbstractProducerToDataSourceAdapter ( Producer<T> producer, SettableProducerContext settableProducerContext, RequestListener requestListener) { mSettableProducerContext = settableProducerContext; mRequestListener = requestListener; mRequestListener.onRequestStart( settableProducerContext.getImageRequest(), mSettableProducerContext.getCallerContext(), mSettableProducerContext.getId(), mSettableProducerContext.isPrefetch()); if (FrescoSystrace.isTracing()) { FrescoSystrace.endSection(); } producer.produceResults(createConsumer(), settableProducerContext); }
复制
原来DataSource一创建就会启动produer的工作流程
3. Producer序列的工作流程 3.1 Producer/Consumer的基本概念 模板代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class XXXXProducer implements Producer { private final Producer mInputProducer; public BitmapMemoryCacheProducer (Producer inputProducer) { mInputProducer = inputProducer; } @Override public void produceResults ( final Consumer consumer, final ProducerContext producerContext) { ... 尝试直接得到结果 if (已经获取到结果){ consumer.onNewResult(result, status); return ; } Consumer newConsumer = new DelegatingConsumer(consumer){ @Override public void onNewResultImpl (newResult, int status) { ... 处理上一阶段返回的结果 if (isLast){ getConsumer().onNewResult(); } } } mInputProducer.produceResults(newConsumer, producerContext); } }
复制
Consumer的onNewResult方法 onNewResult会直接调用自己的onNewResultImpl方法
1 2 3 4 5 6 7 8 9 10 11 12 @Override public synchronized void onNewResult (@Nullable T newResult, @Status int status) { if (mIsFinished) { return ; } mIsFinished = isLast(status); try { onNewResultImpl(newResult, status); } catch (Exception e) { onUnhandledException(e); } }
复制
按照这样类似责任链的设计模式, 实际上, 往后加入的producer越晚执行
3.2 主要的producer内容梳理
BitmapMemoryCacheGetProducer 从内存中获取已解码的图片, 因为是直接从内存中获取可以用的, 所以是即时的, 在UI线程中就可以做
BackgroundThreadHandoffProducer 将任务移交给子线程, 这里仅仅是转移线程而已, 具体的工作在具体的线程中完成
BitmapMemoryCacheKeyMultiplexProducer 将多个拥有相同已解码内存缓存键的ImageRequest进行“合并”,若缓存命中,它们都会获取到该数据
BitmapMemoryCacheProducer 又一次获取内存缓存? 我觉得主要是将下一阶段获取的已解码图片存储到缓存中
DecodeProducer 解码
ResizeAndRotateProducer 旋转, 缩放
AddImageTransformMetaProducer 添加MetaData
EncodeCacheKeyMutiplexProducer 将多个拥有相同未解码内存缓存键的ImageRequest进行“合并”,若缓存命中,它们都会获取到该数据;
EncodedMemoryCacheProducer 查找未解码的图片缓存, 将下一步得到的未解码图片保存到缓存中
DiskCacheReadProducer 读磁盘缓存, 有分MainCache和SmallCache, SmallCache存储小图片, 避免大图片被挤出缓存. 启动task并扔进线程池
DiskCacheWriteProducer 存入磁盘缓存, 同样是在线程池中操作
newNetworkFetchProducer 从网络中获取图片
4. Fresco 在内存管理上的优势
在Dalvik虚拟机中,gc性能较差,会伴有stop-the-world的发生,导致卡顿,所以Fresco会将解码之后的Bitmap存放到Ashmem当中,并且每次解码完都会通过Native层的代码进行PinBitmap的操作,防止被系统回收。 Fresco使用了 CloseableReference 进行引用计数,手动回收bitmap对象。
在Art虚拟机中,gc性能得到了大幅的提升,所以没必要用各种骚操作,直接将Bitmap解码到Java堆当中即可。
4.1 CloseableReference 引用计数 使用 CloseableReference 优雅的释放对象,来自 Fresco
CloseableReference 是 fresco 内部建立的一个机制,核心思想是 用static的集合屏蔽java的gc的内存回收,转变为类似c/c++的手动释放内存。 在Dalvik虚拟机中,gc性能较差,bitmap的频繁gc会影响性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public final class CloseableReference <T > implements Cloneable , Closeable { private CloseableReference (T t, ResourceReleaser<T> resourceReleaser) { mSharedReference = new SharedReference<T>(t, resourceReleaser); } @Override public void close () { synchronized (this ) { if (mIsClosed) { return ; } mIsClosed = true ; } mSharedReference.deleteReference(); } public synchronized CloseableReference<T> clone () { return new CloseableReference<T>(mSharedReference); } private CloseableReference (SharedReference<T> sharedReference) { mSharedReference = Preconditions.checkNotNull(sharedReference); sharedReference.addReference(); } } public class SharedReference <T > { public static Map<Object, Integer> getLiveObjects () { return sLiveObjects; } public SharedReference (T value, ResourceReleaser<T> resourceReleaser) { mValue = Preconditions.checkNotNull(value); mResourceReleaser = Preconditions.checkNotNull(resourceReleaser); mRefCount = 1 ; addLiveReference(value); } private static void addLiveReference (Object value) { synchronized (sLiveObjects) { Integer count = sLiveObjects.get(value); if (count == null ) { sLiveObjects.put(value, 1 ); } else { sLiveObjects.put(value, count + 1 ); } } } public synchronized void addReference () { mRefCount++; } public void deleteReference () { if (decreaseRefCount() == 0 ) { T deleted; synchronized (this ) { deleted = mValue; mValue = null ; } mResourceReleaser.release(deleted); removeLiveReference(deleted); } } public synchronized int decreaseRefCount () { mRefCount--; return mRefCount; } }
复制
4.2 CloseableReference 在 LruCache 的使用 CountingMemoryCache
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public class 65<K , V > implements MemoryCache <K , V >, MemoryTrimmable { final CountingLruMap<K, Entry<K, V>> mExclusiveEntries; final CountingLruMap<K, Entry<K, V>> mCachedEntries; @Nullable public CloseableReference<V> get (final K 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; } public CloseableReference<V> cache ( final K key, final CloseableReference<V> valueRef, final EntryStateObserver<K> observer ) { maybeUpdateCacheParams(); Entry<K, V> oldExclusive; CloseableReference<V> oldRefToClose = null ; CloseableReference<V> clientRef = null ; synchronized (this ) { oldExclusive = mExclusiveEntries.remove(key); Entry<K, V> oldEntry = mCachedEntries.remove(key); if (oldEntry != null ) { makeOrphan(oldEntry); oldRefToClose = referenceToClose(oldEntry); } if (canCacheNewValue(valueRef.get())) { Entry<K, V> newEntry = Entry.of(key, valueRef, observer); mCachedEntries.put(key, newEntry); clientRef = newClientReference(newEntry); } } CloseableReference.closeSafely(oldRefToClose); maybeNotifyExclusiveEntryRemoval(oldExclusive); maybeEvictEntries(); return clientRef; } 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); } }); } private void releaseClientReference (final Entry<K, V> entry) { Preconditions.checkNotNull(entry); boolean isExclusiveAdded; CloseableReference<V> oldRefToClose; synchronized (this ) { decreaseClientCount(entry); isExclusiveAdded = maybeAddToExclusives(entry); oldRefToClose = referenceToClose(entry); } CloseableReference.closeSafely(oldRefToClose); maybeNotifyExclusiveEntryInsertion(isExclusiveAdded ? entry : null ); maybeUpdateCacheParams(); maybeEvictEntries(); } }
复制
这个cache就是将 LruCache 和 引用计数的思想结合
4.3 针对 Dalvik 虚拟机的缓存优化 在 DecodeProducer 中,进行了图片的解码。 最终调用解码是在 ProgressiveDecoder#doDecode
方法中
1 2 3 4 5 private void doDecode (EncodedImage encodedImage, @Status int status) { ... image = mImageDecoder.decode(encodedImage, length, quality, mImageDecodeOptions); ... }
复制
mImageDecoder的实现类根据系统版本有所不同,在 api21 也就是 art虚拟机实现类是 DefaultDecoder
。 在 Dalvik虚拟机实现类是 DalvikPurgeableDecoder
以jpg图片的解码举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public CloseableReference<Bitmap> decodeJPEGFromEncodedImageWithColorSpace ( final EncodedImage encodedImage, Bitmap.Config bitmapConfig, @Nullable Rect regionToDecode, int length, final boolean transformToSRGB) { BitmapFactory.Options options = getBitmapFactoryOptions( encodedImage.getSampleSize(), bitmapConfig); final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef(); Preconditions.checkNotNull(bytesRef); try { Bitmap bitmap = decodeJPEGByteArrayAsPurgeable(bytesRef, length, options); return pinBitmap(bitmap); } finally { CloseableReference.closeSafely(bytesRef); } }
复制
这里做了两个操作,先解码得到bitmap,然后对pin这个bitmap。 pin的作用是让的bitmap对象避免被系统gc
先来看一下解码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Override protected Bitmap decodeJPEGByteArrayAsPurgeable ( CloseableReference<PooledByteBuffer> bytesRef, int length, BitmapFactory.Options options) { byte [] suffix = endsWithEOI(bytesRef, length) ? null : EOI; return decodeFileDescriptorAsPurgeable(bytesRef, length, suffix, options); } private Bitmap decodeFileDescriptorAsPurgeable ( CloseableReference<PooledByteBuffer> bytesRef, int inputLength, byte [] suffix, BitmapFactory.Options options) { MemoryFile memoryFile = null ; try { memoryFile = copyToMemoryFile(bytesRef, inputLength, suffix); FileDescriptor fd = getMemoryFileDescriptor(memoryFile); if (mWebpBitmapFactory != null ) { Bitmap bitmap = mWebpBitmapFactory.decodeFileDescriptor(fd, null , options); return Preconditions.checkNotNull(bitmap, "BitmapFactory returned null" ); } else { throw new IllegalStateException("WebpBitmapFactory is null" ); } } catch (IOException e) { throw Throwables.propagate(e); } finally { if (memoryFile != null ) { memoryFile.close(); } } }
复制
这里有一个 MemoryFile , 这是安卓系统提供的用来使用匿名共享内存的类(ashmem) , 也就是说在 Dalvik 会将bitmap保存到 ashmem 里,避免被触碰到java层的oom限制。
然后是 pin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public CloseableReference<Bitmap> pinBitmap (Bitmap bitmap) { nativePinBitmap(bitmap); return CloseableReference.of(bitmap, mUnpooledBitmapsCounter.getReleaser()); }
复制
Dalvik 和 Art 在gc上的差异 揭秘 ART 细节 —- Garbage collection
art 新增了 large object space ,专供大对象使用
Dalvik 在垃圾回收时,会暂停所有线程,在内存紧张时会频繁执行,容易造成卡顿丢帧。art优化了回收算法,会减少暂停线程的次数