Fresco(一)Fresco的使用介绍

Fresco是一个出自Facebook的功能强大的图片加载库。本文就来介绍一下它的使用

(1)引入包

    implementation 'com.facebook.fresco:fresco:2.1.0'

最新的是 2.4.0,之所以没用是因为android studio 拉依赖的时候出了点问题,一直没解决,所以降了版本

(2)初始化

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Fresco.initialize(this);
    }
}

初始化两个configura。ImagePipelineConfig 和 DraweeConfig。源码解析放后面说,这里先用默认的。

(3)添加控件 SimpleDraweeView

<com.facebook.drawee.view.SimpleDraweeView
  android:id="@+id/my_image_view"
  android:layout_width="20dp"
  android:layout_height="20dp"
  fresco:fadeDuration="300"
  fresco:actualImageScaleType="focusCrop"
  fresco:placeholderImage="@color/wait_color"
  fresco:placeholderImageScaleType="fitCenter"
  fresco:failureImage="@drawable/error"
  fresco:failureImageScaleType="centerInside"
  fresco:retryImage="@drawable/retrying"
  fresco:retryImageScaleType="centerCrop"
  fresco:progressBarImage="@drawable/progress_bar"
  fresco:progressBarImageScaleType="centerInside"
  fresco:progressBarAutoRotateInterval="1000"
  fresco:backgroundImage="@color/blue"
  fresco:overlayImage="@drawable/watermark"
  fresco:pressedStateOverlayImage="@color/red"
  fresco:roundAsCircle="false"
  fresco:roundedCornerRadius="1dp"
  fresco:roundTopLeft="true"
  fresco:roundTopRight="false"
  fresco:roundBottomLeft="false"
  fresco:roundBottomRight="true"
  fresco:roundWithOverlayColor="@color/corner_color"
  fresco:roundingBorderWidth="2dp"
  fresco:roundingBorderColor="@color/border_color"
/>

以上就是 SimpleDraweeView 可以配置的各种选项。
注意,大小不支持 wrap_content,为什么Fresco中不可以使用wrap_content?主要的原因是,Drawee永远会在getIntrinsicHeight/getIntrinsicWidth中返回-1。Drawee 不像 ImageView 一样。它同一时刻可能会显示多个元素。比如在从占位图渐变到目标图时,两张图会有同时显示的时候。再比如可能有多张目标图片(低清晰度、高清晰度两张)。如果这些图像都是不同的尺寸,那么很难定义”intrinsic”尺寸。(留着这个疑问,我们到源码解析部分去看)
一般情况下,在XML设置显示效果即可, 如果想更多定制化,可以创建一个 builder 然后设置给 DraweeView

        List<Drawable> backgroundsList;
        List<Drawable> overlaysList;
        GenericDraweeHierarchyBuilder builder =
                new GenericDraweeHierarchyBuilder(getResources());
        GenericDraweeHierarchy hierarchy = builder
                .setFadeDuration(300)
                .setPlaceholderImage(new MyCustomDrawable())
                .setBackgrounds(backgroundList)
                .setOverlays(overlaysList)
                .build();
        mSimpleDraweeView.setHierarchy(hierarchy);

点进去看 GenericDraweeHierarchy。里面一共有7层。

  private static final int BACKGROUND_IMAGE_INDEX = 0;
  private static final int PLACEHOLDER_IMAGE_INDEX = 1;
  private static final int ACTUAL_IMAGE_INDEX = 2;
  private static final int PROGRESS_BAR_IMAGE_INDEX = 3;
  private static final int RETRY_IMAGE_INDEX = 4;
  private static final int FAILURE_IMAGE_INDEX = 5;
  private static final int OVERLAY_IMAGES_INDEX = 6;

通过builder把对应的drawable设置进去。比如常见的占位图(setPlaceholderImage),失败占位图(setFailureImage),进度条(setProgressBarImage)等等。还可以设置圆角(setRoundingParams)
当然,GenericDraweeHierarchy 是对所有的配置图片的说明。如果需要对加载显示的图片做更多的控制和定制,那就需要用到DraweeController

        Postprocessor myPostprocessor = new Postprocessor() {
            @Override
            public CloseableReference<Bitmap> process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) {
                for (int x = 0; x < sourceBitmap.getWidth(); x+=2) {
                    for (int y = 0; y < sourceBitmap.getHeight(); y+=2) {
                        sourceBitmap.setPixel(x, y, Color.RED);
                    }
                }
                // 举例,给图片加了红色网格
                return bitmapFactory.createBitmap(sourceBitmap);
            }

            @Override
            public String getName() {
                return null;
            }

            @Override
            public CacheKey getPostprocessorCacheKey() {
                return null;
            }
        };
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                .setProgressiveRenderingEnabled(true)//手动开启渐进式加载
                .setPostprocessor(myPostprocessor)//如果需要修改图片,可使用后处理器(Postprocessor)
                .build();//还可以设置 setCacheChoice,缩放和旋转图片setResizeOptions
        ControllerListener listener = new BaseControllerListener() {
            @Override
            public void onFinalImageSet(String id, Object imageInfo, Animatable animatable) {
            }

            @Override
            public void onIntermediateImageSet(String id, Object imageInfo) {
                // 渐进式图片的回调
            }

            @Override
            public void onIntermediateImageFailed(String id, Throwable throwable) {
                // 渐进式图片的回调
            }

            @Override
            public void onFailure(String id, Throwable throwable) {
            }
        };
        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setUri(uri)
                .setTapToRetryEnabled(true)
                .setOldController(draweeView.getController())//使用setOldController,这可节省不必要的内存分配
                .setControllerListener(listener)//监听下载事件
                .setImageRequest(request)//自定义ImageRequest
                .setAutoPlayAnimations(true)//设置动画图自动播放
                .build();
        draweeView.setController(controller);

本人把常用用法大体都整理了出来。由此可以看到DraweeController功能还是挺强大的
总结:SimpleDraweeView 的配置主要在 GenericDraweeHierarchy(所有图层,包括占位图) 和 DraweeController(定制要显示的图片) 里面

(4)加载图像之 ImagePipelineConfig

对于大多数的应用,Fresco的初始化,只需要以下一句代码:

Fresco.initialize(context);

当然,我们也可以自定义配置,就在 ImagePipelineConfig 里面

ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
    .setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
    .setCacheKeyFactory(cacheKeyFactory)
    .setDownsampleEnabled(true)
    .setWebpSupportEnabled(true)
    .setEncodedMemoryCacheParamsSupplier(encodedCacheParamsSupplier)
    .setExecutorSupplier(executorSupplier)
    .setImageCacheStatsTracker(imageCacheStatsTracker)
    .setMainDiskCacheConfig(mainDiskCacheConfig)
    .setMemoryTrimmableRegistry(memoryTrimmableRegistry)
    .setNetworkFetchProducer(networkFetchProducer)
    .setPoolFactory(poolFactory)
    .setProgressiveJpegConfig(progressiveJpegConfig)
    .setRequestListeners(requestListeners)
    .setSmallImageDiskCacheConfig(smallImageDiskCacheConfig)
    .build();
Fresco.initialize(context, config);

代码应该能看懂,本人就简单讲讲每个参数都有啥。先谈谈 Supplier
许多配置的Builder都接受一个Supplier 类型的参数而不是一个配置的实例。

public interface Supplier<T> {
  /**
   * Retrieves an instance of the appropriate type. The returned object may or may not be a new
   * instance, depending on the implementation.
   *
   * @return an instance of the appropriate type
   */
  T get();
}

它其实是一个接口。举个最简单的在freso库里面就有个实现看看,比如 DefaultBitmapMemoryCacheParamsSupplier

public class DefaultBitmapMemoryCacheParamsSupplier implements Supplier<MemoryCacheParams> {
  private static final int MAX_CACHE_ENTRIES = 256;
  private static final int MAX_EVICTION_QUEUE_SIZE = Integer.MAX_VALUE;
  private static final int MAX_EVICTION_QUEUE_ENTRIES = Integer.MAX_VALUE;
  private static final int MAX_CACHE_ENTRY_SIZE = Integer.MAX_VALUE;
  private static final long PARAMS_CHECK_INTERVAL_MS = TimeUnit.MINUTES.toMillis(5);

  private final ActivityManager mActivityManager;

  public DefaultBitmapMemoryCacheParamsSupplier(ActivityManager activityManager) {
    mActivityManager = activityManager;
  }

  @Override
  public MemoryCacheParams get() {
    return new MemoryCacheParams(
        getMaxCacheSize(),
        MAX_CACHE_ENTRIES,
        MAX_EVICTION_QUEUE_SIZE,
        MAX_EVICTION_QUEUE_ENTRIES,
        MAX_CACHE_ENTRY_SIZE,
        PARAMS_CHECK_INTERVAL_MS);
  }

  private int getMaxCacheSize() { ... }
}

其实很好理解,就是通过统一的接口来实现对参数的管理。好了,接下来就看看各个参数配置了啥
(1)setBitmapMemoryCacheParamsSupplier
默认 DefaultBitmapMemoryCacheParamsSupplier,就是上面贴出来的
最大cache项是256,PARAMS_CHECK_INTERVAL_MS 就是 每隔5分钟就可检查一下Supplier。
getMaxCacheSize 根据系统为你整个app分配的memory大小来决定。系统给的小于32M,就是4M,系统给的小于64M,那就8M
(2)setCacheKeyFactory
默认 DefaultCacheKeyFactory。它提供了4种key

  @Override
  public CacheKey getBitmapCacheKey(ImageRequest request, Object callerContext) {
    return new BitmapMemoryCacheKey(
        getCacheKeySourceUri(request.getSourceUri()).toString(),
        request.getResizeOptions(),
        request.getRotationOptions(),
        request.getImageDecodeOptions(),
        null,
        null,
        callerContext);
  }

  @Override
  public CacheKey getPostprocessedBitmapCacheKey(ImageRequest request, Object callerContext) {
    final Postprocessor postprocessor = request.getPostprocessor();
    final CacheKey postprocessorCacheKey;
    final String postprocessorName;
    if (postprocessor != null) {
      postprocessorCacheKey = postprocessor.getPostprocessorCacheKey();
      postprocessorName = postprocessor.getClass().getName();
    } else {
      postprocessorCacheKey = null;
      postprocessorName = null;
    }
    return new BitmapMemoryCacheKey(
        getCacheKeySourceUri(request.getSourceUri()).toString(),
        request.getResizeOptions(),
        request.getRotationOptions(),
        request.getImageDecodeOptions(),
        postprocessorCacheKey,
        postprocessorName,
        callerContext);
  }

  @Override
  public CacheKey getEncodedCacheKey(ImageRequest request, @Nullable Object callerContext) {
    return getEncodedCacheKey(request, request.getSourceUri(), callerContext);
  }

  @Override
  public CacheKey getEncodedCacheKey(
      ImageRequest request, Uri sourceUri, @Nullable Object callerContext) {//点进去看就是sourceUri
    return new SimpleCacheKey(getCacheKeySourceUri(sourceUri).toString());
  }

  protected Uri getCacheKeySourceUri(Uri sourceUri) {
    return sourceUri;
  }

(3)setEncodedMemoryCacheParamsSupplier
默认 DefaultEncodedMemoryCacheParamsSupplier.挺简单的,不贴代码了
(4)setExecutorSupplier
默认 DefaultExecutorSupplier

public class DefaultExecutorSupplier implements ExecutorSupplier {
  // Allows for simultaneous reads and writes.
  private static final int NUM_IO_BOUND_THREADS = 2;
  private static final int NUM_LIGHTWEIGHT_BACKGROUND_THREADS = 1;

  private final Executor mIoBoundExecutor;
  private final Executor mDecodeExecutor;
  private final Executor mBackgroundExecutor;
  private final Executor mLightWeightBackgroundExecutor;

  public DefaultExecutorSupplier(int numCpuBoundThreads) {
    mIoBoundExecutor =
        Executors.newFixedThreadPool(
            NUM_IO_BOUND_THREADS,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoIoBoundExecutor", true));
    mDecodeExecutor =
        Executors.newFixedThreadPool(
            numCpuBoundThreads,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoDecodeExecutor", true));
    mBackgroundExecutor =
        Executors.newFixedThreadPool(
            numCpuBoundThreads,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoBackgroundExecutor", true));
    mLightWeightBackgroundExecutor =
        Executors.newFixedThreadPool(
            NUM_LIGHTWEIGHT_BACKGROUND_THREADS,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoLightWeightBackgroundExecutor", true));
  }
}

简单介绍一下

  • mIoBoundExecutor---io线程池,用于磁盘操作,本地文件的读取,默认2个线程
  • mDecodeExecutor---解析图片的,线城数可以自己设置
  • mBackgroundExecutor---后台工作的,线城数可以自己设置,暂时不知道干嘛的,等后面的代码解析
  • mLightWeightBackgroundExecutor---轻量级后台工作,等解析,默认1个线程

(5)setMainDiskCacheConfig
默认 DiskCacheConfig。我摘出了重要的代码

    private long mMaxCacheSize = 40 * ByteConstants.MB;
    private long mMaxCacheSizeOnLowDiskSpace = 10 * ByteConstants.MB;
    private long mMaxCacheSizeOnVeryLowDiskSpace = 2 * ByteConstants.MB;
    private EntryEvictionComparatorSupplier mEntryEvictionComparatorSupplier =
        new DefaultEntryEvictionComparatorSupplier();

注意 DefaultEntryEvictionComparatorSupplier。里面就是 Comparator,根据时间戳来决定是否排除响应cache,先排除时间久的

(4)加载图片 setImageURI

        Uri uri = Uri.parse("https://t7.baidu.com/it/u=4036010509,3445021118&fm=193&f=GIF");
        SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
        draweeView.setImageURI(uri);

大致流程如下:

  • 检查内存缓存,如有,返回
  • 后台线程开始后续工作
  • 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
  • 检查是否在磁盘缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
  • 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。

具体源码下一篇说

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容