Gradle android构建用于不同的处理器架构

时间:2013-10-09 09:51:57

标签: android opencv gradle multiprocessor android-gradle

我想使用Gradle为4种不同的Android CPU处理器架构(armeabi armeabi-v7a x86 mips)构建4个单独的apks。

我在 libs 文件夹中为4个CPU架构构建了本机OpenCV库。

libs
    -armeabi
    -armeabi-v7a
    -x86
    -mips

我想每个apk只包含对应正确CPU架构的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"
            }
        }

    }
}

有人可以帮我解决这个问题吗?

干杯,

4 个答案:

答案 0 :(得分:24)

从Android Gradle Plugin版本13开始,您现在可以使用新的“拆分”机制生成单独的APK。你可以阅读它here

放置.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类型添加一个唯一的偏移量,因此版本代码不会发生冲突。

答案 1 :(得分:13)

用于gradle的拆分ABI APK解决方案是迄今为止我发现的最简单的解决方案。 @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执行此操作,但截至目前尚不支持split + flavor: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
    }
}

答案 2 :(得分:3)

我没有gradle的答案,但我想我现在对任何Android构建工具都有一个通用的答案。以下是关于如何为每个支持的处理器体系结构创建单独的APK文件的想法:

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

  2. 将原始APK解压缩到一个空文件夹中,使用任何zip / unzip实用程序,最好使用命令行工具,以便以后可以使用shell脚本或批处理文件自动执行它。

  3. 在原始APK未压缩的文件夹中,删除META-INF子文件夹(这包含签名,我们需要在所有修改后重新签名APK,因此原始的META-INF必须删除)。

  4. 更改为lib子文件夹,并删除新APK文件中不需要的任何处理器体系结构的子文件夹。例如,只留下'x86'子文件夹为Intel Atom处理器制作APK。

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

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

    < / LI>
  7. 对您需要支持的所有其他体系结构重复此过程,创建版本较小的APK文件(但版本名称略有不同)。

  8. 唯一突出的问题是在二进制清单文件中修改'versionCode'的方法。很长一段时间我找不到解决方案,所以最后不得不坐下来动摇我自己的代码来做到这一点。作为起点,我采用了用Java编写的Prasanta Paul http://code.google.com/p/apk-extractor/的APKExtractor。我是旧学校,对C ++更加满意,所以用C ++编写的小实用程序'aminc'现在在GitHub上:

    https://github.com/gregko/aminc

    我发布了整个Visual Studio 2012解决方案,但整个程序是一个.cpp文件,可能可以在任何平台上编译。这里有一个示例Windows .bat文件,用于将名为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上的我的应用)并且一切正常。另请参阅this Github project by Jorge Suárez de Lis,他为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
    

    格雷格

答案 3 :(得分:3)

更新 - 自发布之日起,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)。现在我们将包含每种风格的依赖项。但是我们不能使用“编译文件'......'”。我们必须使用“flavorCompile file'......'”

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)。这里的例子有Arm调试的Debug.jar和Arm释放的NonDebug.jar;和* .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)
}

}