Fresco强大的图片加载组件

来源:互联网 发布:上汽集团 知乎 编辑:程序博客网 时间:2024/05/16 08:40


前言



Fresco是一个强大的图片加载组件,使用它之后你不需要再去关心图片的加载和显示这些繁琐的事情,支持Android2.3~(这已经可以说是版本通用了),官方地址:https://www.fresco-cn.org/docs/


Image Pipeline

Fresco 中设计有一个叫做 Image Pipeline 的模块。它负责从网络,从本地文件系统,本地资源加载图片。为了最大限度节省空间和CPU时间,它含有3级缓存设计(2级内存,1级磁盘)。

Drawees

Fresco 中设计有一个叫做 Drawees 模块,它会在图片加载完成前显示占位图,加载成功后自动替换为目标图片。当图片不再显示在屏幕上时,它会及时地释放内存和空间占用。

特性



动图加载

加载Gif图和WebP动图在任何一个Android开发者眼里看来都是一件非常头疼的事情。每一帧都是一张很大的Bitmap,每一个动画都有很多帧。Fresco让你没有这些烦恼,它处理好每一帧并管理好你的内存。

既然Fresco这么棒咱们就开始按照官方教导来弄一下吧

开始观看教程~

当然咱们要是想愉快的使用Fresco的各种霸气功能,咱们先要去引入Fresco

引入Fresco

这里告诉你如何在项目中引入 Fresco.

使用 Android Studio 或者其他 Gradle 构建的项目

编辑 build.gradle 文件:

1234
dependencies {  // 其他依赖  compile 'com.facebook.fresco:fresco:0.12.0'}

下面的依赖需要根据需求添加:

1234567891011121314
dependencies {  // 在 API < 14 上的机器支持 WebP 时,需要添加  compile 'com.facebook.fresco:animated-base-support:0.12.0'  // 支持 GIF 动图,需要添加  compile 'com.facebook.fresco:animated-gif:0.12.0'  // 支持 WebP (静态图+动图),需要添加  compile 'com.facebook.fresco:animated-webp:0.12.0'  compile 'com.facebook.fresco:webpsupport:0.12.0'  // 仅支持 WebP 静态图,需要添加  compile 'com.facebook.fresco:webpsupport:0.12.0'}

Eclipse ADT

下载 zip 文件.

解压后,你会看到一个目录:frescolib,注意这个目录。

  1. 从菜单 “文件(File)”,选择导入(Import)
  2. 展开 Android, 选择 “Existing Android Code into Workspace”, 下一步。
  3. 浏览,选中刚才解压的的文件中的 frescolib 目录。
  4. 这5个项目应该被添加到工程: draweefbcorefrescoimagepipelineimagepipeline-base。请确认这5个项目一定是被选中的。点击完成。其他的项目参考之前 Gradle的额外依赖介绍。
  5. 右键,项目,选择属性,然后选择 Android。
  6. 点击右下角的 Add 按钮,选择 fresco,点击 OK,再点击 OK。

现在,fresco 就导入到项目中了,你可以开始编译了。如果编译不通过,可以尝试清理资源,或者重启 Eclipse。

如果你想在网络层使用 OkHttp,请看这里.

如果 support-v4 包重复了,删掉 frescolib/imagepipeline/libs 下的即可。

强烈建议尽早使用 Android Studio,Android Studio确实是比Eclipse要好用很多。



开始使用 Fresco

如果你仅仅是想简单下载一张网络图片,在下载完成之前,显示一张占位图,那么简单使用 SimpleDraweeView 即可。

在加载图片之前,你必须初始化Fresco类。你只需要调用Fresco.initialize一次即可完成初始化,在 Application 里面做这件事再适合不过了(如下面的代码),注意多次的调用初始化是无意义的。

12345678
[MyApplication.java]public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();Fresco.initialize(this);}}

做完上面的工作后,你需要在 AndroidManifest.xml 中指定你的 Application 类。为了下载网络图片,请确认你声明了网络请求的权限。

12345678910111213
  <manifest    ...    >    <uses-permission android:name="android.permission.INTERNET" />    <application      ...      android:label="@string/app_name"      android:name=".MyApplication"      >      ...    </application>    ...  </manifest>

在xml布局文件中, 加入命名空间:

123456
<!-- 其他元素--><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:fresco="http://schemas.android.com/apk/res-auto"    android:layout_height="match_parent"    android:layout_width="match_parent">

加入SimpleDraweeView:

123456
<com.facebook.drawee.view.SimpleDraweeView    android:id="@+id/my_image_view"    android:layout_width="130dp"    android:layout_height="130dp"    fresco:placeholderImage="@drawable/my_drawable"  />

开始加载图片:

123
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);draweeView.setImageURI(uri);

剩下的,Fresco会替你完成:

  • 显示占位图直到加载完成;
  • 下载图片;
  • 缓存图片;
  • 图片不再显示时,从内存中移除;

等等等等。


关键概念

Drawees

Drawees 负责图片的呈现。它由三个元素组成,有点像MVC模式。

DraweeView

继承于 View, 负责图片的显示。

一般情况下,使用 SimpleDraweeView 即可。 你可以在 XML 或者在 Java 代码中使用它,通过 setImageUri 给它设置一个 URI 来使用,这里有简单的入门教学:开始使用

你可以使用 XML属性来达到各式各样的效果。

DraweeHierarchy

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

你可以通过它来在Java代码中自定义图片的展示,具体的请参考这里: 在Java代码中自定义显示效果

DraweeController

DraweeController 负责和 image loader 交互( Fresco 中默认为 image pipeline, 当然你也可以指定别的),可以创建一个这个类的实例,来实现对所要显示的图片做更多的控制。

如果你还需要对Uri加载到的图片做一些额外的处理,那么你会需要这个类的。

DraweeControllerBuilder

DraweeControllers 由 DraweeControllerBuilder 采用 Builder 模式创建,创建之后,不可修改。具体参见: 使用ControllerBuilder。

Listeners

使用 ControllerListener 的一个场景就是设置一个 Listener监听图片的下载。

The Image Pipeline

Fresco 的 Image Pipeline 负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中。

在5.0系统以下,Image Pipeline 使用 pinned purgeables 将Bitmap数据避开Java堆内存,存在ashmem中。这要求图片不使用时,要显式地释放内存。

SimpleDraweeView自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView,在特殊的场合,如果有需要,也可以直接控制Image Pipeline。


支持的URI

Fresco 支持许多URI格式。

特别注意:Fresco 不支持 相对路径的URI. 所有的 URI 都必须是绝对路径,并且带上该 URI 的 scheme。

如下:

类型SCHEME示例远程图片http://, https://HttpURLConnection 或者参考 使用其他网络加载方案本地文件file://FileInputStreamContent providercontent://ContentResolverasset目录下的资源asset://AssetManagerres目录下的资源res://Resources.openRawResourceUri中指定图片数据data:mime/type;base64,数据类型必须符合 rfc2397规定 (仅支持 UTF-8)

res 示例:

1
Uri uri = Uri.parse("res://包名(实际可以是任何字符串甚至留空)/" + R.drawable.ic_launcher);


注意,只有图片资源才能使用在Image pipeline中,比如(PNG)。其他资源类型,比如字符串,或者XML Drawable在Image pipeline中没有意义。所以加载的资源不支持这些类型。

ShapeDrawable这样声明在XML中的drawable可能引起困惑。注意到这毕竟不是图片。如果想把这样的drawable作为图像显示,那么把这个drawable设置为占位图,然后把URI设置为null



遇到的问题

刚开始在使用的时候就使用了wrap_content发现不会出图片,看了下讲解搞懂了

主要的原因是,Drawee永远会在getIntrinsicHeight/getIntrinsicWidth中返回-1。

这么做的原因是 Drawee 不像ImageView一样。它同一时刻可能会显示多个元素。比如在从占位图渐变到目标图时,两张图会有同时显示的时候。再比如可能有多张目标图片(低清晰度、高清晰度两张)。如果这些图像都是不同的尺寸,那么很难定义”intrinsic”尺寸。

如果我们要先用占位图的尺寸,等加载完成后再使用真实图的尺寸,那么图片很可能显示错误。它可能会被根据占位图的尺寸来缩放、裁剪。唯一防止这种事情的方式就只有在图片加载完成后强制触发一次layout。这样的话不仅会影响性能,而且会让应用的界面突变,很影响用户体验!如果用户正在读一篇文章,然后在图片加载完成后整篇文章突然向下移动,这是非常不好的。

所以你必须指定尺寸或者用match_parent来布局。

你如果从服务端请求图片,服务端可以做到返回图片尺寸。然后你拿到之后通过setLayoutParams 来给View设置宽高。

当然如果你必须要使用wrap_content,那么你可以参考StackOverflow上的一个回答。但是我们以后会移除这个功能,Ugly things should look ugly。

通过Logcat来判断原因

在加载图片时会出现各种各样的原因导致加载失败。 在使用Fresco的时候,最直接的方式就是查看 image pipeline 打出的VERBOSE级别日志。

启动日志

默认情况下Fresco是关闭日志输出的,你可以配置image pipeline让它启动.

12345678
Set<RequestListener> requestListeners = new HashSet<>();requestListeners.add(new RequestLoggingListener());ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)   // other setters   .setRequestListeners(requestListeners)   .build();Fresco.initialize(context, config);FLog.setMinimumLoggingLevel(FLog.VERBOSE);

查看日志

你可以通过下面这条shell命令来查看Fresco日志:

1
adb logcat -v threadtime | grep -iE 'LoggingListener|AbstractDraweeController|BufferedDiskCache'

它的输出为如下格式:

1234567891011121314151617181920212223242526272829
08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 0 -> 1: initialize08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: onDetach08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: setHierarchy: null08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: setHierarchy: com.facebook.drawee.generic.GenericDraweeHierarchy@2bb88e408-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: onAttach: request needs submit08-12 09:11:14.791 6690 6690 V unknown:PipelineDraweeController: controller 28ebe0eb: getDataSource08-12 09:11:14.791 6690 6690 V unknown:RequestLoggingListener: time 11201791: onRequestSubmit: {requestId: 1, callerContext: null, isPrefetch: false}08-12 09:11:14.792 6690 6690 V unknown:RequestLoggingListener: time 11201791: onProducerStart: {requestId: 1, producer: BitmapMemoryCacheGetProducer}08-12 09:11:14.792 6690 6690 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: BitmapMemoryCacheGetProducer, elapsedTime: 1 ms, extraMap: {cached_value_found=false}}08-12 09:11:14.792 6690 6690 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: BackgroundThreadHandoffProducer}08-12 09:11:14.792 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: submitRequest: dataSource: 36e9585708-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: BackgroundThreadHandoffProducer, elapsedTime: 0 ms, extraMap: null}08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: BitmapMemoryCacheProducer}08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: BitmapMemoryCacheProducer, elapsedTime: 0 ms, extraMap: {cached_value_found=false}}08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: EncodedMemoryCacheProducer}08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: EncodedMemoryCacheProducer, elapsedTime: 0 ms, extraMap: {cached_value_found=false}}08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: DiskCacheProducer}08-12 09:11:14.792 6690 6735 V unknown:BufferedDiskCache: Did not find image for http://www.example.com/image.jpg in staging area08-12 09:11:14.793 6690 6735 V unknown:BufferedDiskCache: Disk cache read for http://www.example.com/image.jpg08-12 09:11:14.793 6690 6735 V unknown:BufferedDiskCache: Disk cache miss for http://www.example.com/image.jpg08-12 09:11:14.793 6690 6735 V unknown:RequestLoggingListener: time 11201793: onProducerFinishWithSuccess: {requestId: 1, producer: DiskCacheProducer, elapsedTime: 1 ms, extraMap: {cached_value_found=false}}08-12 09:11:14.793 6690 6735 V unknown:RequestLoggingListener: time 11201793: onProducerStart: {requestId: 1, producer: NetworkFetchProducer}08-12 09:11:15.161 6690 7358 V unknown:RequestLoggingListener: time 11202161: onProducerFinishWithSuccess: {requestId: 1, producer: NetworkFetchProducer, elapsedTime: 368 ms, extraMap: null}08-12 09:11:15.162 6690 6742 V unknown:BufferedDiskCache: About to write to disk-cache for key http://www.example.com/image.jpg08-12 09:11:15.162 6690 6734 V unknown:RequestLoggingListener: time 11202162: onProducerStart: {requestId: 1, producer: DecodeProducer}08-12 09:11:15.163 6690 6742 V unknown:BufferedDiskCache: Successful disk-cache write for key http://www.example.com/image.jpg08-12 09:11:15.169 6690 6734 V unknown:RequestLoggingListener: time 11202169: onProducerFinishWithSuccess: {requestId: 1, producer: DecodeProducer, elapsedTime: 7 ms, extraMap: {hasGoodQuality=true, queueTime=0, bitmapSize=600x400, isFinal=true}}08-12 09:11:15.169 6690 6734 V unknown:RequestLoggingListener: time 11202169: onRequestSuccess: {requestId: 1, elapsedTime: 378 ms}08-12 09:11:15.184 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: set_final_result @ onNewResult: image: CloseableReference 2fd41bb0

在这个示例中,我们发现名为28ebe0eb的 DraweeView 向名为36e95857的 DataSource 进行了图像请求。首先,图片没有在内存缓存中找到,也没有在磁盘缓存中找到,最后去网络上下载图片。下载成功后,图片被解码,之后请求结束。最后数据源通知 controller 图片就绪,显示图片(set_final_result)。










内存管理

解压后的图片,即Android中的Bitmap,占用大量的内存。大的内存占用势必引发更加频繁的GC。在5.0以下,GC将会显著地引发界面卡顿。

在5.0以下系统,Fresco将图片放到一个特别的内存区域。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。

Fresco 在低端机器上表现一样出色,你再也不用因图片内存占用而思前想后。

图片加载

Fresco的Image Pipeline允许你用很多种方式来自定义图片加载过程,比如:

  • 为同一个图片指定不同的远程路径,或者使用已经存在本地缓存中的图片
  • 先显示一个低清晰度的图片,等高清图下载完之后再显示高清图
  • 加载完成回调通知
  • 对于本地图,如有EXIF缩略图,在大图加载完成之前,可先显示缩略图
  • 缩放或者旋转图片
  • 对已下载的图片再次处理
  • 支持WebP解码,即使在早先对WebP支持不完善的Android系统上也能正常使用!

图片绘制

Fresco 的 Drawees 设计,带来一些有用的特性:

  • 自定义居中焦点
  • 圆角图,当然圆圈也行
  • 下载失败之后,点击重现下载
  • 自定义占位图,自定义overlay, 或者进度条
  • 指定用户按压时的overlay

图片的渐进式呈现

渐进式的JPEG图片格式已经流行数年了,渐进式图片格式先呈现大致的图片轮廓,然后随着图片下载的继续,呈现逐渐清晰的图片,这对于移动设备,尤其是慢网络有极大的利好,可带来更好的用户体验。

Android 本身的图片库不支持此格式,但是Fresco支持。使用时,和往常一样,仅仅需要提供一个图片的URI即可,剩下的事情,Fresco会处理。



自定义网络加载

Image pipeline 默认使用HttpURLConnection。应用可以根据自己需求使用不同的网络库。

OkHttp

OkHttp 是一个流行的开源网络请求库。Image pipeline有一个使用OkHttp替换掉了Android默认的网络请求的补充。

如果需要使用OkHttp, 不要使用这个下载页面的gradle依赖配置,应该使用下面的依赖配置

For OkHttp2:

1234
dependencies {  // your project's other dependencies  compile "com.facebook.fresco:imagepipeline-okhttp:0.12.0+"}

For OkHttp3:

1234
dependencies {  // your project's other dependencies  compile "com.facebook.fresco:imagepipeline-okhttp3:0.12.0+"}

Eclipse 中使用 OkHttp

Eclipse 用户需要依赖frescolib目录下的imagepipeline-okhttp 或 imagepipeline-okhttp3。 参考在Eclipse中使用Fresco.

配置 image pipeline

配置Image pipeline这时也有一些不同,不再使用ImagePipelineConfig.newBuilder,而是使用OkHttpImagePipelineConfigFactory:

12345678
Context context;OkHttpClient okHttpClient; // build on your ownImagePipelineConfig config = OkHttpImagePipelineConfigFactory    .newBuilder(context, okHttpClient)    . // other setters    . // setNetworkFetcher is already called for you    .build();Fresco.initialize(context, config);

你传给OkHttpClient需要处理服务器的安全校验工作(可以通过Interceptor处理)。参考这个bug 来处理自定义网络库可能发生的 Cookie 相关的问题。

使用自定的网络层

为了完全控制网络层的行为,你可以自定义网络层。继承NetworkFetchProducer, 这个类包含了网络通信。

你也可以选择性地继承FetchState, 这个类是请求时的数据结构描述。

默认的 OkHttp 3 可以作为一个参考. 源码在这 its source code..

在配置Image pipeline时,把producer传递给Image pipeline。

12345
ImagePipelineConfig config = ImagePipelineConfig.newBuilder()  .setNetworkFetcher(myNetworkFetcher);  . // other setters  .build();Fresco.initialize(context, config);

Fresco的体积可能会导致你的apk体积增大,所以可以在发布的时候使用混淆,使用混淆要注意

在project.properties文件中把proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 
前的#号去掉


# Keep our interfaces so they can be used by other ProGuard rules.# See http://sourceforge.net/p/proguard/bugs/466/-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip# Do not strip any method/class that is annotated with @DoNotStrip-keep @com.facebook.common.internal.DoNotStrip class *-keepclassmembers class * {    @com.facebook.common.internal.DoNotStrip *;}# Keep native methods-keepclassmembers class * {    native <methods>;}-dontwarn okio.**-dontwarn com.squareup.okhttp.**-dontwarn okhttp3.**-dontwarn javax.annotation.**-dontwarn com.android.volley.toolbox.**-dontwarn com.facebook.infer.**



按需打包

Fresco 大部分的代码是由Java写的,但是里面也有很多C++的代码。C++代码必须根据Android 设备的CPU类型(通常称为”ABIs”)进行编译。目前Fresco支持五种 ABI:

  1. armeabiv-v7a: 第7代及以上的 ARM 处理器。2011年15月以后的生产的大部分Android设备都使用它。
  2. arm64-v8a: 第8代、64位ARM处理器,很少设备,三星 Galaxy S6是其中之一。
  3. armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多。
  4. x86: 平板、模拟器用得比较多。
  5. x86_64: 64位的平板。

Fresco 下载下来之后已经包含了在这五种.so文件,你可以根据不同平台打出不同的App,由此来缩减包体积。

如果你的应用不支持 Android 2.3 (Gingerbread),你可以不需要 armeabi 类的ABI.

Android Studio / Gradle

编辑 build.gradle 文件:

1234567891011
android {  // rest of your app's logic  splits {    abi {        enable true        reset()        include 'x86', 'x86_64', 'arm64-v8a', 'armeabi-v7a', 'armeabi'        universalApk false    }  }}

参考Android Gradle 文档来获取更多信息。

Eclipse

默认情况下,Eclipse会产生一个含有所有ABI的app。将他们拆分比较困难(相比于Gradle)。

你需要下载multi-APK zip file而不是我们之前提供的标准ZIP包,而且你需要替换你的项目!

  1. 根据Android官方指示,你可以将项目拆分成多个项目。你可以使用相同的AndroidManifest.xml
  2. 不同ABI需求(flavor)的项目,需要依赖于不同的fresco-<flavor>. (如果你使用OkHttp,你需要对应地引入imagepipeline-okhttp-<flavor>)

Demo

点击打开GitHub链接,刚开始学习Fresco有不足的地方欢迎指教~