At I/O a few years ago, Google announced Instant Apps, an innovation that promises to be good for users and app developers alike.
Instant Apps allow visitors to your Play Store listing to try before they buy – in one tap they’re able to quickly download a demo version of your app and see how it works. This is obviously great for consumers, and Google reports that this results in a ~17% increase in downloads for app developers as well. Perhaps more importantly though, Google has been telegraphing that the technologies that allow Instant Apps (dynamic modules, app bundling, etc) will be where the platform is headed; eventually, Android will support on demand features, device/segment specific builds, and more.
So, how can you make your own app Instant ready?
A basic how-to
Creating an Instant version of your app is easy – on paper. App uploads must be under 4MB and games must be under 10MB in order to count as “Instant” so if your app is already that small, the process couldn’t be simpler (and regardless, this is the first step to making an Instant enabled app):
- In Android Studio, go to File > New > New Module and select Instant Dynamic Feature Module. Fill in the fields and press finish.
- Switch to the instant configuration, build your app, and upload it to the Play Store under Your App > Release Management > Android Instant Apps. (Never published an app before? Read how to publish to the Play Store on my company’s blog!)
Voila! Your existing app will now have “Try now” button for users on Instant capable devices.
Ma app is too big
But what happens if your app is more than 4MB or your game is over 10MB? In the past, Google’s suggestions haven’t been terribly helpful, amounting to “don’t use so much stuff.” They’ve since gotten much better, but it’s still not completely clear.
So what can you do? Well, I have a few suggestions, in order of the difference it will make in app size.
Moar modules
The most effective way to reduce your app size is to break it out into dynamic feature modules. It’s also the most difficult way.
Dynamic feature modules can be many things: a specific user flow, an optional feature only used by some users, an optimization of an existing feature, or simply a way to isolate code from the rest of your app. Technically, an Instant App is just an empty dynamic feature module that relies on your app’s core module.
So, to make your instant version smaller, you have to break things out of your core module into their own dynamic feature modules. This is process is completely app specific, so there’s only so much guidance I can provide you here; suffice to say, it helps to know what is the primary service your users use your app for (and really be strict about it). From there, it becomes clear what you can safely break out of the core module.
This is especially helpful when it comes to dependencies. Dependencies can really add size to your app, so modularizing your code helps you isolate those deps to the features that use them – thereby removing the deps from your core module and decreasing your instant app size.
I will say, though, that while it’s the most effective way of doing things, it is not the easiest. Good architecture certainly helps, but it’s one thing to separate concerns; it’s a completely different thing to put them in different modules. This is primarily because it means one module cannot refer to ANYTHING in a module it does not rely upon… meaning that if you want to navigate to a different Activity, you have to jump through some additional hoops. That said, it’s also clear Google wants you to do this, either now or in the future, which means doing so may now be a good idea regardless.
R8/Proguard
Both R8 and Proguard are ways to obfuscate and minify your code. Basically they work by renaming your methods and functions to shorter identifiers. This, however, can wreak havoc on your app if you use something like Gson, or in some other way rely heavily upon reflection. You can add exceptions to your proguard-rules.pro
to prevent this from happening to the reflected classes.
Proguard is the old way of doing it, though I’ve had more success with it than with R8. To use proguard, put this in your release build type:
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
You’ll also need to set android.enableR8
to false
in your gradle.properties
file, if it’s currently set to true.
To use R8, just set android.enableR8
to true
in your gradle.properties
file. For extra optimization, you can append fullMode
to android.enableR8
.
This can drastically improve your app’s size – I’ve seen it cut app size by two thirds.
Dependencies
Gradle now offers you a lot of great ways to isolate your dependencies (in addition to separating your code into dynamic modules, as mentioned above). Dependencies can really increase your app size, so any way you can find to trim them off is a win.
You can tell gradle to only include certain dependencies in certain flavors or build configurations. This, too, can greatly reduce your app size. This latter can be done by prepending the flavor name to implementation, so, for a flavor named paid, to include a dependency only for that flavor you would say paidImplementation
.
A great way to find out which dependencies are the largest is to use the APK analyzer. Simply drag an APK into Android Studio’s editor pane and the APK analyzer will open. From there, you can see how much space each dependency is taking up, along with assets, drawables, strings, or anything else in your app.
Assets
Does your app use large assets? Sometimes you can separate these out and download them at runtime. This isn’t always possible, but some apps can save megabytes worth of space this way. You might also consider breaking up larger assets into smaller pieces based on what users will actually use in your instant app. Doing this has brought down my app size significantly in a couple asset-heavy applications.
Resources
I… have not had much success with this. If you’ve already made sure to remove unused pngs from your drawables folder, there’s not much to be done (though, doing an audit is worthwhile).
If you use a lot of PNGs, you might consider converting them to Google’s webp format instead, as webp images can be made smaller without a loss of quality. If you’d like to move to vector graphics instead, you might be able to save even more space – but so far I’ve had nothing but trouble vector stuff on Android.
Google also indicates that you can shrink resources with the aptly named shrinkResources
option in your gradle file. Paired with the resConfigs
option, you may be able to remove unused localization resources.
Messing with resources hasn’t saved me much space when I’ve tried it, and I only resort to trying when I’m really close to the 4MB line already.
Conclusion
I hope these tips and tricks have been helpful – I’m going to be speaking about this topic with a real life example at DroidCon Vienna in a couple weeks. If you’re in the area (or already attending!) come check it out! A lot of these concepts are much clearer with examples, so it should be a helpful talk. Hope to see you there!