Are you facing issues with your app not installing on devices running Android versions lower than API 30? You're not alone! Many developers encounter problems when their app signing key is unexpectedly upgraded, causing compatibility issues with older Android versions. In this guide, we’ll walk you through a proven solution using Multiple APKs to ensure seamless distribution across different Android versions. Plus, we'll dive into ABI splitting to optimize performance and reduce APK size. Let’s fix your app installation issues and make your app more efficient than ever!
Problem statement
If your app is failing to install on devices running Android versions below API 30, you're not alone. The issue arises from an unexpected upgrade of the app signing key, preventing the rollout of new releases with a minSdkVersion lower than 30. Fortunately, you don’t need to create a new app on Google Play—there’s a way to resolve this issue while keeping your existing app listing and user base intact.
Proposed solution
Regarding Multiple APKs support
To successfully roll out a new release without creating a new app, we need to upload both an app bundle and an APK signed with the legacy key in every release. Google Play will automatically generate APKs signed with the new key for devices running Android R* (API level 30) and above, while older Android versions (API level 29 and below) will continue receiving updates through the legacy APKs.
This approach ensures seamless compatibility across different platform versions while maintaining a single app listing on Google Play.
How multiple APKs work
Using multiple APKs on Google Play allows you to maintain a single app entry while delivering different APKs to different devices based on compatibility. This approach provides several key benefits:
✅ Unified Product Details: You manage only one set of product descriptions, images, and metadata—keeping your app listing consistent.
✅ Consistent User Experience: Users see only one version of your app on Google Play, avoiding confusion between different APK variants.
✅ Unified Ratings & Reviews: All user reviews apply to the same app listing, regardless of which APK version is downloaded.
Seamless Updates: When a user upgrades their Android version, Google Play automatically provides the most compatible APK, ensuring a smooth transition.
This method simplifies app management while ensuring optimal performance across a wide range of devices.
API Level Considerations
The multiple APKs approach relies on the android:minSdkVersion and android:maxSdkVersion attributes in your app's manifest file. This allows you to serve different APKs based on a device’s Android version while maintaining a single app entry on Google Play.
Example Setup:
You can publish multiple APKs based on API levels, such as:
APK 1: Supports API levels 16 - 19 (Android 4.1.x - 4.4.4), using only APIs available from API level 16 or lower.
APK 2: Supports API levels 21 and above (Android 5.0+), using APIs available from API level 21 or lower.
👉 To learn how to configure APKs for different API levels, refer to Configure Product Flavors.
Versioning Rules for Multiple APKs
If an APK has a higher android:minSdkVersion, it must also have a higher android:versionCode to ensure proper updates through Google Play.
This ensures that when a device receives a system update, Google Play can offer the user the most compatible version of your app—because updates are based on an increase in the app’s version code.
For more details, refer to Rules for multiple APKs.
Avoid Using android:maxSdkVersion
⚠️ Avoid setting android:maxSdkVersion unless absolutely necessary. Android is designed to maintain backward compatibility, so properly developed apps using public APIs will work with future Android versions.
If you want to release a different APK for newer API levels, you don't need to specify maxSdkVersion—Google Play will automatically deliver the correct APK based on the minSdkVersion and versionCode.
Rules for multiple APKs
Before publishing multiple APKs for your application, it’s essential to follow these rules to ensure compatibility and smooth updates:
General Rules:
✅ Same Package & Signature: All APKs must have the same package name and be signed with the same key.
✅ Unique Version Code: Each APK must have a unique versionCode to differentiate it from other versions.
✅ Distinct Configurations: No two APKs should exactly match the same device configuration. Each APK should target a different device segment (e.g., API level, screen size, ABI).
✅ Higher API Level = Higher Version Code: An APK requiring a higher API level must have a higher versionCode.
Handling Overlapping APKs:
If multiple APKs differ only by API levels, or if they also use another distinguishing factor (e.g., screen size) but have overlapping filters, their versionCode values must increase in correlation with API levels.
Example Scenario:
Imagine you have an active APK for small - normal screen sizes with version code 0400, and you try to replace it with an APK targeting the same screen sizes but with version code 0300. This will cause an error, because users of the older APK would not be able to update the app due to the lower version code.
Why This Matters:
📌 Google Play delivers app updates only if the new APK has a higher version code than the one installed on a device.
📌 This rule ensures that if a device receives a system update, making it eligible for a higher API level, it will automatically receive the correct APK update with a higher version code.
Examples of Version Code Rules
To better understand how version codes should be assigned when using multiple APKs, consider the following scenarios:
1️⃣ API Levels Differ Only (Version Code Must Increase)
✅ If an APK for API levels 16+ (Android 4.1.x+) has a version code of 0400, then an APK for API levels 21+ (Android 5.0+) must have a version code of 0401 or higher.
📌 This ensures that devices receiving a system update to API level 21 will also receive the new APK update.
2️⃣ Overlapping Screen Sizes & API Levels (Version Code Must Increase)
✅ If one APK supports API level 16+ and small to large screens, while another supports API level 21+ and large to xlarge screens, the version codes must increase with API levels since large screens overlap.
📌 Because both APKs support large screens, a large-screen device that upgrades to API level 21 should receive the correct APK update.
3️⃣ No Overlapping Filters (Version Code Doesn’t Need to Increase)
✅ If one APK supports API level 16+ with small to normal screens, and another supports API level 21+ with large to xlarge screens, then version codes do not need to increase.
📌 Since there is no screen size overlap, no devices will transition between these APKs, so the version codes don’t need to be in sequential order.
Following these versioning rules ensures seamless updates and avoids compatibility issues when rolling out multiple APKs. 🚀
Assigning Version Codes
Each APK must have a unique versionCode to ensure proper ordering and flexibility for updates.
Since Google Play uses android:versionCode to determine whether an update is available, you need to carefully structure version codes when publishing multiple APKs.
Ordering Version Codes
📌 An APK requiring a higher API level must have a higher version code.
For example, if you release two APKs for different API levels:
The APK for API level 21+ must have a higher version code than the APK for API level 16+.
This guarantees that devices updating to a higher Android version receive the appropriate APK update.
Using a Version Code Scheme
To allow independent version updates for each APK (e.g., fixing a bug in one without updating others), use a structured versioning scheme:
✅ Leave space between version codes to allow updates without affecting all APKs.
✅ Embed the API level in the version code for clarity.
versionCode 21012
versionName '0.1.2'
//21: API level (constant)
//012: version name (incremental)
versionCode 30012
versionName '0.1.2'
//30: API level (constant)
//012: version name (incramental)
This approach ensures that updates are managed efficiently while keeping the versioning system clear and maintainable. 🚀
Split APKs Based on ABI
Why Split APKs Based on ABI?
Beyond supporting different API levels, splitting APKs based on ABI (Application Binary Interface) further optimizes performance and reduces app size.
📌 Why does this matter?
Android devices use different CPU architectures (armeabi-v7a, arm64-v8a, x86, x86_64).
Including binaries for all architectures in a single APK increases its size unnecessarily.
A device should only download the binaries it actually needs.
Implementing ABI Splitting
We can extend the multiple APKs approach by also generating separate APKs for different ABIs.
To enable ABI-based APK splitting, modify your build.gradle file:
android {
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk false // Prevents generating a universal APK
}
}
}
Benefits of ABI Splitting
✅ Smaller APKs: Only the necessary binaries are included, reducing file size.
✅ Optimized Performance: Devices install APKs optimized for their specific CPU architecture.
✅ Faster Updates: Smaller file sizes mean faster downloads and installations.
ABI-Based Version Code Strategy
Each APK must have a unique versionCode to ensure proper updates. To achieve this, we can encode API level, version name, and ABI identifiers into the version code, following a structured approach.
Example Version Code Structure
To maintain proper ordering while allowing independent updates, we structure the versionCode as:
AACBBB
Where:
AA → API level (constant)
BBB → Version name (incremental)
C → ABI identifier
versionCode 210112
versionName '1.1.2'
// 21: API level (constant)
// 012: Version name (incremental)
versionCode 300112
versionName '1.1.2'
// 30: API level (constant)
// 012: Version name (incremental)
Modify your build.gradle to implement this structured versioning:
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersion = 0
if (output.getFilter(com.android.build.OutputFile.ABI) == 'armeabi-v7a') {
abiVersion = 1
} else if (output.getFilter(com.android.build.OutputFile.ABI) == 'arm64-v8a') {
abiVersion = 2
} else if (output.getFilter(com.android.build.OutputFile.ABI) == 'x86') {
abiVersion = 3
} else if (output.getFilter(com.android.build.OutputFile.ABI) == 'x86_64') {
abiVersion = 4
}
output.versionCodeOverride = defaultConfig.versionCode abiVersion * 1000
}
}
How this works:
The first two digits (AA) represent the API level.
The next last three digits (BBB) track the app version.
The third (C) uniquely identifies the ABI:
1 → armeabi-v7a
2 → arm64-v8a
3 → x86
4 → x86_64
API Level | Version Name | ABI | Final Version Code |
---|---|---|---|
21 | 0.1.2 | armeabi-v7a | 211112 |
21 | 0.1.2 | arm64-v8a | 212112 |
21 | 0.1.2 | x86 | 213112 |
21 | 0.1.2 | x86_64 | 214112 |
Conclusion
Using this structured approach ensures:
✅ Proper versioning for updates
✅ Consistent ordering of APKs
✅ Easy maintenance of separate APKs for API levels & ABIs
This method provides a future-proof way to manage multiple APKs efficiently! 🚀
📚 References
For you to dive deeper into the technical details of multiple APK support, versioning strategies, and ABI-based APK splitting.
- Google Play Multiple APK Support
- Managing Version Codes in Android
- Android Splitting APKs by ABI
- Google Play App Signing
- Gradle Configuration for APK Splitting
Happy Solving - Great solutions lead to great experiences! ! 🚀
Top comments (0)