Journey of Reducing Android App Size by 33% -
In the ever-evolving world of mobile app development, one key factor that significantly impacts user un-installs is the size of the app. A bloated app can lead to longer download times, more storage consumption, and ultimately, frustrated users. Recently, we embarked on a journey to reduce our app size from 60 MB to 40 MB (Around 33%), and this blog outlines the steps we took to achieve this.
Why is App Size important?
- It significanlty impacts app's Download to install conversion percentage
- Users with low storage on their device may uninstall large apps to free up space. Google Play also considers app size when it suggests apps to users that they can uninstall to free up storage.
- In a Google's experiment, they concluded - "For every 6 MB increase to an APK's size, we see a decrease in the install conversion rate of 1%"
Key Points for App Size Reduction
- Dynamic Feature Modules (DFM): Implement to modularize app components.
- Analysis of compiled APK/AAB:
- Migrating legacy dependencies to use newer libraries.
- Reviewing R8 configuration file to ensure all the keep classes are really required.
- How to use dependencyInsight to find dependencies for imports.
- Migrate frontend resources to backend where possible.
- Optimizing Vector Support for Lower Android Versions
- Monitoring Size Differences with each Pull request
Dynamic Feature Modules (DFM)
Dynamic Feature Modules allowed us to break down our app into smaller, on-demand pieces. Instead of bundling every feature into the initial download, users now only download the features they actually use. This approach not only reduced the initial app size but also optimized user experience by delivering only what’s necessary.
Analysis of compiled APK/AAB
A major part of reducing our app size involved a deep dive into the dex files. By closely inspecting these files, we were able to identify and remove unnecessary libraries, refine our R8 configuration, and streamline our app’s dependencies. Here's how we did it:
- Migrating legacy dependencies to use newer libraries - We discovered that several legacy libraries, including old versions of Material Design and Material3 libraries, were still being bundled into our dex files. By removing these unused libraries, we managed to free up significant space
- Reviewing R8 configuration file to ensure all the keep classes are really required - Our legacy R8 configuration was due for an update. We found that certain libraries, particularly from Google Play services, were not being optimized or removed as expected. By refining our R8 setup, and specifically by removing the
-keep class com.google.android.gms
rule in our ProGuard rules, we were able to allow R8 to remove some unused Google GMS libraries after thorough testing.
You can read more here about R8 Shrinking
- How to use dependencyInsight to find dependencies for imports
Additionaly you can use dependencyInsight to analyze to trace exactly where dependencies were being imported, helping us eliminate unnecessary ones
./gradlew -q :app:dependencyInsight --configuration prodReleaseRuntimeClasspath --dependency com.google.android.gms
For example we tracked down how this (net.bytebuddy) dependency was being imported in our dex files using the above command and we were able to easily identify from which module and which dependency is this import coming from as shown in the image below.
Moving frontend resources to BE
Another key step was offloading the png's/webp's images stored in the res directory to images, lotties to CDN where possible.
And By utilizing libraries like Glide, we were able to manage image loading more efficiently, reducing the app's overall size and decreasing the size.
Optimizing Vector Support for Lower Android Versions
Supporting lower Android versions often requires generating PNG files at build time, which are then shipped with the final build. We discovered that by using CompatLibraries, we could render vectors without generating these extra PNG files, thus saving valuable space.
These targeted strategies played a crucial role in our successful reduction of app size, enhancing the overall user experience and making the app more efficient. By constantly analyzing and optimizing our resources, we were able to make our app leaner and faster, proving that every megabyte matters in mobile app development. You can read here more about this https://developer.android.com/develop/ui/views/graphics/vector-drawable-resources#vector-drawables-backward-solution
Bonus Tip: Monitoring Size Differences with each Pull request -
To ensure that our app size remains optimized, we use Diffuse to check how much size difference each PR causes compared to our development branch. This allows us to catch any unintended size increases early. Additionally, an alerting mechanism can be set up to notify the team if any PR increases the size beyond a set threshold, considering the number of lines changed. This proactive approach helps maintain a lean app, preventing size bloat over time.
As shown in the image below, our Jenkins CI checks each PR the size difference and posts as comment.
This is an example of one PR where we removed a lot of unused resources in our project.
These targeted strategies played a crucial role in our successful reduction of app size, enhancing the overall user experience and making the app more efficient. By constantly analyzing and optimizing our resources, we were able to make our app leaner and faster, proving that every megabyte matters in mobile app development.