Official documentation: www.fresco-cn.org/docs/gettin…

preface

Fresco is a powerful image loading library from Facebook

The advantages and disadvantages

Advantages:

1) Automatic memory reclamation. If the image is not visible, it will automatically release the memory occupied in time, avoiding OOM 2) level 3 cache mechanism as much as possible. Two levels of memory cache (decoded and undecoded) + one level of disk cache to improve loading speed and save memory footprint 3) Support various loading scenarios. Such as GIF loading, Gaussian blur and other common image loading scenarios. In addition, it provides unique incremental loading, loading small image before loading large image, loading schedule, etc. (very powerful).

Disadvantages:

1) Big (very fat). Larger than other mainstream photo libraries 2) more intrusive. Use SimpleDraweeView instead of ImageView to load and display images. Generally, if your application has high requirements for image display, loading, etc., then Fresco is recommended. But if it’s not that demanding use Glide or another library.

introduce

Configuration, SimpleDraweeView, loading images, obfuscating, and more.

1. The configuration

1.1 Adding a Dependency

compile 'com. Facebook. Fresco ": the fresco" : 1.5.0'
compile 'com. Facebook. Fresco ": animated - GIF: 1.5.0'// Add this library compile to load gifs'com. Facebook. Fresco ": animated - webp: 1.5.0'// Add this library compile to load webp GIF'com. Facebook. Fresco ": webpsupport: 1.5.0'// add this library compile to support webp'com. Facebook. Fresco ": imagepipeline - okhttp3:1.5.0'// The network implementation layer uses okhttp3 to add this library compile'jp. Wasabeef: fresco "- processors: 2.1.0 @ aar'// Used to provide fresco image transformationsCopy the code

1.2 Configuring Disk Caching

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context); imagePipelineConfigBuilder.setMainDiskCacheConfig(DiskCacheConfig.newBuilder(context) SetBaseDirectoryPath (context. GetExternalCacheDir ()) / / set the path of the disk cache SetBaseDirectoryName (baseconstants.app_image)// Set the name of the disk cache folder. SetMaxCacheSize (MAX_DISK_CACHE_SIZE)// Set the size of the disk cache.build());Copy the code

1.3 Setting Memory Cache

Set up the decoded memory cache (Bitmap cache)

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setBitmapMemoryCacheParamsSupplier(new Supplier<MemoryCacheParams>() {
public MemoryCacheParams get() { int MAX_HEAP_SIZE = (int) Runtime.getRuntime().maxMemory(); int MAX_MEMORY_CACHE_SIZE = MAX_HEAP_SIZE / 5; MemoryCacheParams bitmapCacheParams = new MemoryCacheParams(// // Maximum memory available, MAX_MEMORY_CACHE_SIZE, in bytes, // Maximum number of images allowed in memory integer. MAX_VALUE, // Maximum amount of memory available for total images that are ready to be cleaned up but not deleted, MAX_MEMORY_CACHE_SIZE, in bytes, // Maximum number of images in memory to be cleared integer. MAX_VALUE, // Maximum size of a single image in memory integer. MAX_VALUE);returnbitmapCacheParams; }});Copy the code

Set undecoded memory cache

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setEncodedMemoryCacheParamsSupplier(new Supplier<MemoryCacheParams>() {
public MemoryCacheParams get() { MemoryCacheParams bitmapCacheParams; // Set the size, refer to the decoded memory cache abovereturnbitmapCacheParams; }});Copy the code

1.4 Setting The Solution for Memory Shortage

MemoryTrimmableRegistry memoryTrimmableRegistry = NoOpMemoryTrimmableRegistry.getInstance();
memoryTrimmableRegistry.registerMemoryTrimmable(new MemoryTrimmable() {
@Override
public void trim(MemoryTrimType trimType) {
final double suggestedTrimRatio = trimType.getSuggestedTrimRatio();
if(MemoryTrimType.OnCloseToDalvikHeapLimit.getSuggestedTrimRatio() == suggestedTrimRatio || MemoryTrimType.OnSystemLowMemoryWhileAppInBackground.getSuggestedTrimRatio() == suggestedTrimRatio || MemoryTrimType. OnSystemLowMemoryWhileAppInForeground. GetSuggestedTrimRatio () = = suggestedTrimRatio) {/ / clear the memory cache ImagePipelineFactory.getInstance().getImagePipeline().clearMemoryCaches(); }}}); ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context); imagePipelineConfigBuilder.setMemoryTrimmableRegistry(memoryTrimmableRegistry);Copy the code

1.5 Set progressive display effect

ProgressiveJpegConfig progressiveJpegConfig = new ProgressiveJpegConfig() {@ Override public int getNextScanNumberToDecode (int scanNumber) {/ / return the next need to scan the decodingreturnscanNumber + 2; } public QualityInfo getQualityInfo(int scanNumber) { boolean isGoodEnough = (scanNumber >= 5); // Determine the number of scans before the image is displayed.return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false); }}; / / ImagePipelineConfig specific meaning may refer to http://wiki.jikexueyuan.com/project/fresco/progressive-jpegs.html. The Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context); imagePipelineConfigBuilder.setProgressiveJpegConfig(progressiveJpegConfig); / / or the effect of using the default / / imagePipelineConfigBuilder setProgressiveJpegConfig (new SimpleProgressiveJpegConfig ());Copy the code

After setting up the effects, you also need to enable progressive loading in ImageRequest, described below.

1.6 Allows resizing images while decoding

If allowed, the size of the decoded image can be adjusted in combination with ResizeOptions in ImageRequest introduced later, so as to optimize the image size. Only JPEG images are supported by default, so set this property to support PNG, JPG, webP.

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);

imagePipelineConfigBuilder.setDownsampleEnabled(true);
Copy the code

1.7 open the Log

FLog.setMinimumLoggingLevel(FLog.VERBOSE);

Set<RequestListener> requestListeners = new HashSet<>();

requestListeners.add(new RequestLoggingListener());

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);

imagePipelineConfigBuilder.setRequestListeners(requestListeners);
Copy the code

1.8 the initialization

All of the above configuration is done via ImagePipelineConfig and then needs to be initialized in the Application

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context); / /... In various Settings ImagePipelineConfig config = imagePipelineConfigBuilder. The build (); Fresco.initialize(context, config);Copy the code

If you want to use the default configuration, you can

Fresco.initialize(context);
Copy the code

2. SimpleDraweeView

Fresco requires that images be loaded and displayed using SimpleDraweeView instead of ImageView, which is why some people don’t want to use Fresco.

The following describes the various attributes of SimpleDraweeView in XML

/ / in the outermost layout attribute to join XMLNS: fresco “=” schemas.android.com/apk/res-aut…”

<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/sdv"
android:layout_width="150dp"
android:layout_height="150dp"
fresco:actualImageScaleType="centerCrop"
fresco:fadeDuration="2000"
fresco:failureImage="@mipmap/ic_launcher"
fresco:failureImageScaleType="centerCrop"
fresco:placeholderImage="@mipmap/ic_launcher"
fresco:placeholderImageScaleType="centerCrop"
fresco:progressBarImage="@drawable/rotate"
fresco:progressBarImageScaleType="centerCrop"
fresco:retryImage="@mipmap/ic_launcher"
fresco:retryImageScaleType="centerCrop"
fresco:backgroundImage="@mipmap/ic_launcher"
fresco:overlayImage="@mipmap/ic_launcher"
fresco:pressedStateOverlayImage="@mipmap/ic_launcher"
fresco:roundAsCircle="false"
fresco:roundedCornerRadius="7dp"
fresco:roundTopLeft="true"
fresco:roundTopRight="false"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="true"
fresco:roundWithOverlayColor="@color/colorAccent"
fresco:roundingBorderWidth="2dp"
fresco:roundingBorderColor="@color/colorPrimary"
fresco:viewAspectRatio="1"/>
Copy the code

Attribute Description

ActualImageScaleType Loads the scale style of the finished image

FadeDuration The time interval used to transition from the progress bar and placeholder images to the finished loaded images

FailureImage Image used by the loading failure

FailureImageScaleType Scale style of the image used when loading failed

PlaceholderImage placeholderImage

PlaceholderImageScaleType placeholder image scaling style

Rotate the progress bar progressBarAutoRotateInterval 1 lap time

ProgressBarImage Image used to rotate the progress bar

ProgressBarImageScaleType used by rotating the progress bar image scaling style

RetryImage Specifies the image used for retry

RetryImageScaleType Retries the scaling style of the image used

BackgroundImage backgroundImage

OverlayImage Overlays an image over a loaded image

PressedStateOverlayImage Overlay image in the pressed state

RoundAsCircle whether to cut the image to a circle

RoundedCornerRadius Specifies the radius of the rounded corner when the image is rounded

Whether the upper left corner of the roundTopLeft is rounded

Whether the upper right corner of roundTopRight is rounded

Whether the lower left corner of roundBottomLeft is rounded

Whether the lower right corner of roundBottomRight is rounded

RoundWithOverlayColor Round corners or circles overlay color, can only be color

RoundingBorderWidth Width of the border of a rounded corner or circle

RoundingBorderColor The color of the rounded corners or borders of a circular drawing

ViewAspectRatio Sets the aspect ratio

* note:

1) The android: SRC attribute is invalid for SimpleDraweeView, use fresco:placeholderImage if necessary. 2) SimpleDraweeView does not support android:layout_width and Android :layout_height both set to wrap_content.

3. Load the image

Loading images with Fresco roughly follows this process. Hierarchay 1. Set Hierarchay (attributes in the XML above and display load progress bar, etc.) 2. Build ImageRequest (loading path, enable progressive loading, image transform, resize decoded image, etc., can be set here) 3. Build DraweeController (GIF load, click reload after failure, etc., can be set here) 4. Loading images

3.1 set Hierarchay

Although all the properties in the XML can be set in this step through code, there are generally only fixed properties set here, such as load placeholder, load failure graph, and so on. In addition, the loading progress of images is also set here.

Resources res = MyApplication.getInstance().getResources(); Drawable retryImage = ResourcesCompat.getDrawable(res, R.mipmap.ic_image_load, null); Drawable failureImage = ResourcesCompat.getDrawable(res, R.mipmap.ic_image_load, null); Drawable placeholderImage = ResourcesCompat.getDrawable(res, R.mipmap.ic_image_load, null); Hierarchy is set, for example, images displayed in various states public voidsetHierarchay(GenericDraweeHierarchy hierarchy) {
if(hierarchy ! = null) {// Reload the displayed image hierarchy. SetRetryImage (retryImage); / / load failure display picture hierarchy. The setFailureImage (failureImage, ScalingUtils. ScaleType. CENTER_CROP); / / before completion of loading shows placeholder figure hierarchy. SetPlaceholderImage (placeholderImage, ScalingUtils. ScaleType. CENTER_CROP); / / set to load after a successful picture zoom model hierarchy. The setActualImageScaleType (ScalingUtils. ScaleType. CENTER_CROP); New ProgressBarDrawable() is displayed at the bottom of the image by default. You can set the color of the progress bar. hierarchy.setProgressBarImage(new ProgressBarDrawable()); / / set the picture loaded on circular hierarchy. The setRoundingParams (RoundingParams. AsCircle ()); / / set the images added to rounded corners, and can set the fillet size hierarchy. SetRoundingParams (RoundingParams. FromCornersRadius (radius)); // For other Settings, please check the specific API. }}Copy the code

3.2 build ImageRequest

/** * Build ImageRequest * @param URI load path * @param simpleDraweeView load image control * @return ImageRequest
*/

public ImageRequest getImageRequest(Uri uri, SimpleDraweeView simpleDraweeView) {
int width;
int height;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
width = simpleDraweeView.getWidth();
height = simpleDraweeView.getHeight();
} else{ width = simpleDraweeView.getMaxWidth(); height = simpleDraweeView.getMaxHeight(); } / / according to the request path generates ImageRequest initializer ImageRequestBuilder builder = ImageRequestBuilder. NewBuilderWithSource (uri). // Resize the decoded imageif(width > 0 && height > 0) { builder.setResizeOptions(new ResizeOptions(width, height)); } / / set whether to open the incremental load, only supports JPEG image builder. SetProgressiveRenderingEnabled (true); / / picture transformation processing CombinePostProcessors. The Builder processorBuilder = new CombinePostProcessors. Builder (); ProcessorBuilder. Add (new BlurPostprocessor(context, RADIUS)); Processorbuilder.add (new GrayscalePostprocessor()); Builder.setpostprocessor (processorBuilder.build())); / / transform to see https://github.com/wasabeef/fresco-processors for more picturesreturn builder.build();
}
Copy the code

3.3 build DraweeController

/** * Build and get Controller * @param request * @param oldController * @return*/ public DraweeController getController(ImageRequest request, @Nullable DraweeController oldController) { PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder(); builder.setImageRequest(request); / / set the picture request builder. SetTapToRetryEnabled (false); Click when loading failure / / Settings are allowed to load builder. SetAutoPlayAnimations (true); // Set whether to allow the animation to autoplay builder.setoldController (oldController);return builder.build();
}
Copy the code

3.4 Loading images

Create a loadImage method to string the Hierarchy, ImageRequest, and DraweeController together for specific loading scenarios

/** @param simpleDraweeView @param uri @param public void loadImage(simpleDraweeView SimpleDraweeView, Uri Uri) {// Set HierarchysetHierarchay(simpleDraweeView.getHierarchy()); ImageRequest ImageRequest = getImageRequest(URI, simpleDraweeView); / / build and obtain the Controller DraweeController DraweeController = getController (imageRequest, simpleDraweeView. The getController ()); / / load simpleDraweeView. SetController (draweeController); }Copy the code

Specific loading scenarios:

  • Load web images, including GIFs/Webp giFs
public void loadNetImage(SimpleDraweeView simpleDraweeView, String url) {
Uri uri = Uri.parse(url);
loadImage(simpleDraweeView, uri);
}
Copy the code

Load local file images

public void loadLocalImage(SimpleDraweeView simpleDraweeView, String fileName) {
Uri uri = Uri.parse("file://" + fileName);
loadImage(simpleDraweeView, uri);
}
Copy the code

Load resource images under RES

public void loadResourceImage(SimpleDraweeView simpleDraweeView, @DrawableRes int resId) {
Uri uri = Uri.parse("res:///" + resId);
loadImage(simpleDraweeView, uri);
}
Copy the code

Load the image under the ContentProvider

public void loadContentProviderImage(SimpleDraweeView simpleDraweeView, int resId) {
Uri uri = Uri.parse("content:///" + resId);
loadImage(simpleDraweeView, uri);
}
Copy the code

Load the image under asset

public void loadAssetImage(SimpleDraweeView simpleDraweeView, int resId) {
Uri uri = Uri.parse("asset:///" + resId);
}
Copy the code

To load network images, load the small image first and replace the small image after the large image is loaded

This requires modifying the DraweeController build to add the small graph request via setLowResImageRequest

public DraweeController getSmallToBigController(ImageRequest smallRequest, ImageRequest bigRequest, @Nullable DraweeController oldController) { PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder(); builder.setLowResImageRequest(smallRequest); Builder.setimagerequest (bigRequest); / / a larger picture request builder. The setTapToRetryEnabled (false); Click when loading failure / / Settings are allowed to load builder. SetAutoPlayAnimations (true); // Set whether to allow the animation to autoplay builder.setoldController (oldController);returnbuilder.build(); } public void loadImageSmallToBig(SimpleDraweeView SimpleDraweeView, Uri smallUri, Uri bigUri) {// Set HierarchysetHierarchay(simpleDraweeView.getHierarchy()); ImageRequest smallRequest = getImageRequest(smallUri, simpleDraweeView); ImageRequest bigRequest = getImageRequest(bigUri, simpleDraweeView); DraweeController = getSmallToBigController(smallRequest, bigRequest, simpleDraweeView.getController()); / / load simpleDraweeView. SetController (draweeController); } // Load the network image. Public void loadNetImageSmallToBig(SimpleDraweeView SimpleDraweeView, String smallUrl, String bigUrl) { Uri smallUri = Uri.parse(smallUrl); Uri bigUri = Uri.parse(bigUrl); loadImageSmallToBig(simpleDraweeView, smallUri, bigUri); }Copy the code

4. Confused

Add the following to the proGuard-rules. pro file for obfuscation configuration

-keep class com.facebook.fresco.** { *; } -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip -keep @com.facebook.common.internal.DoNotStrip class * -keepclassmembers class * { @com.facebook.common.internal.DoNotStrip *;  } -keep class com.facebook.imagepipeline.gif.** { *; } -keep class com.facebook.imagepipeline.webp.* { *; } -keepclassmembers class * { native <methods>; } -dontwarn okio.** -dontwarn com.squareup.okhttp.** -dontwarn okhttp3.** -dontwarn javax.annotation.** -dontwarn com.android.volley.toolbox.** -keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl { public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory,com.facebook.imagepipeline.core.ExecutorSup plier); }Copy the code

5. Other

5.1 Cache Policy Fresco uses a three-level cache mechanism, consisting of two levels of memory cache and one level of disk cache. The two levels of memory cache are divided into Bitmap cache and undecoded image cache. Let’s look at the caching strategy through the load process. 1. Check whether a corresponding Bitmap exists in the decoded image cache according to the Uri. If present, return Bitmap display; If not, look it up in the undecoded image cache. 2. If the corresponding data exists in the undecoded image cache, decode, return the Bitmap display and add it to the decoded image cache; If not, look it up in the disk cache. 3. If the corresponding data exists in the disk cache, the data is added to the undecoded image cache, and then decoded, returning the Bitmap display and adding it to the decoded image cache; If not, make a network request or load to a local file. 4. After the request or load is successful, add the data to the disk cache and the undecoded image cache, then decode, return the Bitmap display and add it to the decoded image cache. Simple whole schematic diagram, to help understand:

5.2 compatible with shared animation android5.0 added shared animation, if directly combined with Fresco and shared animation page transition effect, will find invalid or abnormal. Fresco has a note from www.fresco-cn.org/docs/shared… 1. Overwrite the XML file that shared the animation transformation effect, comment out the changeImageTransform, and place the file in the RES/Transition folder

<? xml version="1.0" encoding="utf-8"? > <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"> <explode/> <changeBounds/> <changeTransform/> <changeClipBounds/> <! --<changeImageTransform/>--> <! -- The Fresco image framework does not support changeImageTransform. By default, all five transformations are used, so you need to rewrite the XML and comment out changeImageTransform --> </transitionSet>Copy the code

2. Use the XML file overridden in the previous step in the style file

<? xml version="1.0" encoding="utf-8"? > <resources> <style name="AppTheme" parent="AppTheme.Base"> <! -- Transitions -> <item name= is allowed"android:windowContentTransitions">true</item> <! -- Specify shared Element transitions --> <item name="android:windowSharedElementEnterTransition">
@transition/share_element_transition</item>
<item name="android:windowSharedElementExitTransition">
@transition/share_element_transition</item>
</style>
</resources>
Copy the code

5.3 Viewing the Larger Image “Click on the smaller image to view the larger image, and the larger image supports zooming.” This requirement is common; the SimpleDraweeView mentioned above doesn’t support zooming and so on, so you need to customize a control to display it. A ZoomableDraweeView is provided to support this scene, and you can also refer to the PhotoDraweeView

Sometimes, we need to get the Bitmap from the network request, so we can do this:

// Load the image, Public void getBitmap(Context Context, String URL, final ImageListener<Bitmap> imageListener) { Uri uri = Uri.parse(url); ImagePipeline imagePipeline = Fresco.getImagePipeline(); ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri); ImageRequest imageRequest = builder.build(); DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, context); dataSource.subscribe(new BaseDataSubscriber<CloseableReference<CloseableImage>>() { @Override public void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {if(! dataSource.isFinished()) {return;
                }
                CloseableReference<CloseableImage> imageReference = dataSource.getResult();
                if(imageReference ! = null) { final CloseableReference<CloseableImage> closeableReference = imageReference.clone(); try { CloseableImage closeableImage = closeableReference.get(); // GIF processingif(closeableImage instanceof CloseableAnimatedImage) { AnimatedImageResult animatedImageResult = ((CloseableAnimatedImage)  closeableImage).getImageResult();if(animatedImageResult ! = null && animatedImageResult.getImage() ! = null) { int imageWidth = animatedImageResult.getImage().getWidth(); int imageHeight = animatedImageResult.getImage().getHeight(); Bitmap.Config bitmapConfig = Bitmap.Config.ARGB_8888; Bitmap bitmap = Bitmap.createBitmap(imageWidth, imageHeight, bitmapConfig); animatedImageResult.getImage().getFrame(0).renderFrame(imageWidth, imageHeight, bitmap);if(imageListener ! = null) { imageListener.onSuccess(bitmap); }}} // Non-gif processingelse if (closeableImage instanceof CloseableBitmap) {
                            CloseableBitmap closeableBitmap = (CloseableBitmap) closeableImage;
                            Bitmap bitmap = closeableBitmap.getUnderlyingBitmap();
                            if(bitmap ! = null && ! bitmap.isRecycled()) { final Bitmap tempBitmap = bitmap.copy(bitmap.getConfig(),false);
                                if(imageListener ! = null) { imageListener.onSuccess(tempBitmap); } } } } finally { imageReference.close(); closeableReference.close(); } } } @Override public void onFailureImpl(DataSource dataSource) { Throwable throwable = dataSource.getFailureCause();if(imageListener ! = null) { imageListener.onFail(throwable); } } }, UiThreadImmediateExecutorService.getInstance()); }Copy the code

Or if there is data in the cache, it can be fetched from the cache and converted into a Bitmap

FileBinaryResource resource = (FileBinaryResource) Fresco.getImagePipelineFactory().getMainFileCache().getResource(new SimpleCacheKey(url));
if(resource ! = null && resource.getFile() ! = null) { Bitmap bitmap = BitmapFactory.decodeFile(resource.getFile().getAbsolutePath()); }Copy the code

5.5 Downloading Images

Download the image to the specified location and get the download result in the ImageListener callback

public void downLoadImage(Context context, String url, final File saveFile, final ImageListener<File> imageListener) { Uri uri = Uri.parse(url); ImagePipeline imagePipeline = Fresco.getImagePipeline(); ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri); ImageRequest imageRequest = builder.build(); // CloseableReference<PooledByteBuffer>> DataSource = imagePipeline.fetchEncodedImage(imageRequest, context); dataSource.subscribe(new BaseDataSubscriber<CloseableReference<PooledByteBuffer>>() { @Override public void onNewResultImpl(DataSource<CloseableReference<PooledByteBuffer>> dataSource) {if(! dataSource.isFinished()) {return;
                                     }
                                     CloseableReference<PooledByteBuffer> imageReference = dataSource.getResult();
                                     if(imageReference ! = null) { final CloseableReference<PooledByteBuffer> closeableReference = imageReference.clone(); try { PooledByteBuffer pooledByteBuffer = closeableReference.get(); InputStream inputStream = new PooledByteBufferInputStream(pooledByteBuffer); OutputStream outputStream = new FileOutputStream(saveFile);if(FileUtil.saveFile(inputStream, outputStream) && imageListener ! = null) { imageListener.onSuccess(saveFile); } } catch (Exception e) {if(imageListener ! = null) { imageListener.onFail(e); } e.printStackTrace(); } finally { imageReference.close(); closeableReference.close(); } } } @Override public void onProgressUpdate(DataSource<CloseableReference<PooledByteBuffer>> dataSource) { int progress  = (int) (dataSource.getProgress() * 100); RingLog.d("Fresco download progress:" + progress);
                                 }

                                 @Override
                                 public void onFailureImpl(DataSource dataSource) {
                                     Throwable throwable = dataSource.getFailureCause();
                                     if(imageListener ! = null) { imageListener.onFail(throwable); } } }, Executors.newSingleThreadExecutor()); }Copy the code