Whether it’s battery usage, network, or time, we care a lot about our users’ resources. A big app creates a barrier of entry for users on metered or low speed networks. Last Thanksgiving, our automated alerts let us know that our app size was getting larger than we’d like, and making it harder for low resource users to download our app.

Here we saw an upward trend in app size as we added more features and built a more compelling app. In order to tackle app size, we first had to understand where that size was coming from.

Breakdown of a release build around September 2015.

Looking at these two graphs we could see that images took up a large percentage of the overall APK size. But why did they take up a larger percentage of space once it was compressed?

The reason was that images are not compressed during the APKs zipping process. This is to allow images to be memory mapped from disk rather than loading them into RAM. This is a great optimization of modern operating systems like Android, but means that we cannot rely on the APK process to optimize images for us. Every byte in an image translates to a byte in the final APK.

It started to look like we could make a big difference by reducing the file size of our images. On our hunt for potential improvements, we learned that Android 4.2.1 introduced a new image format called WebP that claimed better compression than JPEG or PNG. Fortunately, the Yelp app supports Android 4.4+, and its images are mostly PNGs with a few JPEGs. To test WebP’s claims, we converted over 2000 of our PNGs with this command:

cwebp -pass 10 -m 6 -mt -lossless [1]

Only 8 of the over 2000 PNGs we compressed did not benefit from the conversion to WebP, and the difference for those was extremely slight (only around 400 bytes). Overall our compressed APK size dropped from 27.1MB to 23.1MB with no loss in the quality of images. Our breakdown still favoured images by quite a bit, but not as significantly.

This size reduction was great, but we had concerns about runtime performance. Would WebP require a lot more processing time to decode/render? Would we end up using more memory? Using Android’s performance tools, we didn’t find any increase in memory or CPU load when loading WebP images compared to their PNG variants on a variety of devices. Similar results were reported by this paper on WebP, which is Android agnostic.

Having no remaining concerns, we moved this to production. We set up a pre-commit hook that converts PNGs automatically to lossless WebP. This allowed us to enjoy a much smaller app without having to do any extra development work - win!

However the story doesn’t end there…

What about lossy content like JPEGs for photos?

As part of an experiment for onboarding, we added three new photos to dazzle and amaze new users. However, when this experiment was pushed to master, our alerting let us know it was very unhappy.

Why was it so mad about this new picture!?

It turned out that some of these images were pretty large JPEGs, the largest being 1.4MB. Remember that these will not be compressed in the final APK, so each byte of every image will ship to users. All the new assets combined to give us a 34% increase in APK size over our previous release, from 21.88MB to 29.39MB!

To compress these images, we tried converting to lossless WebP like we did before. However, these new images were rich photos with lots of color, so we didn’t see similar gains from a lossless compression. With lossless compression a dead end, we tried using lossy compression instead:

cwebp -pass 10 -m 6 -mt -jpeg_like -q 60 [2]

1,463KB lossless

92KB 60 quality lossy

62KB 40 quality lossy

The reductions were huge! After converting all the new images, our final APK size was 22.76MB, which was a great improvement over 29.39MB.

Because this process was highly subjective, we did not add this to our pre-commit hooks. Some images, like icons, should always be very high quality and only be compressed losslessly; while others, like large photos, will benefit from various degrees of lossy compression. Making that decision is hard to automate.

For those stuck with PNGs (supporting a minSdkVersion less than 17) or those who aren’t quite ready to jump ship to WebP, Colt McAnlis wrote a very detailed article on PNG compression. It includes many methods to removing unnecessary bytes from images that fall outside the scope of this post.

WebP conversion was a quick and easy way for us to dramatically reduce our APK size. If your app uses a lot of images, this strategy should help you too! While the effects of reducing APK size are not well known, it’s still pretty much a no-brainer: when we can easily reduce our impact on our users’ storage and data usage, we will!

Footnotes

  1. Cwebp is a program provided by Google to convert images to WebP. Here, we let it do the maximum amount of analysis passes (-pass 10). We set the compression method to the slowest (-m 6) in order to get the best compression. We enabled multithreading (-mt) for some speed improvements. And finally, we specified that we want the image to be lossless (-lossless) so that we’ll have no quality lost in our produced WebP image. There are many more options available for the astute reader. 

  2. Here we’ve removed the lossless (-lossless) option so that cwebp will actually remove image quality. We told cwebp to use a compression method similar to JPEG (-jpeg_like) because we know these are all photos. And finally we have the quality setting (-q 60) which is similar but different from JPEG quality settings. The value of 60 was selected after experimenting with different values for these specific photos, and seemed to give us the biggest reduction in file size before much quality loss was noticeable. Your specific images and quality thresholds will vary. 

Back to blog