Photo by Markus Spiske on Unsplash.
When you work on a large app, every so often someone will decide that it is time to care about app size. There will be a presentation with a worrisome chart that marches inexorably up and to the right, and when people look at the chart, they will frown very solemnly. "Mankind went to the moon with 72KB," the next slide will say, "why do we need 200MB to tell the user it's their birthday?"
There will be a "Task Force" or a "Tiger Team", and if you find yourself on it, you will probably take a look at image compression. Image compression is awesome! As my economist friends are always telling me, "there is no such thing as a free lunch",1 but image compression is very close, I think? It is a heavily subsidized lunch. You find a big image, you throw it at some mystical combination of command line consonants like ImageMagick (?) or zopfli (??) and out pops a smaller image — often at the exact same quality. The line on the chart begins to go down, the solemn frowns turn to approving smiles, everyone gets a good story for their next performance review.
Microfilm is a new Gradle plugin designed to make this process easy and automatic for Android apps.
Of course, you can compress images on your own without the help of a Gradle plugin, but there are a couple of things that make this hard:
- You have to remember to do it! Every single time you add an image, you have to remember to compress it manually. And not just you — everyone who works on the app with you has to remember it too. It's easy to forget.
- You have to choose compression settings! Lossy or lossless? If lossy, at what quality? Do you remember what quality you picked last time? Do you know what quality your teammates are picking? If lossless, did you remember to tell the compressor to try super extra hard2 to make the image small or did you use the default settings? Some compressors are lazy and won't put in their maximum effort by default!
Microfilm solves all of these problems by automatically compressing resource images from PNG to WebP (Google's recommended image format) using explicitly configured compression settings. If you forget to compress an image, Microfilm fails the build. When you choose compression settings, they show up as explicit choices in your repository and in code review. If you decide to change those settings later, Microfilm still has the uncompressed source image and can generate a new compressed asset instantly.
If you're familiar with the workflow for screenshot testing libraries like Paparazzi, then Microfilm should feel very familiar. There is a Gradle task that performs an action and records the outcome, and then there is another Gradle task that checks the current repository state against that record:
compressMicrofilmscans your module'sres/drawable*directories for PNG source images, moves them to a separatemicrofilm/drawable*directories, generates compressed WebP images based on the compression settings that you declare in the module'sbuild.gradlefile, and records everything in a manifest file.verifyMicrofilmreads the manifest and checks it against the state of your module. It makes sure the source image hashes, the compressed image hashes, and the compression settings all match, and fails if it finds any discrepancies.
Microfilm is flexible and pragmatic about how you apply it to existing projects, so if you have a module with nothing but PNG images, that's great! You can apply Microfilm and compress everything right away:
kotlin1microfilm { 2 compress { 3 lossless = true 4 compressionFactor = 100 5 compressionMethod = 6 6 } 7}
But if you have a mix of other image formats, some of them lossy and some of them lossless, that's ok too. You can add rules to exclude the existing images but apply Microfilm to new images while you work on tracking down high quality sources for the old images:
kotlin1microfilm { 2 compress { 3 lossless = true 4 compressionFactor = 100 5 compressionMethod = 6 6 } 7 8 compress("**/*_lossy.png") { 9 lossless = false 10 compressionFactor = 100 11 compressionMethod = 6 12 } 13 14 exclude("**/*_excluded.png") 15}
That's the approach we took on Cash App, and we found that it worked well. Applying Microfilm early helped us immediately prevent future regressions, and the explicit set of exclude rules gave us a clear burndown list of old image sources to work on. We were ultimately able to migrate every single image to Microfilm except one.3
If your app is already tiny, congratulations, I'm not jealous at all. But if your app could stand to lose a few bytes, check out the Microfilm GitHub repo here, and let us know how it goes.
As with all things worth doing, Microfilm was a group effort. Thanks to Eric Labelle for his support and partnership from design through implementation and integration, thanks to Derek Ellis for solving cwebp binary publishing, and thanks to Josh Friend for being our resident Gradle expert.
Footnotes
-
Shout out to TINSTAAFL, my all time favorite acronym. ↩
-
For example,
cwebpuses a compression method of 4 by default, but if you pass-m 6it will spend a longer time on optimization and will possibly produce a smaller image. ↩ -
Someday I will find you
card_tortoise_roughness.png. ↩

