Fix APK Size Increase & Compose Obfuscation After Android 15 Upgrade
Hey guys! Upgrading your Android project can sometimes feel like navigating a maze, right? You're aiming for the latest features and performance boosts, but then you hit unexpected bumps – like a sudden increase in your APK size or issues with code obfuscation. If you've recently made the jump to Android 15, AGP 8.8.1, and Kotlin 2.0 and noticed your release APK ballooning in size, or if you're scratching your head over why your androidx.compose.*
isn't being obfuscated, you're definitely in the right place. Let's dive into these challenges and figure out how to tackle them!
Understanding the APK Size Increase After Upgrades
So, you've updated your project to target Android 15 (SDK 35), embraced the shiny new AGP 8.8.1, and hopped on the Kotlin 2.0 train. Everything seems great, until you generate a release APK and BAM! It's noticeably larger – we're talking a 2.4 MB jump, which is kinda significant. What gives?
Why the Size Increase?
There are several factors at play here, and it's usually a combination of them that leads to the increased APK size. Let's break down the main culprits:
- Kotlin Standard Library (stdlib) Updates: Kotlin 2.0 comes with updates to its standard library. These updates often include new features, performance improvements, and bug fixes – all great stuff! But, they can also introduce additional code, leading to a larger overall size. The Kotlin stdlib is a fundamental part of your app when you're using Kotlin, so these changes are unavoidable but manageable.
- AGP 8.8.1 and Build Tooling Enhancements: The Android Gradle Plugin (AGP) is the backbone of your build process. Newer versions, like 8.8.1, bring optimizations and new functionalities. However, they might also change how resources are packaged, or include updated build tools that have a larger footprint. This is all part of the evolution of the Android build system, and while it aims for efficiency, sometimes there are growing pains.
- Compose Compiler Updates: If you're using Jetpack Compose (and who isn't these days?), the Compose compiler plays a critical role. Updates to the Compose compiler can influence the generated code size. New optimizations might add code in some areas while reducing it in others, but overall, updates can contribute to the final APK size.
- Dependency Overhaul: Upgrading your project often means updating your dependencies too. Libraries evolve, and newer versions might include more features (and thus, more code) than their predecessors. It's essential to review your project dependencies and see if any specific library update is significantly contributing to the size increase.
- R8 Optimization Changes: R8 is the code shrinking, obfuscation, and optimization tool that's a key part of the Android build process. Updates to R8 can sometimes lead to different outcomes in terms of code size. While R8 generally aims to reduce size, specific optimization strategies might, in certain cases, result in a slightly larger APK. Understanding R8 optimizations is crucial for fine-tuning your build.
Digging Deeper: Analyzing Your APK
Before we jump into solutions, it's super important to understand exactly what's contributing to the increased size. Thankfully, Android Studio provides some awesome tools for this:
- APK Analyzer: This built-in tool lets you dissect your APK and see a breakdown of the size contribution from different components – code, resources, libraries, etc. You can compare APKs built with different configurations to pinpoint exactly what changed and where the size increase is coming from. The APK Analyzer is your best friend in this situation.
- Build Analyzer: Android Studio's Build Analyzer helps you understand your build process and identify bottlenecks. While it doesn't directly analyze APK size, it can reveal if certain tasks (like resource processing or code shrinking) are taking longer or producing larger outputs than expected. Using the Build Analyzer in conjunction with the APK Analyzer gives you a holistic view.
By using these tools, you can move beyond guesswork and start making informed decisions about how to optimize your APK size.
Troubleshooting androidx.compose.* Obfuscation Issues
Now, let's tackle the second part of the puzzle: why your androidx.compose.*
code isn't being obfuscated. Obfuscation is a crucial security measure that makes it harder for someone to reverse-engineer your app. If your Compose code isn't being obfuscated, it's like leaving the front door open for potential attackers. Not good!
Why Isn't Compose Being Obfuscated?
There are a few common reasons why this might be happening:
- Missing ProGuard/R8 Rules: The most likely culprit is that you're missing the necessary ProGuard or R8 rules to properly obfuscate Compose code. These rules tell the tool which classes and methods should be kept (not obfuscated) and which can be safely obfuscated. If you're missing these rules, R8 might skip obfuscating Compose code to avoid breaking your app. The ProGuard/R8 rules are essential for controlling the obfuscation process.
- Incorrect Configuration: Your ProGuard or R8 configuration might be incorrect in other ways. For example, you might have a rule that accidentally keeps too much code, or you might have disabled obfuscation altogether. Reviewing your R8 configuration is crucial.
- Compiler Options: Certain compiler options can also affect obfuscation. If you're using specific experimental features or compiler flags, they might interfere with the obfuscation process. Double-check your compiler options to ensure they're not causing issues.
The Fix: Adding the Right ProGuard/R8 Rules
The key to solving this is to add the correct ProGuard/R8 rules to your proguard-rules.pro
file. Here's a set of rules that generally works well for Compose projects:
-keep class androidx.compose.runtime.internal.* { *; }
-keep class androidx.compose.runtime.RecomposeScopeImpl { *; }
-keep class androidx.compose.runtime.RecomposeKt { *; }
-keep class androidx.compose.ui.platform.AndroidComposeView { *; }
-keep class androidx.compose.ui.platform.WrappedComposition { *; }
-keep class androidx.compose.ui.platform.ComposeView { *; }
-keep class androidx.compose.ui.platform.ViewCompositionStrategy { *; }
-keep class androidx.compose.ui.platform.WindowRecomposer_androidKt { *; }
-keep class androidx.lifecycle.ViewTreeLifecycleOwner { *; }
-keep class androidx.lifecycle.ViewTreeViewModelStoreOwner { *; }
-keep class androidx.savedstate.ViewTreeSavedStateRegistryOwner { *; }
-keepnames @kotlin.Metadata class * { *; }
Let's break down what these rules do:
-keep class androidx.compose.runtime.internal.* { *; }
: This rule keeps all classes in theandroidx.compose.runtime.internal
package and their members. This package contains internal Compose runtime classes that are essential for the framework to work correctly.-keep class androidx.compose.runtime.RecomposeScopeImpl { *; }
,-keep class androidx.compose.runtime.RecomposeKt { *; }
: These rules keep specific classes related to recomposition, a core concept in Compose's reactive UI system.-keep class androidx.compose.ui.platform.AndroidComposeView { *; }
,-keep class androidx.compose.ui.platform.WrappedComposition { *; }
,-keep class androidx.compose.ui.platform.ComposeView { *; }
,-keep class androidx.compose.ui.platform.ViewCompositionStrategy { *; }
,-keep class androidx.compose.ui.platform.WindowRecomposer_androidKt { *; }
: These rules keep classes related to hosting Compose UI within the Android view system.-keep class androidx.lifecycle.ViewTreeLifecycleOwner { *; }
,-keep class androidx.lifecycle.ViewTreeViewModelStoreOwner { *; }
,-keep class androidx.savedstate.ViewTreeSavedStateRegistryOwner { *; }
: These rules keep classes that integrate Compose with Android's lifecycle, ViewModel, and SavedState APIs.-keepnames @kotlin.Metadata class * { *; }
: This rule keeps the names of Kotlin metadata classes, which are used by Compose and other Kotlin libraries.
Important: Add these rules to your proguard-rules.pro
file in your app's build.gradle.kts
(or build.gradle
) file:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Verifying Obfuscation
After adding these rules and rebuilding your APK, how do you know if it worked? The easiest way is to use the APK Analyzer again. Open your release APK in the analyzer and inspect the classes in the androidx.compose.*
packages. If they're obfuscated, you'll see names like a.a.b
instead of the original class names. That's a sign that R8 is doing its job!
Strategies for Reducing APK Size
Okay, so we've identified why your APK might be larger and fixed the obfuscation issue. Now, let's talk about some general strategies for reducing APK size – because a smaller APK is a happier APK (and happier users!).
1. Enable Code Shrinking and Obfuscation
This might seem obvious, but it's worth reiterating. Make sure code shrinking (using R8) and obfuscation are enabled in your release build. This is the most effective way to remove unused code and make your app harder to reverse-engineer, both of which contribute to a smaller APK.
2. Use App Bundles
App Bundles are your secret weapon against APK bloat. Instead of generating a single, monolithic APK, you upload an App Bundle to the Google Play Store. The Play Store then generates optimized APKs for each user's device configuration, delivering only the code and resources they need. This can significantly reduce the download size for your users.
3. Optimize Resources
Resources (images, layouts, etc.) can take up a significant chunk of your APK size. Here are some tips for optimizing them:
- Use Vector Drawables: Vector drawables scale without losing quality and are typically much smaller than raster images (PNGs, JPEGs). Use them whenever possible for icons and simple graphics.
- Compress Images: Use tools like TinyPNG or ImageOptim to compress your images without sacrificing visual quality.
- Remove Unused Resources: Use Android Studio's