Giphy Android App and Fresco
September 27, 2018 by
GIPHY’s Android team recently released our third major version of the GIPHY app. In the latest release, we focused on delivering new features, redesigning the home page to support GIPHY Stories, and improving our app’s performance. We also delivered an entirely new set of GIF creation tools! Behind these front-facing features for our users, we focused on fundamentally improving our app’s performance by converting the codebase to Kotlin, refactoring the navigation stack, and animating screen transitions..
Testing the new app codebase showed promising results, except for the GIF rendering. Renditions took a lot of time to load and feedback we’ve collected from our users suggested this is a common issue. The Android team headed out into the wild to find a suitable replacement for our current image rendering technology. We soon had to decide between the two major image loading libraries offering support for GIF and WebP format:
- Glide – A popular choice for image loading in Android, supported by Google.
- Fresco – Created by Facebook for efficient and fast image loading.
As both libraries offer support for disk caching, efficient image loading, and post-processing, we made our decision based on animated drawing performance.
We quickly drafted a demo app into which we loaded a ton of GIFs, similar to GIPHY’s functionality. We noticed that Glide encountered issues when rendering multiple sources at once, dropping frames from the GIF animation compared to Fresco.
Doing a quick search about Glide’s performance with GIFs, we found multiple issues on GitHub related to this issue. Developers made it clear that performance can’t be improved, unless they rewrite their rendering engine from scratch. At this point, it was clear we have a winner: Fresco
Setting up Fresco was easy. Because we use several different types of GIF renditions (i.e. thumbnails, gif details, story feed), we decided to setup two cache configs. In this way, Fresco will not be forced to evict a lot of small thumbnails, because the cache will quickly fill with high quality images.
val previewsDiskConfig = DiskCacheConfig.newBuilder(this)
.setMaxCacheSize(250L * ByteConstants.MB).build()
val qualityDiskConfig = DiskCacheConfig.newBuilder(this)
.setMaxCacheSize(250L * ByteConstants.MB).build()
val config = ImagePipelineConfig.newBuilder(this)
For rendering, we used the SimpleDraweeView class. With just some small additions to support our SDK models out of the box, we had Fresco rendering our GIFs in no time. Loading a image into a DraweeView basically means creating a new controller and assigning it to the view, like so:
val newController = Fresco.newDraweeControllerBuilder()
draweeView.controller = newController
Easy, fast, fun! Launch it!
Browsing the GIPHY app means exploring thousands of GIFs. Small animated images (thumbnails) are used to populate the home feed in GIPHY Stories or presently trending GIFs. After tapping a thumbnail, the app will open a page to present the selected GIF at full size, or to play a Story feed. For the GIF detail page and the content of a story, the app will progressively load a set of renditions, starting from low-quality images to high-quality ones. The first image to be loaded from the set is the low-quality thumbnail the user tapped. Concurrently, in the background, a high quality version of the same GIF is loaded. When it is complete, we replace the thumbnail.
We soon discovered that when making a simple request to change the resource of a DraweeView, Fresco will clear the current content, display the placeholder, then load the new resource. Even with the resources preloaded, there is a noticeable flickering. Searching for answers on Fresco’s GitHub project, we found similar problems being discussed, but for static images. We found two possible solutions:
- Fresco provides a low-quality/high-quality schema for loading images. Unfortunately, in this schema, the low-quality image is not animated.
- Use a
RetainingDataSource. This allows us to keep the same
DraweeControllerand replace its content. Unfortunately, we couldn’t make animated images play.
None of the above solutions worked out of the box, so we decided to solve the issue for this particular case of animated images. As Fresco is an open-source project, we forked their GitHub repo and started to analyze the problem. After an extensive debug session with different kind of images, we identified the following actors responsible for loading and rendering images:
RetainingDataSource looked like what we were looking for, it turns out that this data source works its magic by lying to the underlying Fresco implementation, keeping it in a forever PROGRESS state. That means the
DraweeControllers will never be notified when an GIF is loaded and they will never start to play.
Solving the issue of GIFs not being played turned out to be an easy fix by following these steps:
- Let DataSource provide multiple results. In our loading flow, a new result meant a GIF image of a higher quality.
RetainingDataSourceas a data source capable of delivering multiple results.
- Modify the Fresco
DraweeControllerbase class to deliver each result to its DraweeControllers. At this point, controllers will start playing the animated drawables.
As the open source community is extremely important for everyone here at GIPHY, we pushed all our changes to GitHub and opened a pull request to Fresco, excited to solve an issue in a library used by thousands of projects. The Fresco team was great to work with and after a few exchanged comments, they accepted the pull request.
You can check our work on the Fresco repo, here!
– Cristian Holdunu, Senior Android Developer