Using Fresco to Load Images Efficiently on Android

Using Fresco to Load Images Efficiently on Android

by
Updated on October 5th, 2020

It’s well known that you don’t get a second chance at a first impression and often, it’s the first impression that becomes the lasting memory. Whether you’re buying, selling, or browsing homes, you always want to remember the hero shotthe image that represents the home perfectly. It could be the trail of flowers leading up to a grand front entrance, a cozy view of the family room fireplace, or maybe just a sneak peek from the foyer.

The one thing you definitely don’t want to remember is a loading spinnerparticularly one that prevents you from loading any more images.

Unfortunately, this is exactly what was happening for many of Redfin’s Android app users. Every release, Redfin users would run out of memory on their devices as they were browsing through images. Once they ran out, we were often unable to free that memory, requiring them to restart the app to continue.

We tried several solutions to alleviate the problem. Our first attempt was to be more conservative with our memory usage by waiting for the user to scroll to an image before loading it. This allowed us to minimize the number of images loaded at once, and made sure we never loaded an image unnecessarily. We also became a lot more conservative with our memory caches, aggressively evicting images that were off screen and reloading them if they needed to be displayed again. We used Leak Canary, an awesome debugging tool developed by Square, to track down memory leaks and avoid wasting memory. Using Leak Canary, we were able to track down several scenarios where we leaked a context, and by removing these leaks we reduced our memory footprint by up to 20%.

All of these improvements helped but did not really solve the problem. It became clear we needed a more sophisticated pipeline for loading images and to better manage our memory. This is where Fresco, an open source image-loading library developed by Facebook, came into play.

Fresco allowed us to greatly improve the performance and memory usage of displaying images to our users. The results of the awesome improvement in experience are shown below! See if you can guess which screen shows life before Fresco, and which is after…

Preloading Images In A RecyclerView

Before switching to Fresco we were forced to focus on minimizing our memory footprint even at the expense of our user experience.  One way we accomplished this was by only loading images for views currently on screen. We did this by attaching a scroll listener to our list, which is implemented using a RecyclerView, so that we only start loading our images once a user scrolls and settles on a new position in the list.

Loading Images as they come into view:

final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
recyclerList.setLayoutManager(layoutManager);
recyclerList.addOnScrollListener(new RecyclerView.OnScrollListener() {

   @Override
   public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
       if (newState == RecyclerView.SCROLL_STATE_IDLE) {
           int firstViewIndex = layoutManager.findFirstVisibleItemPosition();
           int lastViewIndex = layoutManager.findLastVisibleItemPosition();

           for (int viewIndex = firstViewIndex; viewIndex <= lastViewIndex; viewIndex++) {
               HomeCardView childView = (HomeCardView) layoutManager.findViewByPosition(viewIndex);
               childView.downloadPhoto();
           }
       }
   }
});

This implementation minimizes our memory footprint because we’re guaranteeing that images are only loaded when needed.  However, by definition this provides a bad experience – the user will have to wait for the image to load because it doesn’t start loading until the user is ready to see it.

Fresco allows us to pre-load images more aggressively so that they’ll always be fully rendered by the time the user scrolls to them. With a RecyclerView we can accomplish this by starting to load an image when the Adapter for our RecyclerView binds a view holder to a data set. This is when the View for that item is populated in the list, as well as a perfect time to launch the image request. Now the user is much less likely to have to wait for an image to load since we’re able to prefetch images as they scroll through a list. If the user scrolls back up in the list, the image will be found in our cache and can be rendered instantly.

Loading an image as soon as a View is bound:

public class RecyclerViewAdapter extends RecyclerView.Adapter<HomeCardViewHolder> {

   @Override
   public void onBindViewHolder(HomeCardViewHolder holder, int position) {
       holder.bind(getData(position));
       holder.downloadImage();
   }
}

Memory Management on Android

Android provides developers with a few different regions of memory for applications to use. The most common is the Java Heap, where the memory for standard Java objects is allocated. In this region, Java automatically tracks all references to these objects and the Java garbage collector frees the corresponding memory when an object is no longer referenced.

Storing images here was troublesome for us because devices limit the size of the Java Heap for each application. When large images were saved here, the limited space was used up quickly which prevented us from loading any more images, and sometimes even caused a crash.

However, another region of memory available to us is the ashmem region. This region operates much like a native heap where developers must manage their own memory, outside of the garbage collector’s jurisdiction. In general, Java applications don’t have access to this region. However, Android does provide some hooks to use it for images. When we decode an uncompressed image, known as a bitmap, we can mark that bitmap to be placed on the ashmem. This allows us to store the bitmaps off the Java Heap and would effectively resolve our memory issues.

As with all things that seem too good to be true, there’s a catch. The Android system will “unpin” the image after it’s finished drawing it, which effectively tells the system we’re done with this memory and it can be freed. If the image needs to be re-drawn, we would need to decode it to a bitmap again. Decoding an image is an expensive operation, doubly so because it needs to happen on the UI thread, where processing power is a precious resource. User interactions need to be buttery smooth, which can’t happen when we’re dropping frames while decoding the image. Thus, we need to be able to save the decoded bitmap to keep our app running smoothly; it’s not feasible to decode an image every time it needs to be redrawn.

This is where the magic of the Fresco pipeline happens. Fresco is able to use Android’s Native Development Kit (NDK) to manually manage when images are placed in ashmem, and more importantly, when they are unpinned from ashmem. By using Fresco, we can use ashmem as an efficient memory storage away from the Java heap and not worry about decoding our images over and over. A more thorough technical deep dive of Fresco can be found in Facebook’s development blog post announcing the Fresco release.

Delivering Results

Integrating Fresco has allowed us to deliver a much better user experience. Before switching to Fresco we regularly saw about 1% of our users get failures during loading, and on average, users had around 7.5 images fail to load. In many of these cases, users would need to quit the app to once again browse images.  By switching to Fresco, we’ve seen a whopping 90% reduction in these errors.

All of our users were experiencing dropped frames and long image load times. We’ve improved this experience by now more aggressively caching and pre-fetching our images than ever before. In our list views, we can now begin loading images before they come into view, giving the user a smoother scrolling experience. We can also keep the images around longer; once they’re displayed, we don’t need to load or decode them again.

Avatar

Anshu Rustagi

Android Developer at Redfin.

Email Anshu

Leave a Comment

Your email address will not be published. Required fields are marked *

Be the first to see the latest real estate news:

  • This field is for validation purposes and should be left unchanged.

By submitting your email you agree to Redfin’s Terms of Use and Privacy Policy

Scroll to Top