Monday 18 April 2016

My experience with using Fresco (Android image management library)

I recently worked on Replacing UIL with Fresco in an Android App. I would like to share my experience in doing so and some of the learnings that might help you if you plan to use Fresco.

If you don’t know what Fresco is yet, please look at the github repository https://github.com/facebook/fresco and official site http://frescolib.org/

If you are new to Fresco, I recommend you read article by Facebook https://code.facebook.com/posts/366199913563917/introducing-fresco-a-new-image-library-for-android/. It clearly explains the motive behind Fresco, its implementation, different memory regions in Android than an App might use and the way  Fresco works.

Assuming that you have read about how Fresco and how it works I will only explain about my observations and about the features of Fresco which is already available in the above links.

Fresco has three levels of caching
  • Bitmap cache
  • Encoded memory cache
  • Disk cache

On Android 4.x and lower, the bitmap cache’s data lives in the ashmem heap, not in the Java heap. This means that images don’t force extra runs of the garbage collector, slowing down your app.

Android 5.0 has much improved memory management than earlier versions, so it is safer to leave the bitmap cache on the Java heap.

In Android 4.x devices you will observe a lot less OutOfMemoryErrors after switching to Fresco.

Hard part

Note that if you want to replace UIL or Picasso with Fresco you will have to edit all the layout files using ImageViews which load images from network. 
Also note Drawees do not support the wrap_content value for the layout_width and layout_height attributes. http://frescolib.org/docs/using-drawees-xml.html#wrap-content. Keep these limitations in mind when you want to replace the existing image download library.


Below are some of the useful tips and code that might be useful if you are planning to use Fresco in your next project. These are the difficulties faced and some possible solutions

Bitmap Downloading 

One of the limitations of Fresco is that image request can be attached to only DraweeView and its subclasses. Right now there is no way to attach image request to ImageView or custom views extending ImageView. 

One way to handle this is to download the bitmap and set this bitmap to ImageView / custom view.
But this approach is not very direct. One way is to do this is to have a method in a helper class to return a CloseableReference of type CloseableImage (imageReference in the code below) in a callback ( don't try to read the bitmap and return bitmap ). Reason why CloseableReference is returned not the actual bitmap is because Fresco recycles the bitmap if there are no reference to bitmap. The underlining bitmap can me read from CloseableReference from Activity / Fragment.

Below is one of the ways you can do it

private static void downloadBitmap(ImageRequest imageRequest, Context context, final IBitmapDownloader iBitmapDownloader) {
 DataSource<CloseableReference<CloseableImage>> dataSourceImage = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context);
        DataSubscriber<CloseableReference<CloseableImage>> dataSubscriberImage = new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
            @Override
            public void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
                CloseableReference<CloseableImage> imageReference = dataSource.getResult();
                if (imageReference != null) {
                    iBitmapDownloader.onSuccess(imageReference);
                }
                else{
                    iBitmapDownloader.onFailure(null);
                }
            }

            @Override
            public void onFailureImpl(DataSource dataSource) {
                iBitmapDownloader.onFailure(dataSource);
            }
        };
        dataSourceImage.subscribe(dataSubscriberImage, CallerThreadExecutor.getInstance());
}

IBitmapDownloader is a simple interface with callbacks.

In the Activity or Fragment maintain a map of urls and corresponding CloseableReference


Map<String, CloseableReference<CloseableImage>> imageRefMap = new HashMap<String, CloseableReference<CloseableImage>>();

When you get the callback in the Activity add the reference to imageRefMap. We can get the bimap from the CloseableReference. Next time you can check if the CloseableReference is present for a URL in imageRefMap, if present you can get the bitmap, no need to send the request down the pipeline again. This is mostly useful in case of Bitmap being used in RecyclerView or ViewPager.  

CloseableReference<CloseableImage> imageRef = imageReference.clone();
imageRefMap.put(imageUrl,imageRef);
CloseableImage image = imageRef.get();
final Bitmap bitmap = ((CloseableBitmap) image).getUnderlyingBitmap();

Don't forget to close the references in onDestroy of the activity so that bitmaps can be garbage collected. 


Iterator<Map.Entry<String, CloseableReference<CloseableImage>>> entries = imageRefMap.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<String, CloseableReference<CloseableImage>> entry = entries.next();
            CloseableReference<CloseableImage> closeableRef = entry.getValue();
            if(closeableRef != null){
                closeableRef.close();
            }
        }


Adding to Cache Manually 


If you want to add any bitmap to cache manually, this code can be used


try {
    CacheKey cacheKey = new SimpleCacheKey(url);
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
    final byte[] byteArray = stream.toByteArray();
    Fresco.getImagePipelineFactory().getMainDiskStorageCache().insert(cacheKey, new WriterCallback() {
        @Override
        public void write(OutputStream outputStream) throws IOException {
            outputStream.write(byteArray);
        }
    });
} catch (IOException cacheWriteException) {
   
}


Checking in Disk Cache Synchronously 

In Fresco Disk cache check is asynchronous (http://frescolib.org/docs/caching.html#checking-to-see-if-an-item-is-in-cache). This is because Disk cache check is a costly operation and takes a lot of clock cycles, so the thread gets blocked. But if you are checking in Disk cache in non UI thread you can use this code to check 



CacheKey cacheKey = DefaultCacheKeyFactory.getInstance().getEncodedCacheKey(ImageRequest.fromUri(imageUrl));
        StagingArea stagingArea = StagingArea.getInstance();
        EncodedImage result = stagingArea.get(cacheKey);
        if(result != null){
            result.close();
            return true;
        }
return ImagePipelineFactory.getInstance().getMainDiskStorageCache().hasKey(cacheKey) || ImagePipelineFactory.getInstance().getSmallImageDiskStorageCache().hasKey(cacheKey);

Fresco has a very active community using it and backed by Facebook. More enhancements can be expected by the actively contributing community in the coming days.

Hope this was use useful!