Сборка Gradle Android для разных архитектур процессоров

Я хочу создать 4 отдельных apks для 4 разных архитектур процессоров Android (armeabi armeabi-v7a x86 mips) с помощью Gradle.

У меня есть собственные библиотеки OpenCV, созданные для четырех архитектур ЦП, в папке libs.

libs
    -armeabi
    -armeabi-v7a
    -x86
    -mips

Я хочу, чтобы каждый apk содержал только библиотеку OpenCV, соответствующую правильной архитектуре процессора.

Текущий сценарий сборки выглядит следующим образом:

apply plugin: 'android'

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    compile project(':workspace:OpenCV4Android:sdk:java')
}

android {
    compileSdkVersion 11
    buildToolsVersion "18.1.0"

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')

        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')

        flavorGroups "abi", "version"
        productFlavors {
            x86 {
                flavorGroup "abi"
            }
            arm {
                flavorGroup "abi"
            }
            mips {
                flavorGroup "abi"
            }
        }

    }
}

Может кто-нибудь помочь мне решить эту проблему, пожалуйста?

Ваше здоровье,


person Blukee    schedule 09.10.2013    source источник
comment
ты решил свою проблему? Мне тоже нужно такое решение, хотя я все еще использую сборку ANT. Переключился бы на градиент, если бы у приведенного выше был четкий ответ. Пытался подойти к проблеме с противоположной стороны: удалить ненужные нативные библиотеки из .apk (который на самом деле является zip-файлом) и заново подписать его. Это легко, но нам также нужно изменить AndroidManifest.xml, чтобы указать немного другой код версии для каждой платформы (также: более низкий для armeabi и более высокий для armeabi-v7a), и этот файл находится в скомпилированной двоичной форме, его трудно изменить ...   -  person gregko    schedule 23.10.2013


Ответы (4)


Начиная с версии 13 плагина Android Gradle, вы можете создавать отдельные APK, используя новый механизм «разделения». Вы можете прочитать об этом здесь .

Файловая структура по умолчанию для размещения ваших файлов .so:

src
-main
  -jniLibs
    -armeabi
      -arm.so
    -armeabi-v7a
      -armv7.so
    -x86
      -x86.so
    -mips
      -mips.so

Обратите внимание, что имя файла .so не имеет значения, если он имеет расширение .so.

Затем в вашем файле сборки Gradle:

android {
...
splits {
abi {
  enable true
  reset()
  include 'x86', 'armeabi-v7a', 'mips', 'armeabi'
  universalApk false
  }
 }
}

а также

// map for the version code
ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]

import com.android.build.OutputFile

android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        output.versionCodeOverride =
            project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
    }
}

Обратите внимание, что коды версий, приведенные выше в ext.versionCodes, в значительной степени не имеют значения, это здесь, чтобы добавить уникальное смещение для каждого типа ABI, чтобы коды версий не конфликтовали.

person withoutclass    schedule 30.09.2014
comment
делал это аналогично этому раньше, но после обновления до build-tool 1.0.0 я столкнулся с этой проблемой: не удалось найти свойство abiFilter на com.android.build.gradle.internal.api.ApkVariantOutputImpl_Decorated@61a2abff. - person ligi; 12.01.2015
comment
@ligi Я обновил приведенный выше пример последней версией документации Google. - person withoutclass; 13.01.2015
comment
К какому файлу build.gradle относятся эти изменения? Проект один или главный модуль приложения один? А также куда мы добавляем эту вторую партию кода ?? Под каким блоком? Не могли бы вы показать нам полный пример кода build.gradle - person Asiimwe; 31.01.2015
comment
@PaulAsiimwe, в каком файле gradle у вас есть блок "android" ... Затем второй блок выходит за пределы блока android. - person withoutclass; 04.02.2015
comment
Спасибо за подробности. Я получал сообщение об ошибке «Невозможно выполнить mulitply () для null», потому что output.getFilter (OutputFile.ABI) возвращал «armabi», а в карте versionCodes нет записи для этого. Я добавил ему значение 1 и увеличил все остальные, и теперь он работает. Вы вообще пробовали разделение плотности? Требуются ли дополнительные уловки с кодом версии для успешного разделения apk по abi и плотности? - person speedynomads; 25.02.2015
comment
Если кому-то интересно, ответ на мой вопрос о разделении плотности находится здесь со ссылкой на пример кода: stackoverflow.com/questions/20599840/ - person speedynomads; 25.02.2015
comment
нам нужно изменить код версии для каждого двоичного файла? - person Akshat; 17.06.2015
comment
@Akshat да, если вы видите нижнюю часть моего примера, это создает коды версий для каждого двоичного файла, которые не должны конфликтовать при обновлении ваших приложений. - person withoutclass; 19.06.2015
comment
Благодарю. Если я сделаю для universalAPK значение true, я получаю ошибку в gradle: невозможно вызвать multiply () для нулевого объекта. Есть ли способ указать universalAPK в карте ext.versionCodes? - person Akshat; 20.06.2015
comment
@withoutclass: Я имел в виду, что у меня есть разделение вместе с UniversalAPK (чтобы распространять его конфиденциально для QA). - person Akshat; 24.06.2015
comment
@Akshat Я сегодня возился с этим и обнаружил, что, действительно, установка для universalApk значения true фактически приведет к созданию всех разделенных apk и universalApk, поэтому я был неправ. - person withoutclass; 15.07.2015
comment
@withoutclass, но это сработает, если мы не изменим код версии, используя массив. - person Akshat; 16.07.2015

Разделенное решение ABI APK для gradle - самое простое, что я нашел до сих пор. У @withoutclass есть хорошая запись здесь: https://stackoverflow.com/a/26129447/254573 Мне пришлось сослаться на Документация Android, поскольку это новая функция, которая все еще может изменяться: http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits

Однако мне пришлось отказаться от этой простой реализации, так как мне нужно было поддерживать как толстую сборку, так и сборки, специфичные для архитектуры. Вы можете столкнуться с этой же проблемой, если поддерживаете как магазин Google Play (который поддерживает APK-файлы для конкретной архитектуры), так и Amazon Appstore (который поддерживает только толстые APK).

Это можно сделать с помощью разделенных APK, если вы можете добавить компонент аромата, но на данный момент разделение + аромат еще не поддерживается: https://code.google.com/p/android/issues/detail?id=76469

В итоге я использовал abiFilter, см. Пример кода ниже:

android {
    flavorDimensions "abi"

    productFlavors {
        fat {
            flavorDimension "abi"
            ndk {
                abiFilters "x86", "armeabi-v7a", "armeabi"
                versionCode = 0;
            }
        }
        arm {
            flavorDimension "abi"
            ndk {
                abiFilter "armeabi"
                versionCode = 1;
            }
        }
        armv7a {
            flavorDimension "abi"
            ndk {
                abiFilter "armeabi-v7a"
                versionCode = 3;
            }
        }
        x86 {
            flavorDimension "abi"
            ndk {
                abiFilter "x86"
                versionCode = 6;
            }
        }
    }
}

// Each APK needs a different version code when submitted to Google,
// bump the versionCode we set in defaultConfig
android.applicationVariants.all { variant ->
    // Ugly hard coded flavorDimensions position
    // If you have more than one flavorDimension, make sure to target the position for "abi"
    def abiVersion = variant.productFlavors.get(0).versionCode

    variant.mergedFlavor.versionCode = abiVersion * 1000 + android.defaultConfig.versionCode
}

Обновить. Использование для universalApk значения true решает эту проблему, просто добавляется время для сборки каждого apk.

android {
    // Rest of Gradle file
        splits {
            abi {
            enable true
            reset()
            include 'armeabi', 'armeabi-v7a', 'x86'
            universalApk true
        }
    }
}

//Ensures architecture specific APKs have a higher version code
//(otherwise an x86 build would end up using the arm build, which x86 devices can run)
ext.versionCodes = [armeabi:1, 'armeabi-v7a':3, x86:6]

android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        int abiVersionCode = project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) ?: 0
        output.versionCodeOverride = (abiVersionCode * 1000) + android.defaultConfig.versionCode
    }
}
person Sebastian Gallese    schedule 03.12.2014
comment
Разве вы не можете решить эту проблему, изменив флаг universalApk для своего аромата Amazon? - person withoutclass; 04.02.2015
comment
@withoutclass об этом не думал, но работает! У меня время сборки необычайно велико (требуется около 20 секунд, чтобы создать новую сборку после изменения одной строки). Использование разделения APK с универсальной сборкой добавляет к этому процессу около 30 секунд, а для измерения результатов используется флаг Gradle --profile. Вы видите такое же увеличение времени сборки? - person Sebastian Gallese; 05.02.2015
comment
Лично мне это сейчас не нужно. Меня бы не сильно удивило то, что упаковка всех вариантов в один apk займет дополнительное время. - person withoutclass; 13.02.2015

ОБНОВЛЕНИЕ - со времени этой публикации в процессе сборки gradle был достигнут значительный прогресс, поэтому этот ответ может быть не рекомендуемой передовой практикой, а новые изменения могут даже помешать ему. Используйте свое усмотрение.

Для этого сначала вы должны поместить собственные библиотеки в следующую иерархию папок отдельно

lib
 -armeabi
  -arm.so
  -*.so

-

lib
 -x86
  -x86.so
  -*.so

затем заархивируйте папки lib (без s) (например, arm.zip и x86.zip) и переименуйте расширение zip в jar (например, arm.jar и x86.jar). Поместите эти банки в соответствующие папки (например, armeabi / libs и x86 / libs). Теперь мы собираемся включить зависимости для каждого варианта. Но мы не можем использовать «компилировать файл '....'». Мы должны использовать «файл FlavourCompile '...'»

e.g.

    flavorGroups 'abi'
        productFlavors {
            arm {
                flavorGroup 'abi'
                dependencies {
                    armCompile files('arm/libs/armeabi.jar')
                }
            }
            x86 {
                flavorGroup 'abi'
                dependencies {
                    x86Compile files('x86/libs/x86.jar')
                }
            }

    }

====

Здесь более сложная среда. У вас есть не только варианты архитектуры процессора, но и библиотеки отладки (.jar, .so) для процессоров. В этом примере есть Debug.jar для отладки Arm и NonDebug.jar для выпуска Arm; и * .so для Arm и X86. Такая конфигурация может быть достигнута с помощью gradle ExtraPropertiesExtension Прочтите мой SO-ответ здесь, https://stackoverflow.com/a/19941684/319058, чтобы понять как могут быть структурированы папки отладки.

android {
compileSdkVersion 18
buildToolsVersion "19.0.0"

final DEBUG_ROOT = "build-types/debug"
final RELEASE_ROOT = "build-types/release"
project.ext.set("projRoot", "")
buildTypes {
    debug {
        project.projRoot = DEBUG_ROOT

        dependencies {
            debugCompile files(DEBUG_ROOT+"/libs/Debug.jar")
        }
    }

    release {
        project.projRoot = RELEASE_ROOT
        dependencies {
            releaseCompile files(RELEASE_ROOT+"/libs/NonDebug.jar")
        }
        runProguard true
        proguardFile 'proguard.cfg'
    }
}
sourceSets {

    final PROJ_ROOT = project.ext.get("projRoot")
    final BUILD_TYPE_RES = PROJ_ROOT + "/res"
    main {
        manifest.srcFile 'src/main/AndroidManifest.xml'
        java.srcDirs = ['src/main/java']
        //resources.srcDirs = ['src/main']
        //aidl.srcDirs = ['src/main']
        //renderscript.srcDirs = ['src/main']
        res.srcDirs = ['src/main/res',BUILD_TYPE_RES]
        assets.srcDirs = ['src/main/assets']
    }

    flavorGroups 'abi'
    productFlavors {
        arm {
            flavorGroup 'abi'
            final ARM_LIB_PATH = PROJ_ROOT + "/arm/libs/armeabi.jar"
            dependencies {
                armCompile files(ARM_LIB_PATH)
            }
        }
        x86 {
            flavorGroup 'abi'
            final X86_LIB_PATH = PROJ_ROOT + "/x86/libs/x86.jar"
            dependencies {
                x86Compile files(X86_LIB_PATH)
            }
        }

    }

    // Move the tests to tests/java, tests/res, etc...
    instrumentTest.setRoot('tests')

    // Move the build types to build-types/<type>
    // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
    // This moves them out of them default location under src/<type>/... which would
    // conflict with src/ being used by the main source set.
    // Adding new build types or product flavors should be accompanied
    // by a similar customization.
    debug.setRoot(DEBUG_ROOT)
    release.setRoot(RELEASE_ROOT)
}

}

person Win Myo Htet    schedule 12.11.2013

У меня нет ответа на Gradle, но я думаю, что теперь у меня есть общий ответ для любого инструмента сборки Android. Вот моя идея о том, как создать отдельные файлы APK для каждой поддерживаемой архитектуры процессора:

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

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

  3. В папке, в которую был распакован исходный APK, удалите подпапку 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 меньшего размера с немного разными кодами версий (но с тем же именем версии).

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

https://github.com/gregko/aminc

Я опубликовал все решение Visual Studio 2012, но вся программа представляет собой единый файл .cpp, который, вероятно, можно скомпилировать на любой платформе. А вот образец файла .bat 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), и все работает отлично. См. Также этот проект Github Хорхе Суарес де Лис, который публикует аналогичный сценарий. для Linux.

@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 here for 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

Грег

person gregko    schedule 23.10.2013
comment
Привет, Грег, спасибо за альтернативное решение. Но я не пробовал, так как мой проект не так важен для решения этой проблемы. Я подожду и посмотрю, предоставит ли Google дополнительные документы по этому поводу. - person Blukee; 24.10.2013
comment
Мне понравилось ваше решение, и я адаптировал его для работы в Linux. Кредиты, конечно, даны. github.com/gentakojima/apk-slice-by-arch - person Jorge Suárez de Lis; 25.11.2014
comment
@ JorgeSuárezdeLis - спасибо, Хорхе! Я также вставил ссылку на ваше решение в текст своего сообщения. Да, я все еще использую Windows даже для работы с Android, потому что продукты Windows по-прежнему приносят мне хлеб насущный. Трудно зарабатывать на жизнь приложениями для Android! - person gregko; 26.11.2014
comment
@ JorgeSuárezdeLis - Я также модифицировал репозиторий aminc на Github, добавив недостающий ‹stdlib.h› include. Спасибо еще раз! - person gregko; 26.11.2014