Android - создавайте отдельные APK для разных архитектур процессоров

Есть ли простой способ создать отдельные файлы APK для Android для разных архитектур процессоров с помощью старого ANT или нового процесса сборки Gradle? Мой способ сделать это - создать один «толстый» APK со всеми поддерживаемыми встроенными библиотеками, а затем разделить их на отдельные APK, как я объяснил здесь. Однако кажется, что должен быть более прямой способ сделать это ...


person gregko    schedule 13.11.2013    source источник
comment
Один из подходов обсуждается в stackoverflow.com/questions/19268647/   -  person Seva Alekseyev    schedule 13.11.2013
comment
В идеале сам Google Play должен иметь возможность удалять SO для неподдерживаемых архитектур из APK после загрузки с устройства клиента. Это, вероятно, потребует изменения алгоритма подписи - инструменты сборки должны будут генерировать отдельную подпись для каждой архитектуры и еще одну для толстого APK.   -  person Seva Alekseyev    schedule 13.11.2013
comment
@SevaAlekseyev - да, я надеялся на более простое решение. Подход, о котором вы упоминаете в предыдущем комментарии, является моим собственным, то, что я использую только сейчас. Он работает, и это всего лишь один вызов скрипта или файла .bat с необходимыми приращениями кода версии в AndroidManifest.xml с помощью небольшой программы, которую я написал.   -  person gregko    schedule 13.11.2013


Ответы (3)


Я решил повторно опубликовать свой ответ из другого места здесь, чтобы все это было на одной странице для облегчения доступа. Если это противоречит политике SO, сообщите мне и удалите этот пост отсюда.

Вот моя идея о том, как создавать отдельные файлы APK для каждой поддерживаемой архитектуры процессора:

  1. Создайте один «толстый» APK с любыми используемыми вами инструментами, содержащими все библиотеки собственного кода, которые вы поддерживаете, например armeabi, armeabi-v7a, x86 и mips. Я назову его «оригинальным» файлом APK.

  2. Разархивируйте исходный APK в пустую папку с помощью любой утилиты zip / unzip, лучше всего используйте инструменты командной строки, чтобы вы могли автоматизировать его позже с помощью сценария оболочки или пакетного файла. На самом деле, как показывает мой пример пакетного скрипта, опубликованный ниже, я просто использую инструменты командной строки для zip / unzip для непосредственного управления APK-файлами, вместо того, чтобы полностью распаковывать их, но эффект тот же.

  3. В папке, в которую был распакован исходный APK (или в исходном .apk / .zip), удалите подпапку META-INF (она содержит подписи, нам нужно будет повторно подписать APK после всех изменений, поэтому исходный META-INF должен быть удален).

  4. Перейдите в подпапку lib и удалите подпапки для любых архитектур процессоров, которые вам не нужны в новом файле APK. Например, оставьте только подпапку «x86» для создания APK для процессоров Intel Atom.

  5. Важно: каждый APK для другой архитектуры должен иметь другой номер versionCode в AndroidManifest.xml и код версии, например, armeabi-v7a должен быть немного выше, чем armeabi (см. инструкции Google по созданию нескольких APK здесь: http://developer.android.com/google/play/publishing/multiple-apks.html). К сожалению, файл манифеста находится в скомпилированном двоичном виде внутри APK. Нам понадобится специальный инструмент для изменения versionCode там. См. ниже.

  6. После того, как манифест изменен с использованием кода новой версии и ненужные каталоги и файлы удалены, повторно заархивируйте, подпишите и выровняйте меньший APK (используйте инструменты jarsigner и zipalign из Android SDK).

  7. Повторите процесс для всех других архитектур, которые вам необходимо поддерживать, создав файлы APK меньшего размера с немного разными кодами версий (но с тем же именем версии).

Единственная нерешенная проблема - это способ изменить "versionCode" в двоичном файле манифеста. Я долго не мог найти решение для этого, поэтому, наконец, пришлось сесть и провернуть свой собственный код, чтобы сделать это. В качестве отправной точки я взял APKExtractor от Прасанты Пола, http://code.google.com/p/apk-extractor/, написанный на Java. Я представитель старой школы, и мне еще удобнее работать с C ++, поэтому моя небольшая служебная программа aminc, написанная на C ++, теперь находится на GitHub по адресу:

https://github.com/gregko/aminc

Я разместил там все решение Visual Studio 2012, но вся программа представляет собой единый файл .cpp, который, вероятно, можно скомпилировать на любой платформе. А вот пример файла пакетного сценария Windows, который я использую для разделения моего «толстого» apk с именем atVoice.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, например Intel или MIPS APK на устройстве ARM. В мое приложение добавлен дополнительный код, проверяющий номер VersionCode на Build.CPU_ABI, затем отображающий сообщение об ошибке в случае несоответствия с просьбой к пользователю переустановить из Google Play (или моего собственного веб-сайта, где я публикую "толстый" APK ) в таком случае.

Грег

person gregko    schedule 14.11.2013

В этой статье Android NDK: схема кода версии для публикации APK для каждой архитектуры Я нашел хорошее решение этой проблемы. Он заключается в добавлении следующего кода

 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
    }
}

в раздел android{...} скрипта build.gradle. Если вы хотите разобраться в деталях, настоятельно рекомендую прочитать эту статью, ее действительно стоит прочитать.

person Andrew    schedule 12.10.2016
comment
Конечно, это наиболее подходящий ответ на вопрос на данный момент (2018 г.). Я думаю, что это не тот случай, когда был дан текущий ответ с наибольшим количеством голосов от @gregko. Было бы неплохо дать этот ответ в первую очередь, чтобы избежать ситуации, когда кто-то начнет реализовывать решение gregko, просто проверив первый ответ. - person Dariusz Wiechecki; 22.02.2018
comment
Неужели 10000000 не так уж и много? почему не 10 или 100? - person user924; 03.05.2018
comment
да, это даст нам их сумму, mb как-то объединить (например, строки)? - person user924; 03.05.2018

Начните с сборки Android NDK с помощью сценария ANT с минимальными изменениями:

<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".

Обратите внимание, что технически нет причин изменять versionCode для вариантов armeabi и mips, но может быть важно сохранить armeabi ‹armeabi-v7a‹ x86 .

Обновление Благодарим Эшвина С. Ашока за предложение еще лучшей схемы нумерации: VERSION+10000*CPU .

person Alex Cohn    schedule 17.11.2013
comment
Спасибо, что разместили это, Алекс. Однако похоже, что это повторяет всю сборку Java + Dexguard с каждым вызовом муравья, и это занимает около 3 минут. на моем очень быстром ПК? Моя сборка из одного толстого apk, разделение сборки только один раз, разделение занимает несколько секунд. Кроме того, он строится со всеми включенными двоичными файлами, поэтому, если я не хочу каждый раз создавать собственный код, придется копировать собственные библиотеки в каталог libs и из него? - person gregko; 18.11.2013
comment
Вы правы, конечно, насчет того, что нет причин менять versionCode для armeabi, mips и x86, Play Store должен обслуживать правильные версии на основе включенного нативного кода. Я думаю, что важен только armeabi ‹armeabi-v7a. О, если только x86 не имеет встроенного эмулятора руки ... Думаю, лучше оставить их все разными. - person gregko; 18.11.2013
comment
Да, единственное устройство x86, которое я тестировал (Samsung Galaxy Tab 3), имеет эмулятор руки (он может запускать armeabi и armeabi-v7a). Я не уверен, какую версию он выберет в Play Store, но на всякий случай стоит повысить версию. - person Alex Cohn; 18.11.2013
comment
Зачем ant снова запускать компилятор Java, dex и другие инструменты? Ни одна из зависимостей не меняется при изменении APP_ABI. - person Alex Cohn; 18.11.2013
comment
Не уверен, почему - IntelliJ Idea создал для меня сценарий ant, я изменил его, добавив защиту DexGuard и т.д. мой проект, чем сборка Java + Dex. Возможно, мне пришлось бы переместить собственные библиотеки .so в папку libs и из нее, чтобы этот подход работал. - person gregko; 19.11.2013
comment
О кодах версий для разных процессоров - MIPS однажды может получить и эмулятор ARM, так что, может быть, лучше оставить их все разными. - person gregko; 19.11.2013
comment
Если вы предоставляете по одному APP_ABI за раз, вы создаете один и тот же код для каждой инструментальной цепочки отдельно. Но вы определенно ничего не получите, если соберете APP_ABI=all - тот же цикл происходит внутри ndk_build. - person Alex Cohn; 19.11.2013
comment
Я знаю, и меня это не волновало. Я редко обновляю свой собственный код, гораздо чаще Java. Поэтому я сохраняю все встроенные библиотеки предварительно созданными, и хочу создавать свои отдельные APK, не повторяя каждый раз очень длинную встроенную сборку. Кажется, Ant упаковывает для меня все собственные библиотеки .so, которые он находит в папке проекта 'libs', поэтому я говорю, что мне нужно будет перемещать их внутрь и наружу, чтобы создать отдельные варианты руки, x86 и т. Д. APK. - person gregko; 20.11.2013
comment
Подскажите пожалуйста расположение кода версии в порядке с примером. - person Ashwin S Ashok; 16.07.2014
comment
Мой код версии для x86-60900014, armeabi-v7a-20800014, mips-10900014, armeabi-10800014 есть ли ошибка? - person Ashwin S Ashok; 16.07.2014
comment
Я предложил схему, когда код версии был закодирован как VERSION * 1000 + CPU, но ваша схема может быть даже лучше - person Alex Cohn; 16.07.2014