Is there an easy way to build separate APK files for Android for different processor architectures, with the old ANT or the new Gradle build process? My way of doing this is to build one "fat" APK with all supported native libraries included, and then splitting them into separate APK as I explained here. However, it seem that there should be a more direct method of doing this...
-
One approach is discussed in http://stackoverflow.com/questions/19268647/gradle-android-build-for-different-processor-architectures/19554367#19554367 – Seva Alekseyev Nov 13 '13 at 18:14
-
1Ideally, Google Play itself should be able to strip SO's for unsupported architectures from the APK upon download by customer's device. This would probably warrant a change in the signing algorithm, though - the build tools would need to generate a separate signature for each architecture, and another one for the fat APK. – Seva Alekseyev Nov 13 '13 at 18:16
-
@SevaAlekseyev - yes, I was hoping for an easier solution. The approach you reference in previous comment is my own, what I use just now. It works and it's just one invocation of a script or .bat file, with the necessary version code increments in AndroidManifest.xml with the little program I wrote. – gregko Nov 13 '13 at 18:30
3 Answers
I decided to re-post my answer from elsewhere here, so that all of this is on one page for easy access. If this is against SO policies, please tell me and delete this post from here.
Here is my idea on how to create separate APK files for each supported processor architecture:
Build one "fat" APK with any tools you use, containing all native code libraries you support, e.g. armeabi, armeabi-v7a, x86 and mips. I'll call it the 'original' APK file.
Unzip your original APK into an empty folder, with any zip/unzip utility, best use command line tools, so that you could automate it with a shell script or batch file later. Actually, as my sample batch script posted below shows, I just use command line zip/unzip tools to manipulate APKs directly, instead of unzipping them fully, but the effect is the same.
In the folder where original APK was uncompressed to (or in the original .apk/.zip), delete META-INF sub-folder (this contains the signatures, we'll need to re-sign the APK after all the modifications, so the original META-INF must be deleted).
Change to lib sub-folder, and delete the sub-folders for any processor architectures you don't want in the new APK file. For example, leave only 'x86' sub-folder to make an APK for Intel Atom processors.
Important: each APK for a different architecture, must have a different 'versionCode' number in AndroidManifest.xml, and the version code for e.g. armeabi-v7a must be slightly higher than the one for armeabi (read Google directions for creating multiple APKs here: http://developer.android.com/google/play/publishing/multiple-apks.html ). Unfortunately, the manifest file is in a compiled binary form inside the APK. We need a special tool for modifying the versionCode there. See below.
Once the manifest is modified with a new version code, and unnecessary directories and files deleted, re-zip, sign and align your smaller APK (use jarsigner and zipalign tools from Android SDK).
Repeat the process for all other architectures you need to support, creating smaller APK files with slightly different version codes (but the same version name).
The only outstanding issue is the way to modify ‘versionCode’ in binary manifest file. I could not find a solution for this for a long time, so finally had to sit down and crank my own code to do this. As the starting point, I took APKExtractor by Prasanta Paul, http://code.google.com/p/apk-extractor/, written in Java. I’m the old school and still more comfortable with C++, so my little utility program 'aminc' written in C++ is now on GitHub at:
https://github.com/gregko/aminc
I posted the entire Visual Studio 2012 solution there, but the whole program is a single .cpp file which probably can be compiled on any platform. And here is a sample Windows batch script file I use to split my "fat" apk named atVoice.apk into 4 smaller files named atVoice_armeabi.apk, atVoice_armeabi-v7a.apk, atVoice_x86.apk and atVoice_mips.apk. I actually submit these files to Google Play (see my app at https://play.google.com/store/apps/details?id=com.hyperionics.avar) and all works perfectly:
@echo off
REM My "fat" apk is named atVoice.apk. Change below to whatever or set from %1
set apkfile=atVoice
del *.apk
REM My tools build atVoice-release.apk in bin project sub-dir.
REM Copy it herefor splitting.
copy ..\bin\%apkfile%-release.apk %apkfile%.apk
zip -d %apkfile%.apk META-INF/*
REM ------------------- armeabi ------------------------
unzip %apkfile%.apk AndroidManifest.xml
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi-v7a/* lib/x86/* lib/mips/*
aminc AndroidManifest.xml 1
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_armeabi.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_armeabi.apk MyKeyName
zipalign 4 %apkfile%_armeabi.apk %apkfile%_armeabi-aligned.apk
del %apkfile%_armeabi.apk
ren %apkfile%_armeabi-aligned.apk %apkfile%_armeabi.apk
REM ------------------- armeabi-v7a ---------------------
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi/* lib/x86/* lib/mips/*
aminc AndroidManifest.xml 1
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_armeabi-v7a.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_armeabi-v7a.apk MyKeyName
zipalign 4 %apkfile%_armeabi-v7a.apk %apkfile%_armeabi-v7a-aligned.apk
del %apkfile%_armeabi-v7a.apk
ren %apkfile%_armeabi-v7a-aligned.apk %apkfile%_armeabi-v7a.apk
REM ------------------- x86 ---------------------
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi/* lib/armeabi-v7a/* lib/mips/*
aminc AndroidManifest.xml 9
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_x86.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_x86.apk MyKeyName
zipalign 4 %apkfile%_x86.apk %apkfile%_x86-aligned.apk
del %apkfile%_x86.apk
ren %apkfile%_x86-aligned.apk %apkfile%_x86.apk
REM ------------------- MIPS ---------------------
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi/* lib/armeabi-v7a/* lib/x86/*
aminc AndroidManifest.xml 10
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_mips.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_mips.apk MyKeyName
zipalign 4 %apkfile%_mips.apk %apkfile%_mips-aligned.apk
del %apkfile%_mips.apk
ren %apkfile%_mips-aligned.apk %apkfile%_mips.apk
del AndroidManifest.xml
del %apkfile%.apk
:done
Additional safeguards
I get a few error reports at Google Play developer console, stating that a native method could not be found. Most probably this is caused by the user installing a wrong APK, e.g. Intel or MIPS APK on an ARM device. Added extra code to my app, checking the VersionCode number against Build.CPU_ABI, then displaying an error message in case of mismatch, asking the user to re-install from Google Play (or my own website, where I post a "fat" APK) in such case.
Greg
In this article Android NDK: Version code scheme for publishing APKs per architecture I have found a nice solution to this problem. It consists in adding the following code
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
}
}
to the android{...}
section of the build.gradle script. If you want to understand the details, I highly recommend to read that article, it is really worth reading.

- 2,148
- 5
- 23
- 34
-
Of course, this is the most appropriate answer for the question at this time (2018). I guess it was not the case when current most-voted answer by @gregko was given. It would be nice to make this answer at first place, to avoid situation when someone will start implementing gregko's solution by just checking the first answer. – Dariusz Wiechecki Feb 22 '18 at 08:14
-
-
yeah it will give us sum of them, mb concatenate somehow (like strings)? – user924 May 03 '18 at 09:55
Start with Android NDK build with ANT script, with minimal change:
<target name="-pre-build">
<exec executable="${ndk.dir}/ndk-build" failonerror="true"/>
<arg value="APP_ABI=${abi}"/>
</target>
and use a batch file to run the loop (I use a simple sed
script; sed is available in %NDK_ROOT%\prebuilt\windows\bin\
and on all other platforms):
sed -i -e "s/versionCode=\"\([0-9]*\).]\"/versionCode=\"\11\"/" AndroidManifest.xml
ant -Dsdk.dir=%SDK_ROOT% -Dndk.dir=%NDK_ROOT% -Dabi=armeabi release
ren %apkfile%.apk %apkfile%_armeabi.apk
sed -i -e "s/versionCode=\"\([0-9]*\).\"/versionCode=\"\12\"/" AndroidManifest.xml
ant -Dsdk.dir=%SDK_ROOT% -Dndk.dir=%NDK_ROOT% -Dabi=mips release
ren %apkfile%.apk %apkfile%_mips.apk
sed -i -e "s/versionCode=\"\([0-9]*\).\"/versionCode=\"\13\"/" AndroidManifest.xml
ant -Dsdk.dir=%SDK_ROOT% -Dndk.dir=%NDK_ROOT% -Dabi=armeabi-v7a release
ren %apkfile%.apk %apkfile%_armeabi-v7a.apk
sed -i -e "s/versionCode=\"\([0-9]*\).\"/versionCode=\"\14\"/" AndroidManifest.xml
ant -Dsdk.dir=%SDK_ROOT% -Dndk.dir=%NDK_ROOT% -Dabi=x86 release
ren %apkfile%.apk %apkfile%_x86.apk
This assumes that android.verisonCode in the manifest file has zero as last digit, e.g. android:versionCode="40260"
.
Note that there is technically no reason to change versionCode for armeabi and mips variants, but it may be important to keep armeabi < armeabi-v7a < x86.
Update Thanks to Ashwin S Ashok for proposing an even better numbering scheme: VERSION+10000*CPU
.
-
Thank you for posting this, Alex. However, this seems to repeat all Java+Dexguard build with each ant call, and it takes about 3 min. on my very fast PC? My "build one fat apk, split" builds only once, split takes a few seconds. Plus, it builds with all binaries included, so if I don't want to build the native code each time, would have to copy native libs in and out of libs directory? – gregko Nov 17 '13 at 20:28
-
You're right of course about no reason to change versionCode for armeabi, mips and x86, the Play Store should serve correct versions based on native code included. I think only armeabi < armeabi-v7a would be important. Oh, unless x86 has an arm emulator built in... Guess, better to keep them all different. – gregko Nov 17 '13 at 20:30
-
Yes, the only x86 device that I actually tested (Samsung Galaxy Tab 3) has arm emulator (it can run armeabi and armeabi-v7a). I am not sure which version it will choose in Play Store, but to be on the safe side, it's worth pushing the version up. – Alex Cohn Nov 18 '13 at 07:38
-
Why would **ant** run the Java compiler and dex and other tools again? None of the dependencies change when you change APP_ABI. – Alex Cohn Nov 18 '13 at 07:40
-
Not sure why - IntelliJ Idea created the ant script for me, I modified it to add DexGuard protection etc. It's a complex beast... And if changing APP_ABI means that all native code has to be re-built, it takes much longer for my project than Java + Dex build. Maybe I would have to move the native .so libraries in and out of libs folder to make this approach work. – gregko Nov 19 '13 at 16:02
-
1About version codes for different processors - MIPS could one day get ARM emulator too, so maybe it's better to keep all of them different. – gregko Nov 19 '13 at 16:02
-
If you provide one `APP_ABI` at a time, you build the same code with each toolchain separately. But you definitely gain nothing if you build `APP_ABI=all` - the same loop happens *inside* **ndk_build**. – Alex Cohn Nov 19 '13 at 16:25
-
I know and this was not my concern. I rarely update my native code, much more often Java. So I keep all native libs pre-built, ad want to build my separate APKs without repeating the very long native build each time. Ant seems to package for me _all_ the .so native libraries it finds in project 'libs' folder, that's why I say I would need to move them in and out to make separate arm, x86 etc. variants of APK. – gregko Nov 19 '13 at 21:41
-
can you please tell me the version code arrangement in the order with an example. – Ashwin S Ashok Jul 16 '14 at 04:35
-
1My Version code for x86- 60900014, armeabi-v7a-20800014,mips-10900014,armeabi-10800014 is there any mistake? – Ashwin S Ashok Jul 16 '14 at 04:36
-
I proposed a scheme when version code was "encoded" as VERSION*1000+CPU, but your scheme may be even better – Alex Cohn Jul 16 '14 at 18:44