Fresco图片加载框架的介绍,相关开源库以及工具类的封装

本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发。

简介

Fresco 是Facebook开源的安卓上的图片加载框架,也可以说是至今为止安卓上最强大的图片加载框架.

相对于其他几个图片加载框架,Fresco主要的优点在于更好的内存管理和更强大的功能,更便捷的使用,缺点则是体积比较大,引入后会导致应用apk增加1.5M到2M的大小,但是相对于其便捷性来讲,我觉得这都不是事儿.

优点一:内存管理

对于5.0以下系统,fresco使用”ashmem”(匿名共享内存)区域存储Bitmap缓存,这样Bitmap对象的创建、释放将不会触发GC,不会占用javaheap. 这个特点是其他图片加载框架所没有做到的.

5.0以上系统,由于安卓系统本身内存管理的优化,所以对于5.0以上的系统Fresco将Bitmap缓存直接放到了javaheap内存中.

并且,fresco实现了真正的三级缓存:两级的内存缓存+一个磁盘缓存.两个内存缓存为:bitmap缓存 和未解码的图片缓存,这样既可以加快图片的加载速度,又能节省内存的占用.这个两级的内存缓存也是其他图片加载框架所没有做到的.

另外提一点,在app切换到后台时,fresco会自动清理两级的内存缓存,无需手动.

通过以上几点,使用fresco加载图片时内存占用要比其他图片加载框架小一大半,基本很少发生oom的事情.

几个图片加载框架的内存占用测试结果对比请戳这里:Android Image Loader 第三方库对比测试

优点二:更便捷的使用:

初期入门,轻度的图片加载,甚至可以直接用:

Fresco.initiliaze(context);<com.facebook.drawee.view.SimpleDraweeViewandroid:id="@+id/my_image_view"android:layout_width="20dp"android:layout_height="20dp"fresco:placeholderImage="@drawable/news_default".../>simpleDraweeView.setImageURI(uri);

其他都不用管,fresco自动帮我们做缓存,图片缩略.

显示圆角或圆圈图片也只是xml中配置一下而已:

圆圈 - 设置roundAsCircle为true
圆角 - 设置roundedCornerRadius

另外再说一句,fresco还支持webp,所以,splash和引导界面的大图我一般都是用智图压成webp放在drawable中,用fresco加载就行了.

相关文档及开源库

官方文档中文版

Github开源库:facebook/fresco,已更新到0.10.0

中文的Fresco源码解读,分析版本:0.7.0

fresco里的photoview :用于查看大图并随手势缩放

bilibili开源的借助fresco加载图片的 spannable text view : Bilibili/drawee-text-view

fresco的bitmap后处理器封装,可以直接使用,关于后处理请看文档

使用心得及一些方法的封装

加载超级大图还是会卡

首先,终极的解决方法肯定是,客户端在图片请求中带上需要的宽和高,服务器将图片缩略到该规格后返回该小图.这个做得比较好的是七牛.

注意:不管服务器能不能返回缩略图,所存储的原图都不应该太大,有时图片太大,甚至都无法下载下来(报504之类的错误).

那么,如果服务器只能拿到原图或大图,fresco怎么缩略显示?

fresco中提供了三个功能来生成缩略图:

Scaling :画布操作,通常是由硬件加速的。图片实际大小保持不变,它只不过在绘制时被放大或缩小.使用时需要配置缩放类型fresco:actualImageScaleType,具体类型名与Imageview的ScaleType几乎一样.

Resizing 是一种软件执行的管道操作。它返回一张新的,尺寸不同的图片,也就是说直接改变bitmap的大小,可惜是单独使用时,只支持jpg,当然,结合Downsampling使用时,可以支持除gif以为的所有常见图片,包括webp.

Downsampling 同样是软件实现的管道操作。它不是创建一张新的图片,而是在解码时改变图片的大小。 同样是软件实现的管道操作。它不是创建一张新的图片,而是在解码时改变图片的大小。类似于android中的BitmapFactory在decodefile时的inSampleSize,都是指定一个采样率,默认是关闭的,如果开启,那么需要结合Resizing来使用.

综上,要缩小内存占用,以及减少cpu计算量,减少卡顿,应该是Downsampling结合Resizing来使用.其中Downsampling是在Fresco初始化时开启,而Resizing则是通过构建ImageRequest时通过制定宽高来实现,所以可以定制每一张或每一类图片的宽高. 示例代码如下

初始化:

  /*** 初始化操作,建议在子线程中进行* 添加的依赖:*  compile 'com.facebook.fresco:fresco:0.10.0+'compile 'com.facebook.fresco:animated-webp:0.10.0'//加载webp必须添加compile 'com.facebook.fresco:animated-gif:0.10.0' //加载gif必须添加* @param context* @param cacheSizeInM  磁盘缓存的大小,以M为单位*/public static void init(final Context context,int cacheSizeInM){DiskCacheConfig diskCacheConfig = DiskCacheConfig.newBuilder(context).setMaxCacheSize(cacheSizeInM*1024*1024)//最大缓存.setBaseDirectoryName(PHOTO_FRESCO)//子目录.setBaseDirectoryPathSupplier(new Supplier<File>() {@Overridepublic File get() {return context.getCacheDir();//还是推荐缓存到应用本身的缓存文件夹,这样卸载时能自动清除,其他清理软件也能扫描出来}}).build();MyImageCacheStatsTracker imageCacheStatsTracker = new MyImageCacheStatsTracker();//缓存的监听接口,其方法空实现即可ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context).setMainDiskCacheConfig(diskCacheConfig).setImageCacheStatsTracker(imageCacheStatsTracker).setDownsampleEnabled(true)//Downsampling,要不要向下采样,它处理图片的速度比常规的裁剪scaling更快,// 并且同时支持PNG,JPG以及WEP格式的图片,非常强大,与ResizeOptions配合使用.setBitmapsConfig(Bitmap.Config.RGB_565)//如果不是重量级图片应用,就用这个省点内存吧.默认是RGB_888.build();Fresco.initialize(context, config);}

利用SimpleDraweeView加载图片的一般姿势:

注意,我这里没有去设置DraweeHierarchy,因为依照fresco的设计思维,DraweeHierarchy属于view层次的东西,应该在xml中配置.当然如果非要设置,请看这里.

public static void load(Uri uri,SimpleDraweeView draweeView,BasePostprocessor processor,int width,int height,BaseControllerListener listener){ImageRequest request =ImageRequestBuilder.newBuilderWithSource(uri).setPostprocessor(processor).setResizeOptions(new ResizeOptions(width,height))//缩放,在解码前修改内存中的图片大小, 配合Downsampling可以处理所有图片,否则只能处理jpg,// 开启Downsampling:在初始化时设置.setDownsampleEnabled(true).setProgressiveRenderingEnabled(true)//支持图片渐进式加载.setAutoRotateEnabled(true) //如果图片是侧着,可以自动旋转.build();PipelineDraweeController controller =(PipelineDraweeController) Fresco.newDraweeControllerBuilder().setImageRequest(request).setControllerListener(listener).setOldController(draweeView.getController()).setAutoPlayAnimations(true) //自动播放gif动画.build();draweeView.setController(controller);}

显示图片时把人的头部给截掉了

这个就要用到Scaling了.图片的缩放拉伸以及裁剪模式.具体看文档

可用的缩放类型

类型 描述
center 居中,无缩放。
centerCrop 保持宽高比缩小或放大,使得两边都大于或等于显示边界,且宽或高契合显示边界。居中显示。
focusCrop 同centerCrop, 但居中点不是中点,而是指定的某个点。
centerInside 缩放图片使两边都在显示边界内,居中显示。和 fitCenter 不同,不会对图片进行放大。如果图尺寸大于显示边界,则保持长宽比缩小图片。
fitCenter 保持宽高比,缩小或者放大,使得图片完全显示在显示边界内,且宽或高契合显示边界。居中显示。
fitStart 同上。但不居中,和显示边界左上对齐。
fitEnd 同fitCenter, 但不居中,和显示边界右下对齐。
fitXY 不保存宽高比,填充满显示边界。
none 如要使用tile mode显示, 需要设置为none

这些缩放类型和Android ImageView 支持的缩放类型几乎一样.

图片默认是centerCrop,那么在用一个横向的SimpleDraweeView来显示一张竖着拍的人像时,就很可能把人的头部给截掉了,但对于在listview中展示的SimpleDraweeView来说,我们又无法用focusCrop直接指定其居中点在图片上半部分某个地方,因为其他图片可能是横着拍的或很正方形的自拍,这个时候怎么办?就需要根据人脸的检测来设置focusCrop的那个点了,而android从sdk 1.0开始就已经提供了一个人脸识别的类FaceDetector,原理是通过找眼睛来识别人脸,可以拿到眼睛的中心点坐标,那么根据该坐标,结合图片本身的宽高,计算出针对每张图片的focusCrop 需要设置的点,就能够解决这个问题了.

//todo 这个还没有去写方法,但有一个开源项目facecropper可以参考,他们的做法是将一个大的bitmap截图成小的bitmap. 同样的代码还有中文注释版的,汗…

获取缓存的图片文件

对于内存的缓存,fresco根据图片Uri,以及图片的resising参数和processor参数综合生成缓存key来缓存bitmap,而磁盘文件缓存则是只根据Uri生成key,那么,如果要获取文件缓存,只需要知道uri和通过key取file的api就行了,原先的调用链较长,故封装成单个方法:

需要注意的是文件名后缀不是普通的图片后缀(.jpg之类的),而是.cnt,但都是二进制文件,可以将文件直接拷贝到指定路径重命名成正常图片后缀即可.当然如果只是读取到内存做其他用途,可以直接读取,无需拷贝更改后缀,不影响使用.

以下是读取缓存文件的方法.而拷贝到其他文件目录的方法也已封装好于FrescoUtils中.

public static File getFileFromDiskCache(String url){File localFile = null;if (!TextUtils.isEmpty(url)) {CacheKey cacheKey = DefaultCacheKeyFactory.getInstance().getEncodedCacheKey(ImageRequest.fromUri(url));if (ImagePipelineFactory.getInstance().getMainFileCache().hasKey(cacheKey)) {BinaryResource resource = ImagePipelineFactory.getInstance().getMainFileCache().getResource(cacheKey);localFile = ((FileBinaryResource) resource).getFile();} else if (ImagePipelineFactory.getInstance().getSmallImageFileCache().hasKey(cacheKey)) {BinaryResource resource = ImagePipelineFactory.getInstance().getSmallImageFileCache().getResource(cacheKey);localFile = ((FileBinaryResource) resource).getFile();}}return localFile;}

如果我想用fresco来下载图片,不需要显示,要怎么实现?

首先,如果是单纯的下载,建议使用专门的文件下载框架FileDownloader

如果非要用fresco,那么需要绕一点弯:

​ 通过imagePipeline.prefetchToDiskCache将图片缓存到disk,然后拷贝出来.缓存完成的监听采用fresco提供的BaseDataSubscriber,在文件缓存完成后回调,然后拷贝文件到指定目录.

注意:该方式不是很好用,偶尔会有失败的情况出现.

 /*** 文件下载到文件夹中:将图片缓存到本地后,将缓存的图片文件copy到另一个文件夹中** 容易发生如下异常,progress在100处停留时间长* dalvikvm: Could not find method android.graphics.Bitmap.getAllocationByteCount,* referenced from method com.facebook.imageutils.BitmapUtil.getSizeInBytes06-21 16:15:39.547 3043-3244/com.hss01248.tools W/dalvikvm: VFY:unable to resolve virtual method 569: Landroid/graphics/Bitmap;.getAllocationByteCount ()I* @param url* @param context* @param dir 保存图片的文件夹* @param listener 自己定义的回调*/public static void download(final String url, Context context, final File dir, final DownloadListener listener){ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)).build();final ImagePipeline imagePipeline = Fresco.getImagePipeline();DataSource<Void> dataSource = imagePipeline.prefetchToDiskCache(imageRequest, context, Priority.HIGH);dataSource.subscribe(new BaseDataSubscriber<Void>() {@Overrideprotected void onNewResultImpl(DataSource<Void> dataSource) {File  file  =   copyCacheFileToDir(url,dir);clearCacheByUrl(url);//清除缓存if (file == null || !file.exists()){listener.onFail();}else {listener.onSuccess(file);}}@Overridepublic void onProgressUpdate(DataSource<Void> dataSource) {super.onProgressUpdate(dataSource);listener.onProgress(dataSource.getProgress());}@Overrideprotected void onFailureImpl(DataSource<Void> dataSource) {listener.onFail();}}, CallerThreadExecutor.getInstance());}

由于某种原因无法使用SimpleDraweeView来显示图片(比如说弹幕上显示头像),而需要直接操作bitmap,那么要怎么拿到url返回的bitmap?

自己构建图片请求,然后类似上面的文件下载,还是采用DataSubscriber来监听回调,只不过返回的不是void,而是CloseableImage的bitmap,

/*** 拿到指定宽高,并经过Processor处理的bitmap* @param url* @param context* @param width* @param height* @param processor 后处理器,可为null* @param listener**/public static void getBitmapWithProcessor(String url, Context context, int width, int height,BasePostprocessor processor,final BitmapListener listener){ResizeOptions resizeOptions = null;if (width !=0 && height != 0 ){resizeOptions = new ResizeOptions(width, height);}ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)).setProgressiveRenderingEnabled(false) //我们是拿bitmap对象,不是显示,所以这里不需要渐进渲染.setPostprocessor(processor).setResizeOptions(resizeOptions).build();ImagePipeline imagePipeline = Fresco.getImagePipeline();DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);dataSource.subscribe(new BaseBitmapDataSubscriber() {@Overrideprotected void onNewResultImpl(Bitmap bitmap) {listener.onSuccess(bitmap);}@Overrideprotected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {listener.onFail();}}, CallerThreadExecutor.getInstance());}

注意,此bitmap对象的缓存由Fresco管理,所以不要去调用bitmap.recycle()之类的方法.

对此bitmap的一些处理,如果处理后的bitmap对象还是指向该bitmap引用,会影响到其他同样url,width height,并且同样Postprocessor的图片组件的显示,比如,将该bitmap高斯模糊了,就会影响到其他的这四个参数相同的SimpleDraweeView的显示.

那么,如果要不影响,怎么办?很简单,让四个参数任一一个不同就行.

Fresco中在listview之类的快速滑动时停止加载,滑动停止后恢复加载时调用的API是什么?

话不多说,直接看代码.

/*** 暂停网络请求* 在listview快速滑动时使用*/public static void pause(){Fresco.getImagePipeline().pause();}/*** 恢复网络请求* 当滑动停止时使用*/public static void resume(){Fresco.getImagePipeline().resume();}

gif图片无法显示成圆形,怎么办?(当然有的情况下,jpg也无法显示圆形) –加一层和图片的parentview 背景色一样的圆形遮罩即可:

public static void setCircle( SimpleDraweeView draweeView,int bgColor){RoundingParams roundingParams = RoundingParams.asCircle();//这个方法在某些情况下无法成圆,比如gifroundingParams.setOverlayColor(bgColor);//加一层遮罩,这个是关键方法draweeView.getHierarchy().setRoundingParams(roundingParams);}

当然,无法圆角化,也是同样加一层遮罩:

很多app都有清除磁盘缓存的功能,那么fresco怎么清除缓存呢?

/*** 清除磁盘缓存*/public static void clearDiskCache(){Fresco.getImagePipeline().clearDiskCaches();}/*** 清除单张图片的磁盘缓存* @param url*/public static void clearCacheByUrl(String url){ImagePipeline imagePipeline = Fresco.getImagePipeline();Uri uri = Uri.parse(url);// imagePipeline.evictFromMemoryCache(uri);imagePipeline.evictFromDiskCache(uri);//imagePipeline.evictFromCache(uri);//这个包含了从内存移除和从硬盘移除}

已经设置了downsampling和resize,但是加载很大的gif图还是很卡,怎么办

gif是一帧一帧的图组成,编码的方式跟jpg和png等图片有很大区别,downsampling和resize对gif是无效的.gif图太大了?

分辨率一大的话,那文件不得十几M几十M?下载都要等很久吧?

还是在服务器端放小一点的图吧…

高斯模糊

高斯模糊是app里设置一些背景效果 常用到的手段.

在fresco中,可以通过postprocessor来实现,也可以自己拿到bitmap后将bitmap模糊化后设置到ImageView或SimpleDraweeView(这个不建议,会消除掉SimpleDraweeView的层级结构,变成单纯的ImageView)上.

推荐前一种: 用到别人封装好的BlurPostprocessor :

    /*** 高斯模糊后显示* @param url* @param draweeView* @param width draweeView的宽* @param height draweeView的高* @param context* @param radius  高斯模糊的半径, 每一个像素都取周边(多少个)像素的平均值* @param sampling 采样率 原本是设置到BlurPostprocessor上的,因为高斯模糊本身对图片清晰度要求就不高,*                 所以此处直接设置到ResizeOptions上,直接让解码生成的bitmap就缩小,而BlurPostprocessor*                 内部sampling设置为1,无需再缩*/public static void loadUrlInBlur(String url,SimpleDraweeView draweeView,int width,int height,Context context,int radius,int sampling){if (sampling<2){sampling = 2;}loadUrl(url,draweeView,new BlurPostprocessor(context,radius,1),width/sampling,height/sampling,null);}

当然,拿到bitmap自己去模糊也有开源框架:NativeStackBlur

 private Bitmap fastBlur(Bitmap bkg, int radius,int downSampling) {if (downSampling < 2){downSampling = 2;}Bitmap smallBitmap =   Bitmap.createScaledBitmap(bkg,bkg.getWidth()/downSampling,bkg.getHeight()/downSampling,true);return   NativeStackBlur.process(smallBitmap, radius);}

也可以通过这个方法来封装自己的BlurPostprocessor,具体可参考上面封装好的BlurPostprocessor.

工具类FrescoUtils地址:https://github.com/glassLake/FrescoUtlis

Fresco图片加载框架的介绍,相关开源库以及工具类的封装相关推荐

  1. (三) 技术选型 1.项目框架模式:MVP(得分点);注意:分包分层,避免内存泄漏; 2.图片加载:Fresco图片加载框架; 3.网络加载框架:retrofit;使用Retrofit+RxJ

    首先呢    先把业务逻辑需求讲一下  大致思路都是根据自己所想的去做,下面是我个人做的一个小model,希望能帮到有需要的人 业务逻辑需求 1.MVP分包分层:Model.View.Presente ...

  2. Fresco图片加载框架使用方法完全指南

    简介 Fresco 是Facebook开源的安卓上的图片加载框架,也可以说是至今为止安卓上最强大的图片加载框架. 相对于其他几个图片加载框架,Fresco主要的优点在于更好的内存管理和更强大的功能,更 ...

  3. 【Android】Fresco图片加载框架(二)————Producer

    /** * 本文可以随意转载到任何网站或者App, * BUT * 转载也要按"基本法", * 请注明原文出处和作者 */ 官方源码地址 fresco官方高大上介绍(1)(注意:前 ...

  4. Universal-Image-Loader(UIL)图片加载框架使用简单介绍

    这个也是最近项目中使用到的第三方图片加载框架,在这里也自己总结一下,简单的介绍一些使用的方式. UIL图片加载框架特点 简介: 项目地址:https://github.com/nostra13/And ...

  5. Fresco图片加载+EventBus+Butterknife+Retrofit+RxJava+RxAndroid

    使用MVP框架搭建,分包明确,V层和M层解耦,通过接口完成V层和P层以及P层和M层通信,解决内存泄漏问题 Retrofit做网络请求,封装网络请求工具类,使用单例模式,添加日志拦截器打印网络请求内容 ...

  6. 图片加载框架Glide的简单使用

    图片加载框架Glide的相关使用 一.搭建环境 1.引入依赖,设置网络权限 implementation 'com.github.bumptech.glide:glide:3.7.0' 代码实现 1. ...

  7. 介绍一下google推荐的Android图片加载框架——Glide

    [译文原文]:Introduction to Glide, Image Loader Library for Android, recommended by Google 在泰国举行的Google开发 ...

  8. Android中常见的图片加载框架

    图片加载涉及到图片的缓存.图片的处理.图片的显示等.而随着市面上手机设备的硬件水平飞速发展,对图片的显示要求越来越高,稍微处理不好就会造成内存溢出等问题.很多软件厂家的通用做法就是借用第三方的框架进行 ...

  9. Android Glide 图片加载框架解析

    在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载框架,作者是  bumptech,这个库被广泛的应用在 Google 开源项目中,包括 2014 年 Google I/O ...

最新文章

  1. CentOS Linux内核升级全过程
  2. CSS内容溢出时,显示省略号
  3. 【转】unity3d优化总结篇
  4. .Net日志之nlog
  5. Flex4之元数据标签使用
  6. ios pan手势滑动消失动画_解析Color OS全面屏手势,操作丝滑,操作逻辑帮了大忙...
  7. python---之阿partial
  8. 研究生马上要毕业了,可是完全写不出论文,该退学吗?
  9. Docker客户端与守护进程
  10. 【老生谈算法】matlab实现LSB算法水印算法源码——LSB算法
  11. 7z文件格式及其源码的分析(四)
  12. 2021年电工(中级)考试资料及电工(中级)考试试题
  13. 计算机考研专业课838考什么,17年管理学838专业课初试110分经验贴
  14. 【TCP专题】TCP的可靠性传输
  15. 人工智能非技术从业者必知的十件事
  16. CSLA公链构建多元生态—csla超级公链是什么东西
  17. freeglut_光照设置_材质设置_笔记与心得
  18. python爬虫学习 - 查看显卡价格
  19. python计算数学题_「每日一练」巧用python做小学的数学题
  20. GPS NMEA数据解析

热门文章

  1. Android 优雅的为RecyclerView添加HeaderView和FooterView
  2. iOS 悬浮球效果实现,悬浮按钮,拖拽,贴边,隐藏,显示,旋转屏幕适配
  3. 微信视频号直播功能玩法详解:国仁楠哥
  4. 配置化表单FormRender初尝试
  5. C语音:输入数字找到小于或等于的素数
  6. 华为机试:最长方连续方波信号
  7. PROE基本操作1(查看组件尺寸)
  8. 共话开源 - openKylin出席 FOSSASIA Summit 2023 开源盛会!
  9. 托管C++中函数调用的双重转换(Double Thunking)
  10. 【Phoenix】 ERROR 726 (43M10): Inconsistent namespace mapping properties.