What you don't know about fresco using highlights

Posted by b-real on Sat, 15 Jun 2019 20:52:52 +0200

In a recent project development, I used fresco as the image loading engine framework. Fresco is very powerful, but I encountered many pits in the development process, so I wrote an article to record the problems I encountered in order to facilitate your future use.
The corresponding code in this article is github On the other hand, the code is written in kotlin, so you can experience the power of kotlin in the process of learning.

Environmental Construction

compile 'com.facebook.fresco:fresco:1.3.0'
// Support webp
compile 'com.facebook.fresco:webpsupport:1.3.0'
// gif load usage
compile 'com.facebook.fresco:animated-gif:1.3.0'
// WebP (Static Graph + Motion Graph) Loading Use
compile 'com.facebook.fresco:animated-webp:1.3.0'

Basic concepts

Let's start with a brief look at the important classes in fresco
DraweeView: Inherited from View, we usually use the SimpleDraweeView class for image loading. We'll talk about its custom attributes later.
Drawee Hierarchy: The class that renders the content of an image, through which we can set the properties of DraweeView in java code
DraweeController: Class responsible for image loading at the bottom of the framework
Image Pipeline: (Graphic Pipeline) Complete the acquisition of pictures. Whether through the network, local files, content provider s or local resources, it compresses and caches pictures to disk, and stores decoded pictures in memory as a second cache.

Configuration of attributes in XML

Fresco has the following layers

/* Occupancy Layer, which is displayed by default */
private final int mPlaceholderImageIndex;
/* Schedule Bar Layer: Round or Linear Schedule Bar during Loading */
private final int mProgressBarImageIndex;
/* Target Display Layer, the Layer Displayed after Loading */
private final int mActualImageIndex;
/* Retry Layer, which is the layer displayed when the load fails to retry */
private final int mRetryImageIndex;
/* Failure Layer, which is the layer displayed when loading fails */
private final int mFailureImageIndex;
/* Control Overlay Layer, that is, Overlay Layer */
private final int mControllerOverlayIndex;

Around these six layers, our SimpleDraweeView layout generally contains the following main elements

<com.facebook.drawee.view.SimpleDraweeView xmlns:fresco="http://schemas.android.com/apk/res-auto"
        android:id="@+id/image_fresco2"
        android:layout_width="100dip"
        android:layout_height="wrap_content"
        fresco:actualImageScaleType="centerCrop"
        fresco:fadeDuration="3000"
        fresco:failureImage="@mipmap/ic_launcher"
        fresco:failureImageScaleType="centerCrop"
        fresco:placeholderImage="@mipmap/ic_launcher"
        fresco:placeholderImageScaleType="centerCrop"
        fresco:progressBarAutoRotateInterval="1000"
        fresco:progressBarImage="@drawable/ani_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="5dip"
        fresco:roundTopLeft="true"
        fresco:roundTopRight="true"
        fresco:roundBottomLeft="true"
        fresco:roundBottomRight="true"
        fresco:roundWithOverlayColor="@color/colorAccent"
        fresco:roundingBorderWidth="2dip"
        fresco:roundingBorderColor="@color/colorPrimary"
        fresco:viewAspectRatio="1"/>
attribute Function description
actualImageScaleType Zoom Styles for Loading Completed Pictures
fadeDuration Time interval used to transit from progress bar and placeholder image to loaded image
failureImage Pictures Used for Load Failure
failureImageScaleType Scaling Styles of Pictures Used in Load Failure
placeholderImage Placeholder picture
placeholderImageScaleType Scaling Style of Placeholder Pictures
progressBarAutoRotateInterval The time required to rotate the progress bar for one turn
progressBarImage Pictures used to rotate progress bars
progressBarImageScaleType Scaling Style of Pictures Used in Rotating Progress Bar
retryImage Retry the images used
retryImageScaleType Retry the zoom style of the image used
backgroundImage Background picture
overlayImage Overlay Overlay Pictures on Images After Loading
pressedStateOverlayImage Overlay Pictures under Pressure
roundAsCircle Whether to cut the picture into a circle
roundedCornerRadius The radius of the corner in a rounded picture
roundTopLeft Is the upper left corner rounded?
roundTopRight Is the upper right corner rounded?
roundBottomLeft Is the lower left corner rounded?
roundBottomRight Is the lower right corner rounded?
roundWithOverlayColor The color of an overlay of rounded corners or circles can only be a color.
roundingBorderWidth Width of the border of a rounded corner or circle
roundingBorderColor The color of the border of a rounded corner or circle
viewAspectRatio Setting aspect ratio

Here I'll talk about scaleType. GenericDraweeView has an additional focusCrop property compared to ImageView. The difference between this attribute and centerCrop is that the former can freely define the focus while the latter has a fixed focus. DraweeView displays with this focus as much as possible. Focus is shown in relative coordinates, such as (0f, 0f) upper left corner alignment display, (1f, 1f) lower right corner alignment display. This makes the focus position independent of the specific size, which is very practical. If the focus is set to (0.5f, 0.5f), then it is equivalent to center Crop.

If you want to use this zoom mode, first specify the zoom mode in XML:

fresco:actualImageScaleType="focusCrop"

Then in Java code, specify the focus for your image:

image_fresco2!!.hierarchy.setActualImageFocusPoint(PointF(1f, 1f))

Refer to the Source Section for other attributes


Fresco custom attribute location

Here are a few things to be noted.

  1. The width of SimpleDraweeView cannot be wrap_content at the same time. It requires match_parent or a fixed value. Unless you use viewAspectRatio to set proportions, one of the broader, Higher-school parties under these conditions can be wrap_content.
  2. When using SimpleDraweeView, facebook officially advises against using any attributes of ImageView, such as setImageResource, setBackground, setScaleType, etc., which are in ImageView but not in View.

Implementing attribute configuration in XML in JAVA code

If you are already familiar with custom attributes in xml, it should be easy for you to implement them again in java
Here's a brief look at the dynamic setup process from the source point of view
SimpleDraweeView is inherited from GenericDraweeView

public class SimpleDraweeView extends GenericDraweeView

There is an important method in GenericDraweeView, inflateHierarchy, which is implemented by every construction method of GenericDraweeView. This method is to convert the layer information defined in xml into GenericDrawee Hierarchy Builder, and finally set Hierarchy to GenericDraweeView.

protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
    GenericDraweeHierarchyBuilder builder = GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
    this.setAspectRatio(builder.getDesiredAspectRatio());
    this.setHierarchy(builder.build());
}

Now it's obvious that we just need to set up this hierarchy.

    /**
     * Setting the Hierarchy of Various Images
     */
    private fun setNormalHierarchy(context: Context) : GenericDraweeHierarchy {
        val hierarchy: GenericDraweeHierarchy = this.hierarchy
        // System default horizontal linear progress bar Progress Bar Drawable
//        val drawable: Drawable = ContextCompat.getDrawable(context, R.drawable.ani_rotate)
        if (mySimpleDraweeViewParams!!.progressBarImage!=null) {
            hierarchy.setProgressBarImage(AutoRotateDrawable(mySimpleDraweeViewParams!!.progressBarImage, 1000))
        }

        // Configure rounded corners
        var roundingParams: RoundingParams = RoundingParams.fromCornersRadius(30f)

        hierarchy.fadeDuration= mySimpleDraweeViewParams!!.fadeDuration
        hierarchy.roundingParams = roundingParams
        hierarchy.setOverlayImage(mySimpleDraweeViewParams!!.overlayImageDrawable)
        hierarchy.actualImageScaleType=mySimpleDraweeViewParams!!.scaleType
        hierarchy.setPlaceholderImage(mySimpleDraweeViewParams!!.placeHolderDrawable, mySimpleDraweeViewParams!!.scaleType)
        hierarchy.setFailureImage(mySimpleDraweeViewParams!!.placeHolderDrawable, mySimpleDraweeViewParams!!.scaleType)
        hierarchy.setRetryImage(mySimpleDraweeViewParams!!.placeHolderDrawable, mySimpleDraweeViewParams!!.scaleType)
        return hierarchy
    }

Ultimately, the same is true.

SimpleDraweeView.setHierarchy(setNormalHierarchy(context))

Configure Image Pipeline Config

As I said earlier, ImagePipeline plays a very important role in the image loading process. Let's take a look at its workflow.

  1. Check memory cache, if yes, return
  2. Background threads start follow-up work
  3. Check whether it is in the uncoded memory cache. If yes, decode, transform, return, and then cache in memory.
  4. Check whether it is in the file cache, if so, transform, return. Cached into uncoded and memory caches.
  5. Load from the network or locally. After loading, decode, transform and return. Store in each cache.

Completing these tasks requires a certain strategy, which requires us to complete through a custom ImagePipeline Config interface.

Just load the default configuration.

Fresco.initialize(this)

Let's look at how to customize the configuration

  1. Define constant parameters
    companion object {
         // Maximum available memory
         val maxHeapSize: Long = Runtime.getRuntime().maxMemory()
         // Cache Disk Small Picture Folder Name
         val smallDiskCacheName: String = "smallDiskCacheConfig"
         // Cache Disk Ordinary Picture Folder Name
         val normalDiskCacheName: String = "normalDiskCacheConfig"
         // Cache Disk folder size
         val diskCacheSize: Int = 100 * ByteConstants.MB
         // Cache Disk Folder Size in Low Hard Disk Space
         val lowDiskCacheSize: Int = 50 * ByteConstants.MB
         // Cache Disk Folder Size in Very Low Hard Disk Space
         val veryLowDiskCacheSize: Int = 20 * ByteConstants.MB
    }
  2. Implementing the ImagePipeline Config interface

    fun getDefaultImagePipelineConfig(context: Context) : ImagePipelineConfig {
    }

    Look at the specific configuration

    var imagePipelineConfig: ImagePipelineConfig.Builder = ImagePipelineConfig.newBuilder(context)
                     .setProgressiveJpegConfig(SimpleProgressiveJpegConfig()) // Progressive configuration
                     .setBitmapsConfig(Bitmap.Config.RGB_565) // No transparent picture display requirements, set to RGB_565, reduce memory overhead
                     .setBitmapMemoryCacheParamsSupplier(supplierMemoryCacheParams) // Configuring Caching Policy
                     .setSmallImageDiskCacheConfig(smallDiskCacheConfig) // Disk Caching Strategy for Small Pictures
                     .setMainDiskCacheConfig(normalDiskCacheConfig) // Disk Caching Strategy for Basic Pictures
                     .setMemoryTrimmableRegistry(NoOpMemoryTrimmableRegistry.getInstance()) // Register memory callers to reclaim memory when it is necessary to reclaim it
                     .setResizeAndRotateEnabledForNetwork(true) // resize network pictures to reduce memory consumption
                     .setDownsampleEnabled(true) // It must be used with ReizeOptions of ImageRequest to decode the image according to the width and height of the pixels set by Resize Options so that a smaller Bitmap can be obtained.

    At first glance, many configurations, let me explain a little:
    Image Pipeline Config: Fresco supports the use of OKHTTP as a network loading engine. This article demonstrates the use of the system's own network loading engine. Additional imports are required if the OKhttp network library is used for loading.

    compile 'com.facebook.fresco:imagepipeline-okhttp3:1.3.0'

    At the same time, OkHttpImagePipeline ConfigFactory is used to initialize ImagePipeline Config.

    OkHttpImagePipelineConfigFactory.newBuilder(context, OkHttpClient())

    setProgressiveJpegConfig: Used to set up incremental, let's go into detail later.
    setBitmapsConfig: Filtering alpha channels under RGB_565 reduces image memory consumption and further reduces the risk of OOM
    setBitmapMemoryCacheParamsSupplier: Configure the cache policy, which is mainly used to configure the maximum amount of memory available under fresco-related conditions and the number of files. We need to understand this through code.

    val memmoryCacheParams = MemoryCacheParams(
                     maxHeapSize.toInt()/4, // Maximum available memory in bytes
                     Int.MAX_VALUE, // Maximum number of pictures allowed in memory
                     maxHeapSize.toInt()/4, // The maximum amount of memory available in bytes for the total picture ready to be cleaned up but not yet deleted
                     Int.MAX_VALUE, // Maximum number of images ready to be cleared in memory
                     Int.MAX_VALUE) // Maximum size of a single image in memory

    Place the object in Supplier after setting it up

    val supplierMemoryCacheParams: Supplier<MemoryCacheParams> = Supplier { return@Supplier memmoryCacheParams }

    SetSmallImageDisk CacheConfig: Small picture Disk caching strategy, which mainly sets the location of disk storage and folder related information, available space size and other information.

    val smallDiskCacheConfig = DiskCacheConfig.newBuilder(context)
                     .setBaseDirectoryPath(context.applicationContext.cacheDir) // Setting the local root directory path for cached pictures
                     .setBaseDirectoryName(smallDiskCacheName) // Setting Cache Disk Folder Name
                     .setMaxCacheSize(diskCacheSize.toLong()) // Set the cache Disk folder size
                     .setMaxCacheSizeOnLowDiskSpace(lowDiskCacheSize.toLong()) // Setting the size of cache Disk folder in low hard disk space
                     .setMaxCacheSizeOnVeryLowDiskSpace(veryLowDiskCacheSize.toLong()) // Set the size of the cache Disk folder in very low hard disk space
                     .build()

    SetMainDisk CacheConfig: This configuration is used by default. Here we choose to use the same caching strategy as the small image Disk, but why do we need to configure two copies? We know that in the actual operation of the relevant folders read and write operations will be very frequent. Because of the capacity of available space, if there is only one DiskCache, once large files come in, most small files will be removed, so separate configuration is a good choice. As for what is a big picture and what is a small picture, Fresco offers two ways to let you load the picture freely.

    public static enum CacheChoice {
         SMALL,
         DEFAULT;
    
         private CacheChoice() {
         }
     }

    Set up when configuring ImageRequest

    var imageRequestBuilder: ImageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(url)
    imageRequestBuilder.cacheChoice = mySimpleDraweeViewParams!!.cacheChoice

    SetMemory Trimmable Registry: This is the default configuration item. Register memory callers to reclaim memory when it is necessary to reclaim it
    SetResize AndRotateEnabled ForNetwork: Allowing resize processing when setting ImageRequest reduces memory consumption and also reduces the risk of OOM
    SetDownsample Enabled: Must be used with ReizeOptions of ImageRequest, also to reduce the risk of OOM

display

After setting up the basic information of the picture, we go back to the configuration Controller section. First, we configure the imageRequest.

private fun getImageRequest(url: Uri) : ImageRequest {
    var imageRequestBuilder: ImageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(url)
    imageRequestBuilder.cacheChoice = mySimpleDraweeViewParams!!.cacheChoice // Picture Selection, Big Picture or Small Picture
    imageRequestBuilder.lowestPermittedRequestLevel = mySimpleDraweeViewParams!!.requestLevel // Which level of cache is the lowest allowable access to data?
    imageRequestBuilder.isLocalThumbnailPreviewsEnabled = true // The image request returns a thumbnail when accessing a local image
    imageRequestBuilder.isProgressiveRenderingEnabled = mySimpleDraweeViewParams!!.progressiveRenderingEnabled // Progressive
    imageRequestBuilder.resizeOptions = ResizeOptions(measuredWidth, measuredHeight) // The picture is decoded according to the set width and height.
    imageRequestBuilder.rotationOptions = RotationOptions.forceRotation(mySimpleDraweeViewParams!!.rotate) // Picture rotation angle
    return imageRequestBuilder.build()
}

Then set up some listening events

var pipelineDraweeControllerBuilder: PipelineDraweeControllerBuilder=Fresco.newDraweeControllerBuilder()
pipelineDraweeControllerBuilder.autoPlayAnimations = mySimpleDraweeViewParams!!.autoPlayAnimation
pipelineDraweeControllerBuilder.controllerListener = mySimpleDraweeViewParams!!.controllerListener
pipelineDraweeControllerBuilder.imageRequest=getImageRequest(url)

There's nothing wrong with using it.

image_fresco3!!.hierarchy = setNormalHierarchy(this)
var pipelineDraweeControllerBuilder: PipelineDraweeControllerBuilder=Fresco.newDraweeControllerBuilder()
pipelineDraweeControllerBuilder.autoPlayAnimations = mySimpleDraweeViewParams!!.autoPlayAnimation
pipelineDraweeControllerBuilder.tapToRetryEnabled = true
pipelineDraweeControllerBuilder.controllerListener = mySimpleDraweeViewParams!!.controllerListener
pipelineDraweeControllerBuilder.imageRequest=getImageRequest(url)
image_fresco3!!.setController(pipelineDraweeControllerBuilder.build())

Note that only when tapToRetryEnabled is set to true will a click-retry layer appear, and after more than four retries, the failed layer will be displayed.

Production of Progress Bar

There are currently three styles for progress bars

  1. System default linear progress bar
    hierarchy.setProgressBarImage(ProgressBarDrawable())
  2. Any picture, using rotation animation
    <?xml version="1.0" encoding="utf-8"?>
    <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
     android:drawable="@drawable/load_progress_8"
     android:pivotX="50%"
     android:pivotY="50%">
    </animated-rotate>
    Use this xml directly
    val drawable: Drawable = ContextCompat.getDrawable(context, R.drawable.ani_rotate)
    hierarchy.setProgressBarImage(AutoRotateDrawable(drawable, 1000))
  3. Customize Drawable. Modify the current canvas by onLevelChange callback method

    class LoadingProgressDrawable() : Drawable() {
    
     val images: IntArray = intArrayOf(R.drawable.load_progress_0,
             R.drawable.load_progress_1,
             R.drawable.load_progress_2,
             R.drawable.load_progress_3,
             R.drawable.load_progress_4,
             R.drawable.load_progress_5,
             R.drawable.load_progress_6,
             R.drawable.load_progress_7,
             R.drawable.load_progress_8,
             R.drawable.load_progress_9,
             R.drawable.load_progress_10,
             R.drawable.load_progress_11,
             R.drawable.load_progress_12)
    
     var mLevel: Int = 0
    
     var context: Context? = null
    
     var options: BitmapFactory.Options? = null
    
     var paint: Paint? = null
    
     constructor(context: Context) : this() {
         this.context = context
         options = BitmapFactory.Options()
         paint = Paint()
         paint!!.isAntiAlias = true
     }
    
     override fun draw(p0: Canvas?) {
         val bmp: Bitmap = BitmapFactory.decodeResource(context!!.resources, images[getIndex()], options)
         val imageWidth = bmp.width
         val imageHeight = bmp.height
         val drawableWidth = bounds.width()
         val drawableHeight = bounds.height()
         val left = (drawableWidth-imageWidth)/2
         val top = (drawableHeight-imageHeight)/2
         p0!!.drawBitmap(bmp, left.toFloat(), top.toFloat(), paint!!)
     }
    
     override fun setAlpha(p0: Int) {
         paint!!.alpha = p0
     }
    
     override fun getOpacity(): Int {
         return PixelFormat.TRANSPARENT
     }
    
     override fun setColorFilter(p0: ColorFilter?) {
         paint!!.setColorFilter(p0)
     }
    
     override fun onLevelChange(level: Int): Boolean {
         this.mLevel = level
         invalidateSelf()
         return true
     }
    
     fun getIndex(): Int {
         var index: Int = mLevel/1000
         if (index<0) {
             index=0
         }
         if (index>images.size-1) {
             index=images.size-1
         }
         return index
     }
    }

    There's nothing to say about this.

Progressive Image Loading

Progressive graphics is a loading mode from blurred to clear. Fresco only supports network pictures with JPEG file type, because local pictures are decoded at one time, so local pictures do not need to be progressive. In fresco, you can set a definition standard so that it will be displayed on a placeholder map until it reaches that standard.
This standard is achieved by implementing the Progressive JpegConfig interface

class ProgressiveJpegConfigClass : ProgressiveJpegConfig {
    override fun getNextScanNumberToDecode(p0: Int): Int {
        return p0+2
    }

    override fun getQualityInfo(p0: Int): QualityInfo {
        val isGoodEnough = p0 >= 5
        return ImmutableQualityInfo.of(p0, isGoodEnough, false)
    }
}

After the setup is complete, place it in Image Pineline Config and initialize it with fresco

var imagePipelineConfig: ImagePipelineConfig.Builder = ImagePipelineConfig.newBuilder(context)
                    .setProgressiveJpegConfig(ProgressiveJpegConfigClass())

getNextScanNumberToDecode: Returns the next number of scans that need to be decoded
getQualityInfo: Determine how many times to scan before the image can be displayed
Of course, if you want to be lazy, fresco also provides the default progressive loading scheme class, SimpleProgressive JpegConfig, to avoid the trouble of your handwriting.
Finally, just set up an incremental approach to SimpleDraweeView.

imageRequestBuilder.isProgressiveRenderingEnabled = true

Look at the evolutionary process.


Progressive 1

Progressive 2

Progressive 3

Picture Loading Event Monitor

private class DefaultBaseControllerListener : BaseControllerListener<ImageInfo>() {
    // Method of triggering when picture loading is successful
    override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
        super.onFinalImageSet(id, imageInfo, animatable)
        // Download the width of the successfully displayed picture
        println("width:${imageInfo!!.width}  height:${imageInfo!!.height}")
     }

    // Callback Method for Loading Progressive Pictures
    override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
        super.onIntermediateImageSet(id, imageInfo)
    }

    // Method of Callback when Picture Loading Failed
    override fun onFailure(id: String?, throwable: Throwable?) {
        super.onFailure(id, throwable)
    }
}

Place this listening event in the controller Listener of Pipeline Drawee Controller Builder

var pipelineDraweeControllerBuilder: PipelineDraweeControllerBuilder=Fresco.newDraweeControllerBuilder()
pipelineDraweeControllerBuilder.controllerListener = mySimpleDraweeViewParams!!.controllerListener

Additional processing of pictures

Sometimes, we want to render the loaded image. Although bitmap can be obtained in onFinal Image Set just now, this method is certainly not suitable. Fortunately, fresco provides Postprocessor to implement this function. Here's an example of using a post-processor to achieve simple scaling-up and tailoring

class CutProcess(val mBeginXPercent: Float, val mBeginYPercent: Float, val mCutWidthPercent: Float, val mCutHeightPercent: Float) : BasePostprocessor() {

    override fun process(sourceBitmap: Bitmap?, bitmapFactory: PlatformBitmapFactory?): CloseableReference<Bitmap>? {
        val width = sourceBitmap!!.width
        val height = sourceBitmap!!.height
        val beginX = width*mBeginXPercent
        val beginY = height*mBeginYPercent
        val endX = width*mCutWidthPercent
        val endY = height*mCutHeightPercent
        val bmp: CloseableReference<Bitmap> = bitmapFactory!!.createBitmap(scale(sourceBitmap), beginX.toInt(), beginY.toInt(), endX.toInt(), endY.toInt())
        return CloseableReference.cloneOrNull(bmp)
    }

    fun scale(sourceBitmap: Bitmap?): Bitmap {
        val matrix: Matrix = Matrix()
        matrix.postScale(1.1f, 1.1f, 0.5f, 0.5f)
        return Bitmap.createBitmap(sourceBitmap!!, 0, 0, sourceBitmap!!.width, sourceBitmap!!.height, matrix, true)
    }
}

Add it directly to ImageRequest when you use it

val imageRequest: ImageRequest=ImageRequestBuilder
                    .newBuilderWithSource(Uri.parse("res:///"+R.mipmap.rotate))
                    .setRotationOptions(RotationOptions.forceRotation(0))
                    .setPostprocessor(CutProcess(0f, 0f, 0.5f, 0.5f)).build()

Here's an extra tip. We just clipped the image through createBitmap, but android also provides a BitmapRegionDecoder class, which allows us to decode a rectangular area of a picture.

fun getBitmapArray(bitmap: Bitmap, quality: Int) : ByteArray {
    var os: ByteArrayOutputStream = ByteArrayOutputStream()
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os)
    return os.toByteArray()
}

/**
  * Get Bitmap in rect range
  */
fun getDecodeRegion(bytes: ByteArray, width: Int, height: Int) : Bitmap {
    val decodeRegion: BitmapRegionDecoder = BitmapRegionDecoder.newInstance(bytes, 0, bytes.size, true)
    val rect = Rect(0, 0, width, height)
    return decodeRegion.decodeRegion(rect, null)
}

This allows you to get a rect image directly

val bmp: CloseableReference<Bitmap> = bitmapFactory!!.createBitmap(CommonUtils.getDecodeRegion(CommonUtils.getBitmapArray(sourceBitmap, 100), sourceBitmap.width/2, sourceBitmap.height/2))

Supporting picture types

type SCHEME
Remote Pictures http://, https://
Local files file://
Content provider content://
Resources under asset directory asset://
Resources under res directory res://
Specify image data in Uri data:mime/type;base64, data type must comply with rfc2397 (UTF-8 only)

Control of Animation Events

Previously, we set up autoPlayAnimations in pipeline Drawee Controller Builder to allow fresco to automatically handle this broadcast event. So how can we control the start and stop of this animation by ourselves?
Look at the previous download listener. In the callback completed by the download, there is an Animatable attribute, which can be used to manipulate the animation.

private class DefaultBaseControllerListener : BaseControllerListener<ImageInfo>() {
        override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?)
}

But if this property is to be used in an external class, is it necessary to declare a global variable? fresco is certainly not such a fool. We can get the Animatable through pipeline Drawee Controller, so that we can control the animation flexibly.

image_fresco2!!.controller.animatable

Multi-graph Request and Image Multiplexing

First let's talk about the requirements according to the scenario.

  1. Sometimes, in order to load pictures faster, the background specially prepared different sizes of pictures for us. In this way, we will first display low-resolution images, and then display high-resolution images. How to deal with this in fresco? Simply configure the setLowRes ImageRequest property in the pipeline DraweeController Builder and add the image Request of the small graph as well.
    val draweeController: DraweeController=Fresco.newDraweeControllerBuilder()
     .setLowResImageRequest(ImageRequest.fromUri(Uri.parse("res:///"+R.mipmap.rotate)))
     .setImageRequest(imageRequest).setAutoPlayAnimations(true)
     .setOldController(image_fresco3!!.controller).build()
    image_fresco3!!.controller=draweeController
  2. Thumbnail
    If the local JPEG diagram has an EXIF thumbnail, pipeline can immediately return the thumbnail. DraweeView displays thumbnails first and then a full, clear large image. This feature is limited, supports only local URI s, and is in JPEG image format. This is also very simple, just configure setLocalThumbnail Preview Enabled (true) in the image Request.
    val imageRequest: ImageRequest=ImageRequestBuilder
     .newBuilderWithSource(Uri.parse("file:///storage/emulated/0/DCIM/Camera/20170529_163007.jpg"))
     .setLocalThumbnailPreviewsEnabled(true)
  3. If it is a social app, it usually has the function of mapping. In this way, local pictures and network pictures may coexist. The ideal process is, of course, to load the local image first, and then the network image if the local image does not exist. But if you add this judgment to every call, maybe the code is more cumbersome. Fortunately, fresco has helped us a lot. It's our invincible Pueline Drawee Controller Builder that provides the setFirstAvailable ImageRequests method, where ImageRequest is loaded first for anyone who is fast.
    val requests: Array<ImageRequest> = arrayOf(ImageRequest.fromUri(Uri.parse("res:///"+R.mipmap.rotate)), imageRequest)
    val draweeController: DraweeController=Fresco.newDraweeControllerBuilder()
     .setFirstAvailableImageRequests(requests)
     .setOldController(image_fresco3!!.controller).build()
    image_fresco3!!.controller=draweeController

Get only pictures

Get the successfully loaded image through the BaseBitmapDataSubscriber callback

var dataSource: DataSource<CloseableReference<CloseableImage>> =
                Fresco.getImagePipeline().fetchDecodedImage(ImageRequest.fromUri("xxx.jpg"), this)
dataSource.subscribe(CustomerBitmapDataSubscriber(), CallerThreadExecutor.getInstance())
class CustomerBitmapDataSubscriber : BaseBitmapDataSubscriber() {
    override fun onFailureImpl(p0: DataSource<CloseableReference<CloseableImage>>?) {

    }

    override fun onNewResultImpl(p0: Bitmap?) {
        println("Achieving Success")
    }
}

One thing to note is that because this bitmap also exists in Fresco's cache, it may be quickly recycled by the system and needs to be copied and saved by itself.

Clear Cache

Consistent with previous loading logic, cache cleaning is also divided into two directions: memory and disk.
We haven't configured CacheKeyFactory before, so we can use evictFromXXX directly to delete the specified url. If you configure it yourself, you need to use Image Request to delete it.

Fresco.getImagePipeline().evictFromMemoryCache(url)
Fresco.getImagePipeline().evictFromDiskCache(url)
Fresco.getImagePipeline().evictFromCache(url)

Finally, all caches are cleared

Fresco.getImagePipeline().clearMemoryCaches()
Fresco.getImagePipeline().clearDiskCaches()
Fresco.getImagePipeline().clearCaches()

On OOM

The improper use of Fresco will frequently lead to the occurrence of OOM, at least I have encountered it. Here are two suggestions for you:

  1. Please configure the setResizeOptions(resizeOptions) attribute on the default configuration of ImageRequest, which has been explained many times in this article. For pages with a large number of pictures, such as photo walls, the size of pictures must be limited; network pictures can be processed by the server to the size, quality and type of pictures before returning to the client, but for local pictures of mobile phones, only setResize Options can effectively reduce the memory cache overhead.
  2. Manual release of memory caches in low memory or out of multi-graph pages:

Reference Articles

Use of Fresco-Facebook's Image Loading Framework

Topics: Android network xml Attribute