Fresco分析(一)内存管理篇

2016/03/25 Fresco

[Producer体系整体处理的逻辑伪代码]
[UI更新体系]
[消费者SubscribeAdapter体系]
Native原理 计数与Recycle
不同的版本ArtDecorder与DalvikDecoder

Fresco库对MVC模式的使用

MVC模式,便知道Model负责的是持有数据,Viewer用于展示数据,Controller用于控制数据的逻辑,即核心的控制逻辑位于Controller中

  • DraweeView(Viewer) 继承于 View, 负责图片的显示。

  • DraweeHierarchy(Model) DraweeHierarchy 用于组织和维护最终绘制和呈现的Drawable对象,相当于MVC中的M。

  • DraweeController(Controller) 负责和 image loader 交互(默认是Fresco中image pipeline),可以创建一个这个类的实例,来实现对所要显示的图片做更多的控制

打开官方demo

ndk绝对路径放在imagepipeline的build里面更改

def getNdkBuildFullPath() {	
    return "/Users/personal/software/android-ndk-r10e/ndk-build" 

问题:

图片不用bitmap的时候,如何管理Bitmap引用 如何从inuse变成evikable

Fresco的 DraweeView在onDetacheWindow或者重新绘制View的时候,会 dropCaches(),就好像主动释放内存,其实是减小引用。

  @Override
  public synchronized void dropCaches() {
    mBitmapsToKeepCached.setAll(false);
    dropBitmapsThatShouldNotBeCached();
    for (Bitmap freeBitmap : mFreeBitmaps) {
      freeBitmap.recycle();
      sTotalBitmaps.decrementAndGet();
    }
    mFreeBitmaps.clear();
    mAnimatedDrawableBackend.dropCaches();
    FLog.v(TAG, "Total bitmaps: %d", sTotalBitmaps.get());
  }
  

  private synchronized void dropBitmapsThatShouldNotBeCached() {
    int index = 0;
    while (index < mCachedBitmaps.size()) {
      int frameNumber = mCachedBitmaps.keyAt(index);
      boolean keepCached = mBitmapsToKeepCached.get(frameNumber);
      if (!keepCached) {
        CloseableReference<Bitmap> bitmapReference = mCachedBitmaps.valueAt(index);
        mCachedBitmaps.removeAt(index);
        bitmapReference.close();
      } else {
        index++;
      }
    }
  }
  
  @Override
  public void close() {
    synchronized (this) {
      if (mIsClosed) {
        return;
      }
      mIsClosed = true;
    }

    mSharedReference.deleteReference();
  }

内存的分配策略是怎么样

引用机制

网络图片下载

public class HttpUrlConnectionNetworkFetcher extends BaseNetworkFetcher<FetchState> {

  private static final int NUM_NETWORK_THREADS = 3;

  private final ExecutorService mExecutorService;

  public HttpUrlConnectionNetworkFetcher() {
    mExecutorService = Executors.newFixedThreadPool(NUM_NETWORK_THREADS);
  }

  @Override
  public FetchState createFetchState(Consumer<EncodedImage> consumer, ProducerContext context) {
    return new FetchState(consumer, context);
  }

  @Override
  public void fetch(final FetchState fetchState, final Callback callback) {
    final Future<?> future = mExecutorService.submit(
        new Runnable() {
          @Override
          public void run() {
            HttpURLConnection connection = null;
            Uri uri = fetchState.getUri();
            String scheme = uri.getScheme();
            String uriString = fetchState.getUri().toString();
            while (true) {
              String nextUriString;
              String nextScheme;
              InputStream is;
              try {
                URL url = new URL(uriString);
                connection = (HttpURLConnection) url.openConnection();
                nextUriString = connection.getHeaderField("Location");
                nextScheme = (nextUriString == null) ? null : Uri.parse(nextUriString).getScheme();
                if (nextUriString == null || nextScheme.equals(scheme)) {
                  is = connection.getInputStream();
                  callback.onResponse(is, -1);
                  break;
                }
                uriString = nextUriString;
                scheme = nextScheme;
              } catch (Exception e) {
                callback.onFailure(e);
                break;
              } finally {
                if (connection != null) {
                  connection.disconnect();
                }
              }
          }

          }
        });
    fetchState.getContext().addCallbacks(
        new BaseProducerContextCallbacks() {
          @Override
          public void onCancellationRequested() {
            if (future.cancel(false)) {
              callback.onCancellation();
            }
          }
        });
  }
}

图片获取逻辑

4.3.1.1 ImagePipeline.fetchDecodedImage() 源码分支1的处理

  • ProducerSequenceFactory.getDecodedImageProducerSequence() 源码 *

返回一个可用于解码图片的请求的序列 先获取一个基本的解码请求的序列,如果imageRequest.getPostprocessor()不是null的话,基于基本请求序列,再获取到一个PostprocessorSequence返回.

  /**
   * Returns a sequence that can be used for a request for a decoded image.
   *
   * @param imageRequest the request that will be submitted
   * @return the sequence that should be used to process the request
   */
  public Producer<CloseableReference<CloseableImage>> getDecodedImageProducerSequence(
      ImageRequest imageRequest) {
    Producer<CloseableReference<CloseableImage>> pipelineSequence =
        getBasicDecodedImageSequence(imageRequest);
    if (imageRequest.getPostprocessor() != null) {
      return getPostprocessorSequence(pipelineSequence);
    } else {
      return pipelineSequence;
    }
  }

ImageRequest的Postprocessor是个什么呢?在说这个之前,还要先说一下ImageRequest,翻看ImageRequest的源码得知,ImageRequest是个javabean, 查看Facebook的类的注释: Immutable object encapsulating everything pipeline has to know about requested image to proceed.可以理解为ImagePipeline中需要获取或者需要得知一切请求图片的信息,包括是否用后处理器处理(后处理这个东西我没仔细看,就不班门弄斧了……) 我们就看一般的处理情况即可,就是获取基本的解码请求序列

  • ProducerSequenceFactory.getBasicDecodedImageSequence() 源码 * 看了这么多,终于看到我们想看到的逻辑了,这里在获取图片时,根据判断请求的uri,网络,本地视频,本地图片,本地assets,数据库等等,生成对应的ImageRequestSequence.

       private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(
            ImageRequest imageRequest) {
          Preconditions.checkNotNull(imageRequest);
    	
          Uri uri = imageRequest.getSourceUri();
          Preconditions.checkNotNull(uri, "Uri is null.");
          if (UriUtil.isNetworkUri(uri)) {
            return getNetworkFetchSequence();
          } else if (UriUtil.isLocalFileUri(uri)) {
            if (MediaUtils.isVideo(MediaUtils.extractMime(uri.getPath()))) {
              return getLocalVideoFileFetchSequence();
            } else {
              return getLocalImageFileFetchSequence();
            }
          } else if (UriUtil.isLocalContentUri(uri)) {
            return getLocalContentUriFetchSequence();
          } else if (UriUtil.isLocalAssetUri(uri)) {
            return getLocalAssetFetchSequence();
          } else if (UriUtil.isLocalResourceUri(uri)) {
            return getLocalResourceFetchSequence();
          } else if (UriUtil.isDataUri(uri)) {
            return getDataFetchSequence();
          } else {
            String uriString = uri.toString();
            if (uriString.length() > 30) {
              uriString = uriString.substring(0, 30) + "...";
            }
            throw new RuntimeException("Unsupported uri scheme! Uri is: " + uriString);
          }
        } #### UI部分
    
        public abstract class AbstractDraweeController<T, INFO> implements
      ...  
          @Override
        public void onAttach() {
          if (FLog.isLoggable(FLog.VERBOSE)) {
            FLog.v(
                TAG,
                "controller %x %s: onAttach: %s",
                System.identityHashCode(this),
                mId,
                mIsRequestSubmitted ? "request already submitted" : "request needs submit");
          }
          mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
          Preconditions.checkNotNull(mSettableDraweeHierarchy);
          mDeferredReleaser.cancelDeferredRelease(this);
          mIsAttached = true;
          if (!mIsRequestSubmitted) {
            submitRequest();
          }
        }
    

PipelineDraweeController部分

	  @Override
	  protected PipelineDraweeController obtainController() {
	    DraweeController oldController = getOldController();
	    PipelineDraweeController controller;
	    if (oldController instanceof PipelineDraweeController) {
	      controller = (PipelineDraweeController) oldController;
	      controller.initialize(
	          obtainDataSourceSupplier(),
	          generateUniqueControllerId(),
	          getCallerContext());
	    } else {
	      controller = mPipelineDraweeControllerFactory.newController(
	          obtainDataSourceSupplier(),
	          generateUniqueControllerId(),
	          getCallerContext());
	    }
	    return controller;
	  }	

DateSource

			   /** Gets the top-level data source supplier to be used by a controller. */
	  protected Supplier<DataSource<IMAGE>> obtainDataSourceSupplier() {
	    if (mDataSourceSupplier != null) {
	      return mDataSourceSupplier;
	    }
	
	    Supplier<DataSource<IMAGE>> supplier = null;
	
	    // final image supplier;
	    if (mImageRequest != null) {
	      supplier = getDataSourceSupplierForRequest(mImageRequest);
	    } else if (mMultiImageRequests != null) {
	      supplier = getFirstAvailableDataSourceSupplier(mMultiImageRequests, mTryCacheOnlyFirst);
	    }
	
	    // increasing-quality supplier; highest-quality supplier goes first
	    if (supplier != null && mLowResImageRequest != null) {
	      List<Supplier<DataSource<IMAGE>>> suppliers = new ArrayList<>(2);
	      suppliers.add(supplier);
	      suppliers.add(getDataSourceSupplierForRequest(mLowResImageRequest));
	      supplier = IncreasingQualityDataSourceSupplier.create(suppliers);
	    }
	
	    // no image requests; use null data source supplier
	    if (supplier == null) {
	      supplier = DataSources.getFailedDataSourceSupplier(NO_REQUEST_EXCEPTION);
	    }
	
	    return supplier;
	  } 	  
	  
	  
	  
	    /** Creates a data source supplier for the given image request. */   protected Supplier<DataSource<IMAGE>> getDataSourceSupplierForRequest(
  final REQUEST imageRequest,
  final boolean bitmapCacheOnly) {
final Object callerContext = getCallerContext();
return new Supplier<DataSource<IMAGE>>() {
  @Override
  public DataSource<IMAGE> get() {
    return getDataSourceForRequest(imageRequest, callerContext, bitmapCacheOnly);
  }
  @Override
  public String toString() {
    return Objects.toStringHelper(this)
        .add("request", imageRequest.toString())
        .toString();
  }
};   }

@Override protected DataSource<CloseableReference> getDataSourceForRequest( ImageRequest imageRequest, Object callerContext, boolean bitmapCacheOnly) { if (bitmapCacheOnly) { return mImagePipeline.fetchImageFromBitmapCache(imageRequest, callerContext); } else { return mImagePipeline.fetchDecodedImage(imageRequest, callerContext); } }

private Consumer createConsumer() { return new BaseConsumer() { @Override protected void onNewResultImpl(@Nullable T newResult, boolean isLast) { AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast); }

  @Override
  protected void onFailureImpl(Throwable throwable) {
    AbstractProducerToDataSourceAdapter.this.onFailureImpl(throwable);
  }

  @Override
  protected void onCancellationImpl() {
    AbstractProducerToDataSourceAdapter.this.onCancellationImpl();
  }

  @Override
  protected void onProgressUpdateImpl(float progress) {
    AbstractProducerToDataSourceAdapter.this.setProgress(progress);
  }
};   }

RequestListener干嘛用的

  FLog.setMinimumLoggingLevel(FLog.VERBOSE);
Set<RequestListener> listeners = new HashSet<>();
listeners.add(new RequestLoggingListener());
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
    .setRequestListeners(listeners)
    .setBitmapsConfig(Bitmap.Config.ARGB_8888)
    .build();
Fresco.initialize(this, config);

UiThreadImmediateExecutorService UI线程Handler 也说明一个问题,就是界面更新,无非是一个Handler(Looper.getMainLooper)

	public class UiThreadImmediateExecutorService extends HandlerExecutorServiceImpl {
	  private static UiThreadImmediateExecutorService sInstance = null;
	
	  private UiThreadImmediateExecutorService() {
	    super(new Handler(Looper.getMainLooper()));
	  }
	
	  public static UiThreadImmediateExecutorService getInstance() {
	    if (sInstance == null) {
	      sInstance = new UiThreadImmediateExecutorService();
	    }
	    return sInstance;
	  }
	
	  @Override
	  public void execute(Runnable command) {
	    if (isHandlerThread()) {
	      command.run();
	    } else {
	      super.execute(command);
	    }
	  }
	}

因为不一定每个都需要开启新线程,所以也就需要重写execute

PipelineDraweeControllerBuilderSupplier

public PipelineDraweeControllerBuilderSupplier(
  Context context,
  ImagePipelineFactory imagePipelineFactory,
  Set<ControllerListener> boundControllerListeners) {
mContext = context;
mImagePipeline = imagePipelineFactory.getImagePipeline();
mPipelineDraweeControllerFactory = new PipelineDraweeControllerFactory(
    context.getResources(),
    DeferredReleaser.getInstance(),
    imagePipelineFactory.getAnimatedDrawableFactory(),
    UiThreadImmediateExecutorService.getInstance());
mBoundControllerListeners = boundControllerListeners;   }

final DataSubscriber dataSubscriber AbstractDraweeController

  protected void submitRequest() {
    mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);
    getControllerListener().onSubmit(mId, mCallerContext);
    mSettableDraweeHierarchy.setProgress(0, true);
    mIsRequestSubmitted = true;
    mHasFetchFailed = false;
    mDataSource = getDataSource();
    if (FLog.isLoggable(FLog.VERBOSE)) {
      FLog.v(
          TAG,
          "controller %x %s: submitRequest: dataSource: %x",
          System.identityHashCode(this),
          mId,
          System.identityHashCode(mDataSource));
    }
    final String id = mId;
    final boolean wasImmediate = mDataSource.hasResult();
    final DataSubscriber<T> dataSubscriber =
        new BaseDataSubscriber<T>() {
          @Override
          public void onNewResultImpl(DataSource<T> dataSource) {
            // isFinished must be obtained before image, otherwise we might set intermediate result
            // as final image.
            boolean isFinished = dataSource.isFinished();
            float progress = dataSource.getProgress();
            T image = dataSource.getResult();
            if (image != null) {
              onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate);
            } else if (isFinished) {
              onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true);
            }
          }
          @Override
          public void onFailureImpl(DataSource<T> dataSource) {
            onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true);
          }
          @Override
          public void onProgressUpdate(DataSource<T> dataSource) {
            boolean isFinished = dataSource.isFinished();
            float progress = dataSource.getProgress();
            onProgressUpdateInternal(id, dataSource, progress, isFinished);
          }
        };
        
        <!--这里是关键,定了订阅者跟更UI线程的绑定-->
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
  }

producer.produceResults(createConsumer(), settableProducerContext);

  public abstract class AbstractProducerToDataSourceAdapter<T> extends AbstractDataSource<T> {

private final SettableProducerContext mSettableProducerContext; private final RequestListener mRequestListener;

protected AbstractProducerToDataSourceAdapter( Producer producer, SettableProducerContext settableProducerContext, RequestListener requestListener) { mSettableProducerContext = settableProducerContext; mRequestListener = requestListener; mRequestListener.onRequestStart( settableProducerContext.getImageRequest(), mSettableProducerContext.getCallerContext(), mSettableProducerContext.getId(), mSettableProducerContext.isPrefetch()); producer.produceResults(createConsumer(), settableProducerContext); }

getDataSourceForRequest 也就是

getDataSourceSupplierForRequest 后面的只是实现getDataSourceForRequest

/** Creates a data source supplier for the given image request. */ protected Supplier<DataSource> getDataSourceSupplierForRequest( final REQUEST imageRequest, final boolean bitmapCacheOnly) { final Object callerContext = getCallerContext(); return new Supplier<DataSource>() { @Override public DataSource get() { return getDataSourceForRequest(imageRequest, callerContext, bitmapCacheOnly); } @Override public String toString() { return Objects.toStringHelper(this) .add("request", imageRequest.toString()) .toString(); } }; }

AbstractExecutorService自身并不存在线程概念 看你的实现

初始化的一些变量

private static void initializeDrawee(Context context) { sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context); SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier); }

Producer体系整体处理的逻辑伪代码

层层下传机制,然后层层回传机制,这里的特点是,省略了代码,网络就处理网络,磁盘就处理磁盘,内存处理内粗,磁盘处理的时候,已经知道内存中没有,网络处理的时候,知道磁盘中没有。

Producer.producerResult    
本身是否可以处理

    --> 可以处理   
                --> 直接向外通知consumer做相关的回调


    --> 不能处理
                --> 是否需要做自身相关的consumer回调的逻辑

                        --> 需要 
                                -->  代理consumer的相关实现(Impl)方法

                        --> 不需要

                                ---> 调用下一个producer来producerResult

Fresco 如何支持Native存储Bitmap Purgeable bitmaps

Ashmem is not directly accessible to Java applications, but there are a few exceptions, and images are one of them. When you create a decoded (uncompressed) image, known as a bitmap, the Android API allows you to specify that the image be purgeable:

BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

Purgeable bitmaps live in ashmem. However, the garbage collector does not automatically reclaim them. Android’s system libraries “pin” the memory when the draw system is rendering the image, and “unpin” it when it’s finished. Memory that is unpinned can be reclaimed by the system at any time. If an unpinned image ever needs to be drawn again, the system will just decode it again, on the fly.

Fresco 如何管理BitMap,安全的recycle Write code in Java, but think like C++

Fresco里的内存分配跟管理都是手动的,包括引用的管理 。安全的释放Bitmap

As we learned from Spider-Man, “With great power comes great responsibility.” Pinned purgeable bitmaps have neither the garbage collector’s nor ashmem’s built-in purging facility to protect them from memory leaks. We are truly on our own.

In C++, the usual solution is to build smart pointer classes that implement reference counting. These make use of C++ language facilities — copy constructors, assignment operators, and deterministic destructors. This syntactic sugar does not exist in Java, where the garbage collector is assumed to be able to take care of everything. So we have to somehow find a way to implement C++-style guarantees in Java.

We made use of two classes to do this. One is simply called SharedReference. This has two methods, addReference and deleteReference, which callers must call whenever they take the underlying object or let it out of scope. Once the reference count goes to zero, resource disposal (such as Bitmap.recycle) takes place.

Yet, obviously, it would be highly error-prone to require Java developers to call these methods. Java was chosen as a language to avoid doing this! So on top of SharedReference, we built CloseableReference. This implements not only the Java Closeable interface, but Cloneable as well. The constructor and the clone() method call addReference(), and the close() method calls deleteReference(). So Java developers need only follow two simple rules:

On assigning a CloseableReference to a new object, call .clone(). Before going out of scope, call .close(), usually in a finally block. These rules have been effective in preventing memory leaks, and have let us enjoy native memory management in large Java applications like Facebook for Android and Messenger for Android.

Fresco 的图片接器 ArtDecoder 与 KitKatPurgeableDecoder、GingerbreadPurgeableDecoder

  public static PlatformDecoder buildPlatformDecoder(
      PoolFactory poolFactory,
      boolean decodeMemoryFileEnabled,
      boolean webpSupportEnabled) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      return new ArtDecoder(
          poolFactory.getBitmapPool(),
          poolFactory.getFlexByteArrayPoolMaxNumThreads());
    } else {
      if (decodeMemoryFileEnabled && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        return new GingerbreadPurgeableDecoder(webpSupportEnabled);
      } else {
        return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
      }
    }
  }

Fresco异步更新原理 还是Handler mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);

Fresco 如何实现Simpedreew的绑定 ListView

以前比较Low的做法是ImageView的tag,但是这样影响正常Tag的使用,分理出Controller的好处就是交给Controller处理,不影响原来的Image

  /**
   * Sets a new controller.
   */
   
  public void setController(@Nullable DraweeController draweeController) {
    boolean wasAttached = mIsControllerAttached;
    if (wasAttached) {
      detachController();
    }

    // Clear the old controller
    if (mController != null) {
      mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
      mController.setHierarchy(null);
    }
    mController = draweeController;
    if (mController != null) {
      mEventTracker.recordEvent(Event.ON_SET_CONTROLLER);
      mController.setHierarchy(mHierarchy);
    } else {
      mEventTracker.recordEvent(Event.ON_CLEAR_CONTROLLER);
    }

    if (wasAttached) {
      attachController();
    }
  }

获取DataSource的时候,如何处理后续流程

    mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
    getControllerListener().onFinalImageSet(id, getImageInfo(image), getAnimatable());
    // IMPORTANT: do not execute any instance-specific code after this point
  } else {
    logMessageAndImage("set_intermediate_result @ onNewResult", image);
    mSettableDraweeHierarchy.setImage(drawable, progress, wasImmediate);

Fresco MVC

DraweeView把事件派发给DraweeController,由DraweeController来决定DraweeHierarchy中存储的那种图片来显示(就是getTopLevelDrawable() 返回的Drawable)。至于为什么要使用DraweeHolder,注释说了,这个就是为了解耦了,如果你不想使用DraweeView,通过DraweeHolder还是很方便使用另外两个组件的。

DraweeHierarchy分析及原理

五层架构

  private void fadeOutBranches() {
    fadeOutLayer(mPlaceholderImageIndex);
    fadeOutLayer(mActualImageIndex);
    fadeOutLayer(mProgressBarImageIndex);
    fadeOutLayer(mRetryImageIndex);
    fadeOutLayer(mFailureImageIndex);
  }

基于Drawable的callback进行回调完成

GenericDraweeHierarchy 与FadeDrawable 图层概念的封装

Drawable invalidate-》draw-》 invalidateSelf() -》回调View invalidate 绘制 Drawable的Draw都是处理数据,自身不做绘制,绘制交给View。

  @Override
  public void draw(Canvas canvas) {
    mCurrentDelegate.draw(canvas);
  }
  
  实际设置的drawable在这里生效

总结一下好的模式

bulder delegate Producer subscribe 下发上浮 生产下发,消费上浮下发

参考文档

官方文档 配置和使用

Fresco原理分析

Fresco介绍 - 一个新的android图片加载库

Introducing Fresco: A new image library for Android

Fresco源码阅读(一)

Search

    Table of Contents