http://blog.csdn.net/lpw14/article/details/51260153
Fresco是Facebook推出的一款用于Android应用的强大图片库,相对于其他主流的图片库而言,Fresco的主要优秀特性的在于缓存和内存控制上,当然有利就有弊,Fresco的代码量大,方法数较多,阅读难度比较大,下面我们就从三个方面入手来分析Fresco
一、初始化配置
二、图片的加载与缓存
三、图片的绘制显示
关于Fresco中的一些概念和使用方法,见Fresco的官方文档http://fresco-cn.org/docs/index.html
一、初始化配置
使用Fresco的第一步,就是进行初始化,Fresco提供了两种初始化方式,一种是使用默认配置初始化,一种是使用用户自定义配置初始化。就使用而言,默认配置能完全满足我们的需要,但如果想要做更多的控制,那么可以使用自定义的配置。当然,使用自定义配置的前提,是我们要了解Fresco都提供了哪些配置,哪些配置需要我们自定义,哪些可以使用默认的配置。
默认配置初始化:
- /** Initializes Fresco with the default config. */
- public static void initialize(Context context) {
- ImagePipelineFactory.initialize(context);
- initializeDrawee(context);
- }
自定义配置初始化:
- /** Initializes Fresco with the specified config. */
- public static void initialize(Context context, ImagePipelineConfig imagePipelineConfig) {
- ImagePipelineFactory.initialize(imagePipelineConfig);
- initializeDrawee(context);
- }
这两种的区别就在于ImagePipelineConfig参数,此类是主配置类,主要提供了内存缓存配置,未解码的内存缓存配置,磁盘缓存配置,网络请求配置,线程池配置,缓存Key工厂配置,Pool工厂配置,渐进式Jpeg配置等等。也就是说我们在自定义配置的时候,这些都是可以自定义的,只要实现相应的接口及其接口方法即可,具体可参考默认的实现类,这里不对这些配置一一分析。
我们看默认初始化实现:
- /** Initializes {@link ImagePipelineFactory} with default config. */
- public static void initialize(Context context) {
- initialize(ImagePipelineConfig.newBuilder(context).build());
- }
使用builder模式构建出ImagePipelineConfig,然后进行初始化。在ImagePipelineConfig的构造方法中,那些必要的配置,如果是空实现,那么都赋值为默认的实现。因此我们在自定义配置时,不用每个配置都去自定义设置。在自定义配置时,通过ImagePipelineConfig.newBuilder(context)构建出ImagePipelineConfig.Builder类,根据其提供的方法设置自己对应的实现即可。
ImagePipelineConfig初始化完成后,接着调用了initializeDrawee(context); 我们看下这一步是做什么的。
- private static void initializeDrawee(Context context) {
- sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context);
- SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
- }
调用了SimpleDraweeView的静态初始化方法,其接受一个Supplier<? extends SimpleDraweeControllerBuilder>的参数,这个Supplier是一个接口,仅有一个get()方法,用来提供一个实例对象。在SimpleDraweeView构造方法被调用时,会调用Supplier的get()方法,将其返回的实例对象(这里是PipelineDraweeControllerBuilder)保存到SimpleDraweeView的成员变量中供后续使用。
其实initializeDrawee(context)方法提供了一种默认的实现,即PipelineDraweeControllerBuilder,Fresco同时给我们提供了另一个实现VolleyDraweeControllerBuilder,我们也可以更换为这个,当然更换后的实现细节,也都不太一样了。甚至如果我们愿意,可以自己写一个实现,只要继承AbstractDraweeControllerBuilder抽象类即可,前提是要对后续逻辑流程有足够的了解。
回头来看,初始化过程并不复杂,主要是ImagePipelineConfig的初始化构造和提供SimpleDraweeControllerBuilder的实现类。Fresco类其实就是一个入口封装,方便外部统一调用,我们完全可以不用这个类或者自己写一个封装类去实现自定义的初始化操作。
从初始化的流程,我们可以看到Fresco严格遵循面向对象的设计思想,模块分离,面向接口编程,各种设计模式的使用等都非常值得我们学习借鉴。当我们在做一个库供别人使用时,也应该像这样,定义好接口,提供一些默认的实现,方便使用者自定义自己的实现。
二、图片的加载与缓存
初始化工作做好之后,接下来就是加载显示图片了,这块是重头戏。Fresco与其他图片库最大的不同之处就是它是控件级的,自定义了一个用于显示静态图片或动画图片的视图DraweeView,我们只需要在布局中使用SimpleDraweeView替换ImageView即可,其中SimpleDraweeView在创建的时,其父类GenericDraweeView的构造方法中会构建GenericHierarchy并设置给DraweeHolder,GenericHierarchy是做什么的,我们后面会说明。
加载显示图片非常简单,调用SimpleDraweeView的以下两个方法即可
- setImageURI(Uri uri);
- setImageURI(Uri uri, @Nullable Object callerContext);
- /**
- * Displays an image given by the uri.
- *
- * @param uri uri of the image
- * @undeprecate
- */
- @Override
- public void setImageURI(Uri uri) {
- setImageURI(uri, null);
- }
- /**
- * Displays an image given by the uri.
- *
- * @param uri uri of the image
- * @param callerContext caller context
- */
- public void setImageURI(Uri uri, @Nullable Object callerContext) {
- DraweeController controller = mSimpleDraweeControllerBuilder
- .setCallerContext(callerContext)
- .setUri(uri)
- .setOldController(getController())
- .build();
- setController(controller);
- }
一图胜千言,看图
- /** Builds the specified controller. */
- @Override
- public AbstractDraweeController build() {
- validate();
- // if only a low-res request is specified, treat it as a final request.
- if (mImageRequest == null && mMultiImageRequests == null && mLowResImageRequest != null) {
- mImageRequest = mLowResImageRequest;
- mLowResImageRequest = null;
- }
- return buildController();
- }
- /** Builds a regular controller. */
- protected AbstractDraweeController buildController() {
- AbstractDraweeController controller = obtainController();
- controller.setRetainImageOnFailure(getRetainImageOnFailure());
- maybeBuildAndSetRetryManager(controller);
- maybeAttachListeners(controller);
- return controller;
- }
- @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;
- }
- 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();
- }
- }
- /** Sets the controller. */
- public void setController(@Nullable DraweeController draweeController) {
- mDraweeHolder.setController(draweeController);
- super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
- }
- /**
- * 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();
- }
- }
将draweeController赋值给DraweeHolder的成员变量mController,然后根据是否被Attached,来执行相应的操作。
这里补充一点,DraweeView会根据其当前是否位于屏幕的可视区域来决定图片的加载与否,比如view在滑出屏幕可视区域会停止加载,如果图片正在下载,则会取消。
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mDraweeHolder.onAttach();
- }
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mDraweeHolder.onDetach();
- }
- @Override
- public void onStartTemporaryDetach() {
- super.onStartTemporaryDetach();
- mDraweeHolder.onDetach();
- }
- @Override
- public void onFinishTemporaryDetach() {
- super.onFinishTemporaryDetach();
- mDraweeHolder.onAttach();
- }
- @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();
- }
- }
- 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);
- }
- };
- mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
- }
- @Override
- protected DataSource<CloseableReference<CloseableImage>> getDataSource() {
- return mDataSourceSupplier.get();
- }
- /** 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<CloseableImage>> getDataSourceForRequest(
- ImageRequest imageRequest,
- Object callerContext,
- boolean bitmapCacheOnly) {
- if (bitmapCacheOnly) {
- return mImagePipeline.fetchImageFromBitmapCache(imageRequest, callerContext);
- } else {
- return mImagePipeline.fetchDecodedImage(imageRequest, callerContext);
- }
- }
- /**
- * Submits a request for execution and returns a DataSource representing the pending decoded
- * image(s).
- * <p>The returned DataSource must be closed once the client has finished with it.
- * @param imageRequest the request to submit
- * @return a DataSource representing the pending decoded image(s)
- */
- public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
- ImageRequest imageRequest,
- Object callerContext) {
- try {
- Producer<CloseableReference<CloseableImage>> producerSequence =
- mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
- return submitFetchRequest(
- producerSequence,
- imageRequest,
- ImageRequest.RequestLevel.FULL_FETCH,
- callerContext);
- } catch (Exception exception) {
- return DataSources.immediateFailedDataSource(exception);
- }
- }
- private <T> DataSource<CloseableReference<T>> submitFetchRequest(
- Producer<CloseableReference<T>> producerSequence,
- ImageRequest imageRequest,
- ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
- Object callerContext) {
- try {
- ImageRequest.RequestLevel lowestPermittedRequestLevel =
- ImageRequest.RequestLevel.getMax(
- imageRequest.getLowestPermittedRequestLevel(),
- lowestPermittedRequestLevelOnSubmit);
- SettableProducerContext settableProducerContext = new SettableProducerContext(
- imageRequest,
- generateUniqueFutureId(),
- mRequestListener,
- callerContext,
- lowestPermittedRequestLevel,
- /* isPrefetch */ false,
- imageRequest.getProgressiveRenderingEnabled() ||
- !UriUtil.isNetworkUri(imageRequest.getSourceUri()),
- imageRequest.getPriority());
- return CloseableProducerToDataSourceAdapter.create(
- producerSequence,
- settableProducerContext,
- mRequestListener);
- } catch (Exception exception) {
- return DataSources.immediateFailedDataSource(exception);
- }
- }
总结下这个流程:
1. 当调用SimpleDraweeView的setImageUri(uri)方法时,PipelineDraweeControllerBuilder通过setUri(uri)生成不可变的ImageReqeust成员对象,通过build()方法构建PipelineDraweeController实例对象(创建的Supplier<DataSource<IMAGE>>作为其成员变量),然后将controller设置给DraweeHolder;
2. 当View处于屏幕的可视区域时,会执行controller的onAttach()方法,获取DataSource并绑定订阅者DataSubscriber。
整理下思路回来,我们接着分析,到目前为止还没看到时怎么去加载图片的呀,现在我们去看如何去加载图片,记得我们前面提到的在创建DataSource时传入的一个重要参数producerSequence吗,加载图片的关键流程要登场了。Fresco使用了producer(生产者) — consumer(消费者)模式加载和缓存图片的,producerSequence看名字就知道它应该持有了一系列生产者的序列,其内部代码我们在这不做分析,它内部采用了包装模式,一个producer包装另一个producer,类似于Java中的InputStream,调用关系也是一级一级的调用,consumer是在外面创建,并传递给producer的produceResults方法,在某个producer中如果consumer需要包装,那么包装一层再传递给下一个producer的produceResults方法。
Producer的包装过程,主要涉及两个类,ProducerSequenceFactory,ProducerFactory,前者用来获取经过包装的producer,后者获取具体的单个producer。
producer--生产者,负责生产获取结果,从内存,未解码内存,磁盘,网络等获取数据
consumer–消费者,负责消费使用结果,解码,变换,返回。存到各个缓存中。
前面在创建DataSource时使用了CloseableProducerToDataSourceAdapter.create()的静态方法,创建了一个CloseableProducerToDataSourceAdapter的实例对象(实现了DataSource接口),我们看其父类AbstractProducerToDataSourceAdapter的构造方法
- protected AbstractProducerToDataSourceAdapter(
- Producer<T> producer,
- SettableProducerContext settableProducerContext,
- RequestListener requestListener) {
- mSettableProducerContext = settableProducerContext;
- mRequestListener = requestListener;
- mRequestListener.onRequestStart(
- settableProducerContext.getImageRequest(),
- mSettableProducerContext.getCallerContext(),
- mSettableProducerContext.getId(),
- mSettableProducerContext.isPrefetch());
- producer.produceResults(createConsumer(), settableProducerContext);
- }
1. BitmapMemoryCacheGetProducer 只读内存缓存的producer
2. ThreadHandoffProducer 启动线程的producer,后续的producer都在线程中执行
3. BitmapMemoryCacheKeyMultiplexProducer 使用memory cache key合并请求的producer
4. BitmapMemoryCacheProducer 读取内存缓存的producer
5. DecodeProducer 解码图片的producer,渐进式JPEG图片,gif和webp等动画图片的解码
6. ResizeAndRotateProducer JPEG图片resizes和rotates处理
7. AddImageTransformMetaDataProducer 主要包装解码的consumer,并传递到下一个producer
8. EncodedCacheKeyMultiplexProducer 使用encoded cache key合并请求的producer
9. EncodedMemoryCacheProducer 读取未解码的内存缓存的producer
10. DiskCacheProducer 读取磁盘缓存的producer
11. WebpTranscodeProducer 包装转码WebP到JPEG/PNG的consumer,并传递到下一个producer
12. NetworkFetchProducer 网络请求的producer
2. 后台线程开始后续工作
3. 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
4. 检查是否在文件缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
5. 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
每个过程的具体细节不在这里分析了,有兴趣的话仔细研读。
回到AbstractDraweeController类的submitRequest()方法,数据订阅者DataSubscriber在收到正常的数据结果提醒时,如下处理:
- private void onNewResultInternal(
- String id,
- DataSource<T> dataSource,
- @Nullable T image,
- float progress,
- boolean isFinished,
- boolean wasImmediate) {
- // ignore late callbacks (data source that returned the new result is not the one we expected)
- if (!isExpectedDataSource(id, dataSource)) {
- logMessageAndImage("ignore_old_datasource @ onNewResult", image);
- releaseImage(image);
- dataSource.close();
- return;
- }
- mEventTracker.recordEvent(
- isFinished ? Event.ON_DATASOURCE_RESULT : Event.ON_DATASOURCE_RESULT_INT);
- // create drawable
- Drawable drawable;
- try {
- drawable = createDrawable(image);
- } catch (Exception exception) {
- logMessageAndImage("drawable_failed @ onNewResult", image);
- releaseImage(image);
- onFailureInternal(id, dataSource, exception, isFinished);
- return;
- }
- T previousImage = mFetchedImage;
- Drawable previousDrawable = mDrawable;
- mFetchedImage = image;
- mDrawable = drawable;
- try {
- // set the new image
- if (isFinished) {
- logMessageAndImage("set_final_result @ onNewResult", image);
- mDataSource = null;
- 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);
- getControllerListener().onIntermediateImageSet(id, getImageInfo(image));
- // IMPORTANT: do not execute any instance-specific code after this point
- }
- } finally {
- if (previousDrawable != null && previousDrawable != drawable) {
- releaseDrawable(previousDrawable);
- }
- if (previousImage != null && previousImage != image) {
- logMessageAndImage("release_previous_result @ onNewResult", previousImage);
- releaseImage(previousImage);
- }
- }
- }
- @Override
- protected Drawable createDrawable(CloseableReference<CloseableImage> image) {
- Preconditions.checkState(CloseableReference.isValid(image));
- CloseableImage closeableImage = image.get();
- if (closeableImage instanceof CloseableStaticBitmap) {
- CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage;
- BitmapDrawable bitmapDrawable = new BitmapDrawable(
- mResources,
- closeableStaticBitmap.getUnderlyingBitmap());
- if (closeableStaticBitmap.getRotationAngle() == 0 ||
- closeableStaticBitmap.getRotationAngle() == EncodedImage.UNKNOWN_ROTATION_ANGLE) {
- return bitmapDrawable;
- } else {
- return new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle());
- }
- } else if (closeableImage instanceof CloseableAnimatedImage) {
- return mAnimatedDrawableFactory.create(
- ((CloseableAnimatedImage) closeableImage).getImageResult());
- } else {
- throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage);
- }
- }
如果是静态图片则创建BitmapDrawable,否则如果是动画图片,则创建包含多帧AnimatedDrawable。
最后将Drawable设置给DraweeHierarchy,由DraweeHierarchy负责Drawable的绘制工作。
三、图片的绘制显示
终于到图片的绘制了,接着上一节,AbstractDraweeController将Drawable设置给SettableDraweeHierarchy,其唯一实现类为GenericDraweeHierarchy,我们看它的setImage方法
- @Override
- public void setImage(Drawable drawable, float progress, boolean immediate) {
- drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
- drawable.mutate();
- mActualImageWrapper.setDrawable(drawable);
- mFadeDrawable.beginBatchMode();
- fadeOutBranches();
- fadeInLayer(mActualImageIndex);
- setProgress(progress);
- if (immediate) {
- mFadeDrawable.finishTransitionImmediately();
- }
- mFadeDrawable.endBatchMode();
- }
- GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
- mResources = builder.getResources();
- mRoundingParams = builder.getRoundingParams();
- mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);
- int numBackgrounds = (builder.getBackgrounds() != null) ? builder.getBackgrounds().size() : 0;
- int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 0;
- numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0;
- // layer indices and count
- int numLayers = 0;
- int backgroundsIndex = numLayers;
- numLayers += numBackgrounds;
- mPlaceholderImageIndex = numLayers++;
- mActualImageIndex = numLayers++;
- mProgressBarImageIndex = numLayers++;
- mRetryImageIndex = numLayers++;
- mFailureImageIndex = numLayers++;
- int overlaysIndex = numLayers;
- numLayers += numOverlays;
- // array of layers
- Drawable[] layers = new Drawable[numLayers];
- if (numBackgrounds > 0) {
- int index = 0;
- for (Drawable background : builder.getBackgrounds()) {
- layers[backgroundsIndex + index++] = buildBranch(background, null);
- }
- }
- layers[mPlaceholderImageIndex] = buildBranch(
- builder.getPlaceholderImage(),
- builder.getPlaceholderImageScaleType());
- layers[mActualImageIndex] = buildActualImageBranch(
- mActualImageWrapper,
- builder.getActualImageScaleType(),
- builder.getActualImageFocusPoint(),
- builder.getActualImageMatrix(),
- builder.getActualImageColorFilter());
- layers[mProgressBarImageIndex] = buildBranch(
- builder.getProgressBarImage(),
- builder.getProgressBarImageScaleType());
- layers[mRetryImageIndex] = buildBranch(
- builder.getRetryImage(),
- builder.getRetryImageScaleType());
- layers[mFailureImageIndex] = buildBranch(
- builder.getFailureImage(),
- builder.getFailureImageScaleType());
- if (numOverlays > 0) {
- int index = 0;
- if (builder.getOverlays() != null) {
- for (Drawable overlay : builder.getOverlays()) {
- layers[overlaysIndex + index++] = buildBranch(overlay, null);
- }
- }
- if (builder.getPressedStateOverlay() != null) {
- layers[overlaysIndex + index] = buildBranch(builder.getPressedStateOverlay(), null);
- }
- }
- // fade drawable composed of layers
- mFadeDrawable = new FadeDrawable(layers);
- mFadeDrawable.setTransitionDuration(builder.getFadeDuration());
- // rounded corners drawable (optional)
- Drawable maybeRoundedDrawable =
- WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
- // top-level drawable
- mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);
- mTopLevelDrawable.mutate();
- resetFade();
- }
还记得GenericDraweeHierarchy是在GenericDraweeView中被build出来并设置给DraweeHolder的吗,我们看下其setHierarchy(hierarchy)方法
- /** Sets the hierarchy. */
- public void setHierarchy(DH hierarchy) {
- mDraweeHolder.setHierarchy(hierarchy);
- super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
- }
- /**
- * Sets a drawable as the content of this ImageView.
- *
- * @param drawable the Drawable to set, or {@code null} to clear the
- * content
- */
- public void setImageDrawable(@Nullable Drawable drawable) {
- if (mDrawable != drawable) {
- mResource = 0;
- mUri = null;
- final int oldWidth = mDrawableWidth;
- final int oldHeight = mDrawableHeight;
- updateDrawable(drawable);
- if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
- requestLayout();
- }
- invalidate();
- }
- }
- private void updateDrawable(Drawable d) {
- if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
- mRecycleableBitmapDrawable.setBitmap(null);
- }
- if (mDrawable != null) {
- mDrawable.setCallback(null);
- unscheduleDrawable(mDrawable);
- }
- mDrawable = d;
- if (d != null) {
- d.setCallback(this);
- d.setLayoutDirection(getLayoutDirection());
- if (d.isStateful()) {
- d.setState(getDrawableState());
- }
- d.setVisible(getVisibility() == VISIBLE, true);
- d.setLevel(mLevel);
- mDrawableWidth = d.getIntrinsicWidth();
- mDrawableHeight = d.getIntrinsicHeight();
- applyImageTint();
- applyColorMod();
- configureBounds();
- } else {
- mDrawableWidth = mDrawableHeight = -1;
- }
- }
- public ForwardingDrawable(Drawable drawable) {
- mCurrentDelegate = drawable;
- DrawableUtils.setCallbacks(mCurrentDelegate, this, this);
- }
mFadeDrawable创建时传入了drawable的数组,其父类ArrayDrawable的构造方法中,对数组中的每个drawable的callback设置为this
- public ArrayDrawable(Drawable[] layers) {
- Preconditions.checkNotNull(layers);
- mLayers = layers;
- for (int i = 0; i < mLayers.length; i++) {
- DrawableUtils.setCallbacks(mLayers[i], this, this);
- }
- mDrawableParents = new DrawableParent[mLayers.length];
- }
看下图来了解类之间关系和callback链
也就是说ArrayDrawable中mLayers的Drawable数组中的每个drawable都间接与ImageView建立了联系,其callback的调用关系为的责任链模式,一级一级往上层调用。
看到这里我们在回到本节的开始出,GenericDraweeHierarchy的setImage方法,mActualImageWrapper为实际要绘制的图片,其位于mFadeDrawable的mLayers数组中,fadeInLayer(mActualImageIndex)设置要绘制的Drawable的索引,fendBatchMode()方法中调用了invalidateSelf(),此方法触发调用callback,然后callback链一级一级网上调用直到ImageView,ImageView最终调用onDraw方法,交由Drawable进行绘制,最终会调用到FadeDrawable的draw(canvas)方法,在其方法内部绘制mLayers数组中被标记的Drawable,至此drawable将被绘制到canvas上在屏幕上呈现。如果最终要绘制的Drawable为AnimatedDrawable(gif,webP等动画图片),那么调用其draw(canvas)方法将不断地绘制每一帧。
一些补充:
1、Pool模型
Fresco中有个Pool和Bucket的概念,可理解为缓存池,类似与线程池,即当需要分配一块内存的时候,先从Pool中取,如果有相应大小Bucket,则返回,没有的话看是否满足条件可分配,如果满足则分配对应大小的内存并返回,否则抛出异常。当得到对应的Bucket后获取其FreeList中块大小的值,如果没有,则新分配内存并使用,使用完之后返回到Bucket的FreeList中。
Pool是在初始化阶段完成配置,包括有几个Pool,每个Pool大小的软限制,硬限制,和包含多少个Bucket,每个Bucket的itemSize大小以及item的个数等,看下图来理解
2、Image Decoder
Fresco中图片的解码操作根据不同的系统版本而有所不同,在4.4以下的版本中(sdk<19)解码时先将数据copy到Ashmem区域,然后进行解码;4.4版本由于使用MemoryFile不能work,而使用java内存来存储未解码image,使用BitmapFactory.decodeByteArray方法解码数据,不能从stream解码,因为不支持purgeable;5.0及以上版本使用BitmapFactory.decodeStream方法解码。
3、引用计数CloseableReference
Fresco中使用了一种引用计数的方式来管理内存,主要为了解决Ashmem区域的Bitmap内存的分配管理
以下内容摘自网上:
Ashmem:
在Android系统里面,Ashmem这个区域的内存并不属于Java Heap,也不属于Native Heap。而Ashmem的使用,又有一点像Java的垃圾回收。
当Ashmem中的某个内存空间像要被释放时候,会通过系统调用unpin来告知,但实际上这块内存空间的数据并没有被真正的擦除。
如果Android系统发现内存吃紧时,就会把unpin的内存空间利用起来去存储所需的数据。
而被unpin的内存空间,是可以被重新pin的,如果此时的该内存空间还没有被其他人使用的话,就节省了重新往Ashmem重新写入数据的过程了。
所以Ashmem这个工作原理是一种延迟释放。
Bitmap在Ashmem中的使用
Ashmem内存区域是不能被Java应用直接使用的,但这其中有一些例外,而Bitmap是其中一个。
BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
Purgeable被设置成true以后,这个Bigmap就是保存在Ashmem内存区域中的,Java的垃圾回收是不能回收这片区域的内存的。
当Android系统需要渲染这个Bitmap的时候,会调用pin,渲染完成后会调用unpin。而unpin后的内存空间表示能被其他人所使用。
如果被unpin的Bitmap需要重新渲染,系统会再次Decode这个Bitmap,而这个Decode的过程是在UI线程上完成的,Bitmap decode是非常消耗CPU资源的,当消耗过大时会引起UI阻塞,所以Google后来废弃了这个purgeable的参数。
后来Google提供了另外一个Flag,叫inBitmap。很遗憾的是,直到Android4.4后,这个新的Flag才得到完善。而Fresco致力于实现一个包括Android2.3以及以上的Android系统都能完美工作的图片加载管理开源库,因此Fresco放弃了使用inBitmap的解决方案。
Fresco是如何利用Ashmem去给Bitmap分配和管理内存?
上面说到的pin和unpin两个操作,对应的NDK调用是AndroidBitmap_lockPixels和unlockPixels。按照我们一惯认知,为了避免内存泄漏,这两者必须成对出现。而Fresco为了避免Bitmap再次渲染而导致的在UI线程Decode的过程,偏偏不在渲染完成后调用unlockPixels。
这就需要Fresco自己去管理这块内存区域,保证当这个Bitmap不再使用时,Ashmem的内存空间能被unpin。
而Fresco选择在Bitmap离开屏幕可视范围时候(onDetachWindow等时候),去做unpin。