Distributing native libraries with your APK adds complexity to your build process and can bloat the size of your APK. You have to include a specific build of native libraries for every architecture you want to target, or to be more technically correct every ABI (Application Binary Interface) you are targeting. Different Android devices use different CPU’s architecture, which have support for different instruction sets. An ABI is different for each combination of CPU and the instruction set supported.
The recommended distribution method for your APK is to distribute one APK that contains all the static native libraries for all the ABIs being targeted. This article does not follow that path and discusses how to build, upload and manage separate APKs for your app. Each APK will include only the native libraries for a specific ABI. The following assumptions are made:
- You are using Android Studio 1.2+ and using the Android gradle plugin build system
- You are distributing your app through the Google Play store
- You already have static native libraries (.so) built with the Android NDK
- You are concerned about APK bloat caused by these native libraries
Universal APK or Multiple APKs
You need to make an educated and considered decision about if you distribute one APK that includes all native libraries or multiple APKs. The advantage of one universal APK is that it is simple to deploy and maintain your app, the disadvantage is that you potentially could have a substantial increase in APK size. I have previously described how to use SQLCipher in an Android app which discussed this tradeoff. Figure 1, below, shows the impact on size when shipping SQLCipher native libraries in a basic APK.
Play Store requirement for unique versionCode per ABI
The only way for a developer to distribute an app, with different APKs, to users on different devices is to have a unique versionCode and to ensure each APK is targeting different devices. The versionCode needs to be unique as it is possible that a users device is compatible with more than one APK, when this happens they will receive the version with the highest versionCode.
When I mention the version I am not referring to the version that is visible to the user, which is specified as the versionName, I am talking about the internal version which is a numerical versionCode that indicates an ascending order of releases. The snippet from a build.grade script below shows the default versionCode and versionName that Android Studio will apply to a new project.
defaultConfig { applicationId "com.hookedonplay.androidbycode.mynewapp" minSdkVersion 15 targetSdkVersion 21 versionCode 1 versionName "1.0" }
It is possible that you are maintaining existing apps which only specify the version in the AndroidManifest.xml. While this works well for static version information we need to use the new Android Gradle build system to be able to dynamically generate version information. Here is an example of version information added directly into the AndroidManifest.xml.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hookedonplay.androidbycode.mynewapp" android:versionCode="1" android:versionName="1.0"> ... </manifest>
Format for individual versionCodes per ABI
Each ABI needs to have an unique versionCode before it can be uploaded to the play store. We are going alter the build.gradle file to automatically generate a unique version number for each ABI. Assuming we want to build our app with versionCode 101, Our aim is to produce the following results:
The order of the ABI numbers are important as is the length of the 8 digit versionCode.
The order is based on way the Play store works with multiple APKs. Remember, if a users device is compatible with more than one APK for an application the device will receive the version with the highest version code. This is why an armeabi build will be given a prefix of 1 as this build will also be able to run a armeabi-v7a device (2) and in some cases a x86 device (8) due to a proprietary ARM translation layer called libhoudini.
The versionCode we are going to generate is 8 digits long to cater for multiple APKs that differ not only by target ABI, but also different Android SDK versions and screen sizes. An example of how to incorporate all these details into the 8 digit versionCode is shown in Figure 3 below. This is an extension of a numbering scheme suggested in the Android developer documentation where we prefix the number with the ABI id value that was listed in Figure 2.
You should be aware that this is only a suggested method for generating version information. It is not mandatory and it can be tailored to me your needs. For example, if you know that you are not going to distribute different APKs based on Android SDK API level then you could drop those two digits from the version.
Automating unique versionCode generation
Before continuing and changing your versionCode naming convention you need to understand the following:
If you are distributing one APK containing all flavors of native libraries then you do not need to worry about implementing the following version scheme.
To automate the version process we need to customize the build process. The build.gradle script for your app is where you can configure the build to produce a separate APK for each ABI type and also set the versionCode to conform with the desired 8 digit format discussed earlier.
Here is an example of a complete build.gradle that will achieve this:
apply plugin: 'com.android.application' android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.hookedonplay.androidbycode.androiddatabaseencryption" minSdkVersion 15 targetSdkVersion 22 versionCode 101 versionName "1.0" } splits { abi { enable true reset() include 'x86', 'armeabi', 'armeabi-v7a' universalApk true } } project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9] android.applicationVariants.all { variant -> variant.outputs.each { output -> output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter( com.android.build.OutputFile.ABI), 0) * 10000000 + android.defaultConfig.versionCode } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.0' }
Building separate APK files
The splits mechanism for the Android Gradle Plugin allows you to generate multiple files as shown by the example below. You can include only the ABI types you want to build and it is also possible to build an APK that includes all possible native libraries through the command universalApk true.
splits { abi { enable true reset() include 'x86', 'armeabi', 'armeabi-v7a' universalApk true } }
Generate versionCode per APK
The section of the build script that sets the versionCode of each APK to a unique number is shown in the code snippet below. Note the project.ext.versionCodes which sets the first digit of the versionCode when multiplied by 10000000 we generate the 8 digit number we require. Finally the initial versionCode of the app, accessed with android.defaultConfig.versionCode, is added to complete the full numerical version for each APK.
project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9] android.applicationVariants.all { variant -> variant.outputs.each { output -> output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter( com.android.build.OutputFile.ABI), 0) * 10000000 + android.defaultConfig.versionCode } }
Verify the versionCode included in each APK
The Android Asset Packaging Tool (aapt) is part of the Android build tools and is required to build APK files. You can also use it to verify the contents of the APK files you have generated.
You will find the aapt executable in your latest version of the build tools, for example ..\sdk\build-tools\22.0.1
Execute the following command on each of the APKs you have produced:
aapt dump badging AndroidApp.apk
You will find a wealth of information about the APK but the following items in the output will be of most interest:
versionCode='20000101' ... native-code: 'armeabi-v7a'
Below is a more detailed, but still trimmed sample output from the aapt badging request:
package: name='com.hookedonplay.androidbycode.androiddatabaseencryption' versionCode='2000101' versionName='1.0' platformBuildVersionName='5.0.1-1624448' sdkVersion:'15' targetSdkVersion:'21' application-label:'Android Database Encryption' application-icon-160:'res/mipmap-mdpi-v4/ic_launcher.png' application-icon-240:'res/mipmap-hdpi-v4/ic_launcher.png' application-icon-320:'res/mipmap-xhdpi-v4/ic_launcher.png' application-icon-480:'res/mipmap-xxhdpi-v4/ic_launcher.png' application-icon-640:'res/mipmap-xxxhdpi-v4/ic_launcher.png' application: label='Android Database Encryption' icon='res/mipmap-mdpi-v4/ic_launcher.png' application-debuggable uses-feature: name='android.hardware.touchscreen' uses-implied-feature: name='android.hardware.touchscreen' reason='default feature for all apps' main supports-screens: 'small' 'normal' 'large' 'xlarge' supports-any-density: 'true' locales: '--_--' 'ca' 'da' 'fa' 'ja' 'nb' 'de' 'af' 'bg' 'th' 'fi' 'hi' 'vi' 'sk' 'uk' 'el' 'nl' 'pl' 'sl' 'tl' 'am' 'in' 'ko' 'ro' 'ar' 'fr' 'hr' 'sr' 'tr' 'cs' 'es' 'it' 'lt' 'pt' 'hu' 'ru' 'zu' 'lv' 'sv' 'iw' 'sw' 'fr-CA' 'lo-LA' 'en-GB' 'bn-BD' 'et-EE' 'ka-GE' 'ky-KG' 'km-KH' 'zh-HK' 'si-LK' 'mk-MK' 'ur-PK' 'hy-AM' 'my-MM' 'zh-CN' 'ta-IN' 'te-IN' 'ml-IN' 'en-IN' 'kn-IN' 'mr-IN' 'mn-MN' 'ne-NP' 'gl-ES' 'eu-ES' 'is-IS' 'es-US' 'pt-PT' 'zh-TW' 'ms-MY' 'kk-KZ' 'uz-UZ' densities: '160' '240' '320' '480' '640' native-code: 'armeabi-v7a'
This same dump can be also used to verify other attributes of your APK, such as the build SDK version, locales and supported screen densities.
You will also be able to verify the targeted ABI and versionCode once you upload to the Play Store via the developer console.
Distribution of multiple APKs via alternative app stores
This article has assumed that you are distributing your app through the Google Play Store. It may be possible that you are using multiple distribution methods, such as promoting your app on the Amazon Appstore. According to the documentation the same version methodology will work for the multiple APK support within the Amazon Appstore. There are also a number of smaller app marketplaces that may automatically add your app. Each of these app store may or may not handle multiple APKs correctly which may result in some users downloading an APK that is incompatible with their device.
References
Multiple APK support in Android
http://developer.android.com/google/play/publishing/multiple-apks.html
Android apk-splits user-guide
http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
Amazon Device Targeting
https://developer.amazon.com/public/solutions/devices/fire-tv/docs/using-device-targeting
Reblogged this on Dinesh Ram Kali..
LikeLike
Reblogged this on NEW GENERATION MEDIA TECHNOLOGY.
LikeLike
Nice tip about the `aapt dump badging` command. However, why do I get the error: “It appears you do not have ‘build-tools-23.0.1’ installed.”?
LikeLike
Sounds like a path issue. Try using terminal/cmd to execute directly from the folder of your latest build tools version
LikeLike