详细分析 Fresco 的 SimpleDraweeView 的实现

187 阅读7分钟

结合 Fresco 源码,详细讲解 SimpleDraweeView 的显示机制,分析其设计原理、实现细节、工作流程以及在图片加载和渲染中的作用。SimpleDraweeView 是 Fresco 提供的一个轻量级视图组件,用于高效加载和显示图片,集成了 Fresco 的图片加载管道(ImagePipeline)、缓存机制和渐进式渲染功能。


  1. SimpleDraweeView 概述

SimpleDraweeView 是 Fresco 的核心 UI 组件,继承自 GenericDraweeView,用于在 Android 应用中加载和显示图片。它封装了 Fresco 的复杂功能(如图片加载、缓存、渐进式渲染),为开发者提供简单易用的 API,同时支持高级特性(如占位图、失败图、圆角、渐进式 JPEG)。

核心功能:

  • 图片加载:通过 ImagePipeline 从网络、缓存或本地加载图片。
  • 渐进式渲染:支持渐进式 JPEG,逐步显示图片。
  • 占位图和失败图:支持加载中、失败等状态的图片显示。
  • 样式支持:支持圆形、圆角、缩放类型等 UI 定制。
  • 内存管理:结合 CloseableReference,确保 Bitmap 安全释放。

源码位置:

  • 定义在 com.facebook.drawee.view.SimpleDraweeView。

  • 核心相关类:

    • GenericDraweeView:父类,定义基础视图逻辑。
    • DraweeController:控制图片加载和渲染。
    • DraweeHierarchy:管理图片层级(如占位图、实际图片)。
    • ImagePipeline:图片加载管道。
    • CloseableReference:管理 Bitmap 内存。

  1. SimpleDraweeView 设计原理

SimpleDraweeView 的设计基于 MVC 模式(Model-View-Controller):

  • Model:ImagePipeline 提供图片数据,管理加载和缓存。
  • View:SimpleDraweeView 及其 DraweeHierarchy,负责图片渲染。
  • Controller:DraweeController 协调数据加载和 UI 更新。

关键机制:

  1. DraweeHierarchy:

    • 管理图片层级(如占位图、实际图片、失败图),使用 Drawable 树结构。
    • 支持动态切换图片(如渐进式渲染的低质量到高质量)。
  2. DraweeController:

    • 控制图片加载流程,与 ImagePipeline 交互。
    • 处理加载状态(开始、进度、成功、失败)。
  3. ImagePipeline:

    • 提供图片数据,从缓存或网络获取。
    • 支持渐进式 JPEG 和多级缓存。
  4. CloseableReference:

    • 管理解码后的 Bitmap,确保内存安全。
  5. Event Handling:

    • 通过 DataSubscriber 监听加载事件,更新 UI。

工作原理:

  • 用户设置图片 URI,SimpleDraweeView 创建 ImageRequest 和 DraweeController。
  • DraweeController 通过 ImagePipeline 加载图片,获取 CloseableReference。
  • DraweeHierarchy 渲染图片,支持占位图、渐进式更新等。
  • 加载完成后,释放资源,确保内存安全。

  1. SimpleDraweeView 显示流程

以下是 SimpleDraweeView 显示图片的完整流程,结合源码分析:

3.1 初始化

  • SimpleDraweeView 在布局或代码中初始化:

    xml

    <com.facebook.drawee.view.SimpleDraweeView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        fresco:placeholderImage="@drawable/placeholder" />
    
  • 构造方法初始化 DraweeHierarchy 和 DraweeController:

    java

    public class SimpleDraweeView extends GenericDraweeView {
        public SimpleDraweeView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            if (!isInEditMode()) {
                DraweeHolder draweeHolder = DraweeHolder.create(null, context);
                setHierarchy(draweeHolder.getHierarchy());
                setController(draweeHolder.getController());
            }
        }
    }
    
  • 关键逻辑:

    • DraweeHolder 管理 DraweeHierarchy 和 DraweeController。
    • setHierarchy 设置图片层级,setController 设置加载控制器。

3.2 设置图片 URI

  • 用户调用 setImageURI 触发图片加载:

    java

    simpleDraweeView.setImageURI("https://example.com/image.jpg");
    
  • 源码:

    java

    public void setImageURI(String uriString) {
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(uriString))
            .build();
        setController(Fresco.newDraweeControllerBuilder()
            .setImageRequest(request)
            .setOldController(getController())
            .build());
    }
    
  • 关键逻辑:

    • 创建 ImageRequest,封装 URI 和加载选项(如渐进式渲染)。
    • 使用 DraweeControllerBuilder 创建新的 DraweeController。

3.3 DraweeController 创建

  • AbstractDraweeControllerBuilder 创建 DraweeController:

    java

    public class PipelineDraweeControllerBuilder extends AbstractDraweeControllerBuilder {
        @Override
        public PipelineDraweeController build() {
            ImageRequest request = getImageRequest();
            DataSource<CloseableReference<CloseableImage>> dataSource =
                mImagePipeline.fetchDecodedImage(request, getCallerContext());
            return new PipelineDraweeController(
                getResourcesSupplier(),
                getDeferredReleaser(),
                mImagePipeline.getAnimatedDrawableFactory(),
                dataSource);
        }
    }
    
  • 关键逻辑:

    • ImagePipeline 创建 DataSource 加载图片。
    • PipelineDraweeController 管理加载和渲染流程。

3.4 图片加载

  • ImagePipeline 通过 Producer 链加载图片:

    java

    public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest request) {
        CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(request);
        CloseableReference<CloseableImage> cachedImage = mBitmapMemoryCache.get(cacheKey);
        if (cachedImage != null) {
            return ImmediateDataSource.create(cachedImage);
        }
        return mProducerSequenceFactory.getDecodedImageProducerSequence(request).produceResults();
    }
    
  • 加载顺序:

    • 检查 BitmapMemoryCache(Ashmem 或堆内存)。
    • 检查 EncodedMemoryCache 和 DiskCache。
    • 从网络或本地加载,解码为 CloseableImage。

3.5 渐进式渲染(可选)

  • 如果启用渐进式 JPEG,ProgressiveJpegDecoder 逐步解码:

    java

    public CloseableImage decode(EncodedImage encodedImage, int length) {
        int nextScan = mJpegDecoder.getNextScanNumber(encodedImage.getPooledByteBuffer(), length);
        if (nextScan > mLastDecodedScan) {
            Bitmap bitmap = mJpegDecoder.decodeToBitmap(encodedImage, nextScan);
            mLastDecodedScan = nextScan;
            return new CloseableStaticBitmap(bitmap, bitmapReleaser);
        }
        return null;
    }
    
  • DataSource 通知每次解码结果:

    java

    dataSource.subscribe(new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
        @Override
        public void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
            handleNewResult(dataSource.getResult(), dataSource.getProgress());
        }
    });
    

3.6 图片渲染

  • PipelineDraweeController 更新 DraweeHierarchy:

    java

    public class PipelineDraweeController extends AbstractDraweeController {
        @Override
        protected void onNewResultInternal(
            CloseableReference<CloseableImage> image,
            float progress,
            boolean isFinal) {
            if (CloseableReference.isValid(image)) {
                mSettableDraweeHierarchy.setImage(image.get(), progress, isFinal);
            }
        }
    }
    
  • SettableDraweeHierarchy 设置图片:

    java

    public class SettableDraweeHierarchy implements DraweeHierarchy {
        private final FadeDrawable mFadeDrawable;
    
        public void setImage(CloseableImage image, float progress, boolean immediate) {
            Drawable drawable = image.getUnderlyingDrawable();
            mFadeDrawable.setDrawable(drawable, progress, immediate);
        }
    }
    
  • 关键逻辑:

    • FadeDrawable 管理图片层级(如占位图、实际图片)。
    • setDrawable 更新图片,支持淡入效果。

3.7 资源释放

  • 渲染完成后,释放 CloseableReference:

    java

    CloseableReference.closeSafely(imageRef);
    
  • 触发 Bitmap 和 Ashmem 内存释放:

    java

    ResourceReleaser<CloseableImage> releaser = image -> {
        if (image instanceof CloseableStaticBitmap) {
            ((CloseableStaticBitmap) image).close();
        }
    };
    

  1. 底层原理与关键机制

SimpleDraweeView 的显示机制依赖以下核心机制,结合源码分析:

4.1 DraweeHierarchy

  • DraweeHierarchy 使用 Drawable 树结构管理图片层级:

    java

    public class GenericDraweeHierarchy implements DraweeHierarchy {
        private final FadeDrawable mFadeDrawable;
        private final Drawable mPlaceholderDrawable;
        private final Drawable mFailureDrawable;
    
        public GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
            mPlaceholderDrawable = builder.getPlaceholderImage();
            mFailureDrawable = builder.getFailureImage();
            mFadeDrawable = new FadeDrawable(new Drawable[] {
                mPlaceholderDrawable,
                null, // 实际图片占位
                mFailureDrawable
            });
        }
    
        @Override
        public Drawable getTopLevelDrawable() {
            return mFadeDrawable;
        }
    }
    
  • 作用:

    • 支持占位图、失败图和实际图片的动态切换。
    • 使用 FadeDrawable 实现淡入效果。

4.2 DraweeController

  • PipelineDraweeController 协调加载和渲染:

    java

    public void submitRequest() {
        mDataSource = mImagePipeline.fetchDecodedImage(mImageRequest, mCallerContext);
        mDataSource.subscribe(new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
            @Override
            public void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
                onNewResultInternal(dataSource.getResult(), dataSource.getProgress(), dataSource.isFinished());
            }
        }, mUiThreadExecutor);
    }
    
  • 关键逻辑:

    • 订阅 DataSource,监听加载进度和结果。
    • 调用 SettableDraweeHierarchy.setImage 更新 UI。

4.3 ImagePipeline

  • ImagePipeline 提供图片数据,支持多级缓存和渐进式加载:

    java

    CloseableReference<CloseableImage> cachedImage = mBitmapMemoryCache.get(cacheKey);
    if (cachedImage != null) {
        return ImmediateDataSource.create(cachedImage);
    }
    
  • 缓存查询:

    • BitmapMemoryCache(Ashmem 或堆内存)。
    • EncodedMemoryCache 和 DiskCache。

4.4 CloseableReference

  • 管理 CloseableImage(包含 Bitmap)的引用计数:

    java

    CloseableReference<CloseableImage> ref = CloseableReference.of(image, releaser);
    ref.close(); // 减少引用计数,可能释放 Bitmap
    
  • 确保内存安全,防止泄漏。

4.5 渐进式渲染

  • 支持渐进式 JPEG,逐步更新图片:

    java

    mSettableDraweeHierarchy.setImage(image.get(), progress, false);
    
  • progress 参数控制淡入效果,优化视觉体验。


  1. 优点与挑战

优点

  1. 简单易用:

    • setImageURI 等 API 简化图片加载。
    • 支持 XML 属性配置(如占位图、圆角)。
  2. 高性能:

    • 集成 ImagePipeline,利用多级缓存和 Ashmem。
    • 渐进式渲染优化慢网络体验。
  3. 灵活性:

    • 支持圆形、圆角、缩放类型等样式。
    • 可定制 DraweeHierarchy 和 DraweeController。
  4. 内存安全:

    • CloseableReference 确保 Bitmap 正确释放。
  5. 渐进式支持:

    • 动态更新图片质量,提升用户体验。

Challenges

  1. 复杂性:

    • 内部依赖 DraweeHierarchy 和 ImagePipeline,调试较复杂。
    • 需理解 Fresco 的异步机制。
  2. 内存管理:

    • 需正确释放 CloseableReference,否则可能泄漏。
    • Ashmem 和缓存配置需优化,适应低内存设备。
  3. 学习曲线:

    • 相比 Glide 的 ImageView,Fresco 的 API 和配置更复杂。
  4. 性能开销:

    • 渐进式渲染和多层 Drawable 可能增加 CPU 和内存使用。

  1. 与 Glide 的对比

为了更全面理解 SimpleDraweeView,以下将其与 Glide 的图片显示机制对比:

Glide 的图片显示

  • 机制:

    • 使用 ImageView 直接显示 Bitmap 或 Drawable。
    • 通过 RequestBuilder 加载图片,渲染到 ImageView。
  • 源码示例:

    java

    public class GlideRequest<TranscodeType> {
        public void into(ImageView view) {
            RequestManager requestManager = Glide.with(view);
            requestManager.load(model).apply(options).into(view);
        }
    }
    
  • 特点:

    • 简单直接,依赖 Android 原生 ImageView。
    • 不支持渐进式 JPEG,加载完成后一次性显示。

对比

特性SimpleDraweeView (Fresco)ImageView (Glide)
视图组件自定义 DraweeView原生 ImageView
渐进式加载支持(ProgressiveJpegDecoder)不支持
内存管理CloseableReference、AshmemWeakReference、BitmapPool
样式支持圆形、圆角、占位图需额外库(如 Glide Transformations)
复杂性较高(多组件协调)较低(简单 API)
适用场景复杂 UI、慢网络标准加载、快速开发
  • Fresco:适合复杂 UI 和慢网络场景,提供渐进式渲染和严格内存管理。
  • Glide:简单易用,适合标准图片加载,但功能较局限。

  1. 优化建议

基于 SimpleDraweeView 的实现,以下是优化建议:

  1. 配置渐进式加载:

    • 启用渐进式 JPEG,优化慢网络体验:

      java

      ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
          .setProgressiveRenderingEnabled(true)
          .build();
      simpleDraweeView.setController(Fresco.newDraweeControllerBuilder()
          .setImageRequest(request)
          .build());
      
  2. 优化缓存:

    • 调整 ImagePipelineConfig 的缓存大小:

      java

      ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
          .setBitmapMemoryCacheParamsSupplier(() -> new MemoryCacheParams(
              20 * 1024 * 1024, 100, 10 * 1024 * 1024, 50, Integer.MAX_VALUE))
          .build();
      Fresco.initialize(context, config);
      
  3. 确保资源释放:

    • 在 Activity 或 Fragment 销毁时释放 DraweeController:

      java

      @Override
      protected void onDestroy() {
          super.onDestroy();
          simpleDraweeView.setController(null);
      }
      
  4. 自定义样式:

    • 使用 XML 或代码配置圆角、占位图:

      xml

      <com.facebook.drawee.view.SimpleDraweeView
          fresco:roundedCornerRadius="10dp"
          fresco:placeholderImage="@drawable/placeholder" />
      

      java

      GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(getResources())
          .setRoundingParams(RoundingParams.asCircle())
          .setPlaceholderImage(R.drawable.placeholder);
      simpleDraweeView.setHierarchy(builder.build());
      
  5. 监控性能:

    • 使用 DraweeEventTracker 跟踪加载事件:

      java

      DraweeController controller = Fresco.newDraweeControllerBuilder()
          .setImageRequest(request)
          .setEventTracker(new DraweeEventTracker() {
              @Override
              public void onEvent(Event event) {
                  Log.d("Drawee", "Event: " + event);
              }
          })
          .build();
      

  1. 总结

SimpleDraweeView 是 Fresco 的核心视图组件,通过 DraweeHierarchy、DraweeController 和 ImagePipeline 实现高效的图片加载和显示。源码分析表明,其支持渐进式渲染、多级缓存、Ashmem 存储和严格内存管理,适合复杂 UI 和慢网络场景。与 Glide 的 ImageView 相比,SimpleDraweeView 提供更强大的功能,但复杂度较高。优化时需关注渐进式配置、缓存管理和资源释放。