Android - 为不同的处理器架构构建单独的 APK

Posted

技术标签:

【中文标题】Android - 为不同的处理器架构构建单独的 APK【英文标题】:Android - build separate APKs for different processor architectures 【发布时间】:2013-11-26 01:50:03 【问题描述】:

是否有一种简单的方法可以使用旧的 ANT 或新的 Gradle 构建过程为不同的处理器架构为 android 构建单独的 APK 文件?我这样做的方法是构建一个包含所有受支持的本机库的“胖”APK,然后将它们拆分为单独的 APK,如我 explained here。不过,似乎应该有更直接的方法来做到这一点……

【问题讨论】:

***.com/questions/19268647/…中讨论了一种方法 理想情况下,Google Play 本身应该能够在客户设备下载后从 APK 中去除不受支持的架构的 SO。不过,这可能需要更改签名算法 - 构建工具需要为每个架构生成单独的签名,并为胖 APK 生成另一个签名。 @SevaAlekseyev - 是的,我希望有一个更简单的解决方案。您在之前的评论中引用的方法是我自己的,我刚才使用的。它可以工作,它只是对脚本或 .bat 文件的一次调用,在 AndroidManifest.xml 中使用我编写的小程序增加了必要的版本代码。 【参考方案1】:

我决定在这里重新发布我的answer from elsewhere,以便所有这些都在一个页面上以便于访问。如果这违反了 SO 政策,请告诉我并从此处删除此帖子。

以下是我对如何为每个支持的处理器架构创建单独的 APK 文件的想法:

    使用您使用的任何工具构建一个“胖”APK,其中包含您支持的所有本机代码库,例如armeabi、armeabi-v7a、x86 和 mips。我将其称为“原始”APK 文件。

    使用任何 zip/unzip 实用程序将您的原始 APK 解压缩到一个空文件夹中,最好使用命令行工具,以便您以后可以使用 shell 脚本或批处理文件将其自动化。实际上,正如下面发布的示例批处理脚本所示,我只是使用命令行 zip/unzip 工具直接操作 APK,而不是完全解压缩它们,但效果是一样的。

    在原始 APK 解压到的文件夹中(或原始 .apk/.zip 中),删除 META-INF 子文件夹(其中包含签名,我们需要重新签名 APK全部修改后,所以必须删除原来的META-INF)。

    更改为 lib 子文件夹,并删除新 APK 文件中不需要的任何处理器架构的子文件夹。例如,只保留“x86”子文件夹来为英特尔凌动处理器制作 APK。

    重要提示:针对不同架构的每个 APK,在 AndroidManifest.xml 中必须有不同的“versionCode”编号,以及用于例如的版本代码。 armeabi-v7a 必须比 armeabi 略高(在此处阅读有关创建多个 APK 的 Google 说明:http://developer.android.com/google/play/publishing/multiple-apks.html)。不幸的是,清单文件在 APK 中是编译后的二进制形式。我们需要一个特殊的工具来修改那里的 versionCode。见下文。

    使用新版本代码修改清单并删除不必要的目录和文件后,重新压缩、签名并对齐较小的 APK(使用 Android SDK 中的 jarsigner 和 zipalign 工具)。

    对您需要支持的所有其他架构重复该过程,创建版本代码略有不同(但版本名称相同)的较小 APK 文件。

唯一突出的问题是在二进制清单文件中修改“versionCode”的方式。很长一段时间我都找不到解决方案,所以最后不得不坐下来编写自己的代码来做到这一点。作为起点,我使用了 Prasanta Paul 的 APKExtractor,http://code.google.com/p/apk-extractor/,用 Java 编写。我是老派,更喜欢 C++,所以我用 C++ 编写的小实用程序“aminc”现在在 GitHub 上:

https://github.com/gregko/aminc

我在那里发布了整个 Visual Studio 2012 解决方案,但整个程序是一个 .cpp 文件,可能可以在任何平台上编译。这是一个示例 Windows 批处理脚本文件,我使用它将名为 atVoice.apk 的“胖”apk 拆分为 4 个较小的文件,分别名为 atVoice_armeabi.apk、atVoice_armeabi-v7a.apk、atVoice_x86.apk 和 atVoice_mips.apk。我实际上将这些文件提交到 Google Play(请参阅我的应用程序 https://play.google.com/store/apps/details?id=com.hyperionics.avar)并且一切正常:

@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

额外保障

我在 Google Play 开发者控制台收到一些错误报告,指出找不到本机方法。这很可能是由于用户安装了错误的 APK,例如ARM 设备上的 Intel 或 MIPS APK。向我的应用程序添加了额外的代码,对照 Build.CPU_ABI 检查 VersionCode 编号,然后在不匹配的情况下显示错误消息,要求用户从 Google Play(或我自己的网站,我在其中发布“胖”APK)重新安装) 在这种情况下。

格雷格

【讨论】:

【参考方案2】:

在这篇文章Android NDK: Version code scheme for publishing APKs per architecture 中,我找到了一个很好的解决方案。它包括添加以下代码

 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
    

到 build.gradle 脚本的 android... 部分。如果你想了解细节,我强烈推荐阅读那篇文章,真的很值得一读。

【讨论】:

当然,这是目前(2018年)这个问题最合适的答案。我想当@gregko 给出当前投票最多的答案时,情况并非如此。首先给出这个答案会很好,以避免有人通过检查第一个答案就开始实施gregko的解决方案。 10000000不是很大吗?为什么不是 10 或 100? 是的,它会给我们它们的总和,mb 以某种方式连接(如字符串)?【参考方案3】:

从Android NDK build with ANT script 开始,改动很小:

<target name="-pre-build">
    <exec executable="$ndk.dir/ndk-build" failonerror="true"/>
    <arg value="APP_ABI=$abi"/>
</target>

并使用批处理文件运行循环(我使用简单的sed 脚本;sed 在%NDK_ROOT%\prebuilt\windows\bin\ 和所有其他平台上可用):

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

这假设清单文件中的 android.verisonCode 的最后一位数字为零,例如android:versionCode="40260".

请注意,技术上没有理由更改 armeabimips 变体的 versionCode,但保持 armeabi 。

更新感谢Ashwin S Ashok提出更好的编号方案:VERSION+10000*CPU

【讨论】:

感谢您发布此信息,亚历克斯。但是,这似乎在每次 ant 调用中重复所有 Java+Dexguard 构建,大约需要 3 分钟。在我非常快的 PC 上?我的“构建一个胖 apk,拆分”只构建一次,拆分需要几秒钟。另外,它构建时包含所有二进制文件,所以如果我不想每次都构建本机代码,是否必须将本机库复制到 libs 目录中? 您当然是对的,没有理由更改 armeabi、mips 和 x86 的 versionCode,Play 商店应该根据包含的本机代码提供正确的版本。我认为只有 armeabi 是的,我实际测试过的唯一 x86 设备(Samsung Galaxy Tab 3)有 arm 模拟器(它可以运行 armeabi 和 armeabi-v7a)。我不确定它会在 Play Store 中选择哪个版本,但为了安全起见,值得推高版本。 关于不同处理器的版本代码 - MIPS 有朝一日也可以得到 ARM 模拟器,所以最好让它们都不同。 我的x86-60900014、armeabi-v7a-20800014、mips-10900014、armeabi-10800014的版本号有错吗?

以上是关于Android - 为不同的处理器架构构建单独的 APK的主要内容,如果未能解决你的问题,请参考以下文章

Android : 为系统服务添加 SELinux 权限 (Android 9.0)

基于 HBase 构建可伸缩的分布式事务队列

关于Android架构:MVI + LiveData + ViewModel | ProAndroidDev

事件驱动架构

pay-interview

我可以构建 Armv7 + Arm64 架构,但不能单独构建 Armv7