如何使用Android Gradle插件0.7配置NDK

时间:2013-12-19 06:18:13

标签: android-ndk android-studio android-gradle

新的Android gradle插件(0.7)似乎包含对NDK的新支持,但在文档中几乎没有提及它(我发现的唯一参考是名为ndkSanAngeles的测试。)< / p>

看起来gradle正在寻找我已经包含在我的PATH中的NDK。但是,使用

构建项目失败
  
      
  • 出了什么问题:   任务执行失败':OGLTests:compileDefaultFlavorDebugNdk'。   NDK未配置
  •   

如何在gradle中配置NDK?

我当前的build.gradle看起来像这样:

task nativeLibsToJar(type: Zip, description: 'create a jar with native libs') {
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    extension 'jar'
    from fileTree(dir: 'src/main/libs', include: '**/*.so')
    from fileTree(dir: 'src/main/libs', include: '**/gdb*')
    into 'lib/'
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn nativeLibsToJar
}

dependencies {
    compile fileTree(dir: "$buildDir/native-libs", include: '*.jar')
}

android {
    compileSdkVersion 19
    buildToolsVersion '19.0.0'

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 19
        versionCode 1
        versionName "0.1"

    }
    buildTypes {
        release {
            runProguard false
        }
        debug {
           // jniDebugBuild true
            runProguard false
            debuggable true
        }
    }
    productFlavors {
        defaultFlavor {
            proguardFile 'proguard-rules.txt'
        }
    }
}

感谢。

6 个答案:

答案 0 :(得分:76)

通过gradle插件代码,我发现以下内容帮助我使用了NDK和预构建的本机库:

要简单地在预建本地库中链接,只需在任务中添加一个ndk部分即可。例如,我在productFlavors中添加了它。 abiFilter是libs存储的文件夹名称.abiFilters意味着来自逗号分隔列表的libs将被添加到你的最终APK中(理论上你可以有#34; armeabi&#34;,&#34; armeabi-v7a& #34;,&#34; x86&#34;和&#34; mips&#34;所有在一个APK中,O / S将在安装时选择支持的体系结构lib:

productFlavors {
    arm {
        ndk {
            abiFilters "armeabi", "armeabi-v7a"
        }
    }
    x86 {
        ndk {
            abiFilter "x86"
        }
    }
}

在这个例子中,arm构建将创建一个带有V5和V7A arm库的APK,而x86构建将创建一个仅包含x86库的APK。这将搜索项目jniLibs目录中的本机库。 jniLibs目录应该是旧jni目录的结构,即:

[project]/[app]/src/main/jniLibs/armeabi/libmyNative.so
[project]/[app]/src/main/jniLibs/armeabi-v7a/libmyNative.so
[project]/[app]/src/main/jniLibs/x86/libmyNative.so

然后你可以用Java加载它,如下所示:

static
{
    loadLibrary("myNative");
}

现在,让我们说一个本地库依赖于另一个。您必须(如果将最小API设置为API 17或更低版​​本)首先加载依赖库:

static
{
    loadLibrary("myDependency");
    loadLibrary("myNative");
}

您还可以将ndk {}部分放在defaultConfig或buildType中(例如调试或发布或您可能使用的任何其他内容)。例如:

buildTypes {
    debug {
        ndk {
            abiFilters "armeabi", "armeabi-v7a"
        }
    }
}

通过预先构建,我指的是您下载的第三方库或使用NDK工具链或您自己的ARM工具链(而不是ndk-build脚本本身)构建的库。

在API 18中,他们修复了一个长期存在的架构问题,该问题阻止了本机lib加载程序自动生成#34;加载依赖项,因为它不知道应用程序的lib目录(安全性原因等)。在API 18及更高版本中,如果myNative依赖于上面的myDependency,您只需调用loadLibrary(&#34; myNative&#34;),操作系统将处理加载myDependency。不过,请不要这样做,直到运行API 17及更低版本的设备的市场渗透率达到您可接受的低数量。


要在当前版本的Android Studio中明确从源构建NDK库,您可以执行以下操作:

如前所述,将local.properties中的ndk.dir值设置为指向NDK home。有谁知道你是否可以直接在local.properties中使用env vars? :)

在你的build.gradle文件中,为你的任务添加这样的东西(同样,可以是defaultConfig,debug,release,productFlavor等):

ndk {
    moduleName "myNDKModule"
    stl "stlport_shared"
    ldLibs "log", "z", "m"
    cFlags "-I/some/include/path"
}

这是当前支持的类型(moduleName,stl,ldLibs和cFlags)的基本结构。我看起来并没有找到更多。我相信ldLibs存在一个问题,因为它会自动添加&#34; -l&#34;到了上面每个场地的前面。你可以欺骗它(我不得不)说:     ldLibs&#34; log -lz -lm -Wl,-whole-archive -l / path / to / someOtherLib -Wl,-no-whole-archive&#34;

在这一行中,您只是标记到第一个参数的末尾,以添加不以-l开头的参数,这样您就可以暂时使用了。在上面的例子中,我将整个静态库链接到我的NDK模块,以便在Java中使用。我已经要求谷歌开发人员添加其他功能,以允许甚至将您自己的Android.mk文件合并到NDK构建过程中,但由于这是全新的,可能需要一段时间。

目前,无论你在build.gradle中放置什么都删除了临时构建目录并且每次都重新创建它,所以除非你想下载和修改gradle android插件源代码(这会很有趣),还有一些&#34因为这需要将你的东西复制到构建中。提供此ndk支持的android gradle脚本实质上会生成一个Android.mk文件,并使用NDK系统在临时目录中构建。

Sidetracked一秒钟。 moduleName应该与jni目录下的项目中的c或cpp文件匹配,如:

[project]/[app]/src/main/jni/myNDKModule.cpp

stl值应设置为&#34; stlport_shared&#34;或&#34; stlport_static&#34;如果你想使用stlport库的C ++。如果您不需要扩展的C ++支持,可以退出stl。记住Android默认情况下提供非常基本的C ++支持。对于其他受支持的C ++库,请在您下载的NDK中查看NDK文档指南。请注意,通过在此处将其设置为stlport_shared,gradle将libstlport_shared.so lib从NDK的sources / cxx-stl / stlport / libs目录复制到APK的lib目录。它还处理编译器中的包含路径(从技术上讲,gradle并不是完成所有这些,而是​​Android NDK构建系统)。因此,不要将自己的stlport副本放入jniLibs目录。

最后,我认为cFlags非常明显。

你不能在Mac OSX上设置ANDROID_NDK_HOME(见下文),但是从我做过的一些研究中看来,这可能仍适用于其他操作系统。它将被删除。

我想发表评论但尚未获得声誉。丹尼斯,环境变量被完全忽略,而不仅仅是被覆盖。实际上,您没有获得任何环境变量。据我所知,Android Studio IDE只用几个特定的​​环境变量创建自己的环境(检查System.getenv()并从gradle脚本中打印出来)。

我在这里写了一个错误,因为使用env vars从cmd行建立得很好:
https://code.google.com/p/android/issues/detail?id=65213

但正如您所看到的,Google决定他们根本不想让IDE使用环境变量;我仍然坚持这个决定。让我的生活变得痛苦,我必须更新local.properties以指向可以在我的gradle脚本中加载的绝对路径,我仍然没有想出怎么做(但是看起来很难看)。这意味着我要么强迫我的团队成员使用与我相同的路径,玩链接,让他们在每次拉回购物时都输入它们,或添加自动化脚本。我认为这是一个糟糕的决定,任何依赖于env vars的开发人员都会花费时间,这些开发人员在微观层面可能很小,但在宏观层面却很大。

groundloop,我相信IDE很快就会更新,并能够将NDK文件夹路径添加到项目中,并且它会自动生成local.properties文件(至少它不会有意义)他们没有想到这一点。)

有关Google的更多详细示例,以下是最新示例(搜索jni或ndk): https://docs.google.com/viewer?a=v&pid=sites&srcid=YW5kcm9pZC5jb218dG9vbHN8Z3g6NDYzNTVjMjNmM2YwMjhhNA


使用NDK的跨平台胖APK:

最后,使用gradle并且无法提供自己的Android.mk文件存在缺陷,因此您只能将第三方本机库从单个体系结构链接到NDK。注意我说&#34;链接在&#34;。您可以使用&#34; abiFilters&#34;在多个体系结构中构建NDK模块(上面的moduleName)。命令,它们将被放置在您的应用程序中,以便可以在多个体系结构上使用相同的APK。如果你需要链接你自己的第三方库,或者根据你的架构有不同的cFlags值,那么这不是一个简单的方法。

我尝试了下面的内容,它起初似乎起作用了,但后来我发现它只是通过将两个ndk部分中的所有内容相加来构建NDK(或类似的东西,它以某种方式构建多个架构库虽然):

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.1'
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 28
        versionName "3.0"
    }
    buildTypes {
        def commonLibs = " -lfoo -lbar -lwhatever"
        def armV7LibsDir = "/whatever/armv7a/libs"
        def armX86LibsDir = "/whatever/x86/libs"
        def armV7IncDir = "/whatever/armv7a/include"
        def x86IncDir = "/whatever/x86/include"
        debug {
            ndk {
                cFlags = "-I" + armV7IncDir
                moduleName "myNativeCPPModule"
                stl "stlport_shared"
                abiFilter "armeabi-v7a"
                ldLibs "log -L" + armV7LibsDir + commonLibs
            }
            ndk {
                cFlags = "-I" + armX86IncDir
                moduleName "myNativeCPPModule"
                stl "stlport_shared"
                abiFilter "x86"
                ldLibs "log -L" + armX86LibsDir + commonLibs
            }
        }
    }
}

在试图用gradle和本地第三方库创建一个干净的庄园中的胖二进制之后,我终于得出结论,谷歌Play内置的多架构支持APK真的是最好的无论如何要去的路线,所以为每个架构创建单独的APK。

所以我创建了多个buildTypes,没有产品风格,并添加了以下代码来生成每种类型的版本代码。

// This is somewhat nasty, but we need to put a "2" in front of all ARMEABI-V7A builds, a "3" in front of 64-bit ARM, etc.
// Google Play chooses the best APK based on version code, so if a device supports both X86 and
// ARM, it will choose the X86 APK (preferred because Inky ARM running on an X86 with Houdini ARM Emulator crashes in our case)
android.applicationVariants.all { variant ->
    if (variant.buildType.name.equals('release')) {
        variant.mergedFlavor.versionCode = 2000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debug')) {
        variant.mergedFlavor.versionCode = 2000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugArmV8a')) {
        variant.mergedFlavor.versionCode = 3000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseArmV8a')) {
        variant.mergedFlavor.versionCode = 3000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugMips')) {
        variant.mergedFlavor.versionCode = 5000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseMips')) {
        variant.mergedFlavor.versionCode = 5000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugMips64')) {
        variant.mergedFlavor.versionCode = 6000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseMips64')) {
        variant.mergedFlavor.versionCode = 6000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugX86')) {
        variant.mergedFlavor.versionCode = 8000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseX86')) {
        variant.mergedFlavor.versionCode = 8000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('debugX86_64')) {
        variant.mergedFlavor.versionCode = 9000 + defaultConfig.versionCode
    } else if (variant.buildType.name.equals('releaseX86_64')) {
        variant.mergedFlavor.versionCode = 9000 + defaultConfig.versionCode
    }
}

现在,您所要做的就是在defaultConfig对象中设置versionCode的值,就像您通常那样,并根据构建类型将其附加到特定于体系结构的版本字符串的末尾。版本字符串对于所有版本保持不变,但会改变代码以提供从ARM一直到X86_64的优先顺序。它有点hackish或硬编码,但它完成了工作。请注意,这为您提供了多达999个版本,因此如果您需要更多版本,请将上面的数字乘以10,不确定您可以为版本代码添加的最大值。

就我而言,我们有一个相当复杂的构建系统。我们为9个架构构建了CPython,其中3个是Android,然后构建了一堆我们自己的库,并将它们全部链接到每个架构的单个库中。我们使用ndk命令行构建工具,automake和python来构建所有内容,而不是Android.mk文件。然后将最终的库链接到单个JNI接口cpp文件(上面称为myNativeCPPModule)。只需单击一下按钮,一切都可以立即构建,非常漂亮的Android Studio。

答案 1 :(得分:34)

找到答案。在ndk.dir=path/to/ndk文件中加入local.properties就可以了。

<强>更新 在最新版本的Android Studio上,您可以直接在项目结构&gt;中设置值。 SDK位置。

答案 2 :(得分:15)

您还可以设置ANDROID_NDK_HOME环境变量

答案 3 :(得分:1)

我花了很多时间在build.gradle中配置ndk。我得到good blog解决了我的问题。

答案 4 :(得分:0)

如前所述,在local.properties中添加ndk.dir =会有所帮助。有趣的是,我发现local.properties会覆盖为环境变量ANDROID_NDK_HOME,设置的任何值,即使您没有在local.properties中配置ndk.dir 。 (至少使用gradle android插件v 0.7.3)。

这是令人困惑的,因为Android Studio可以覆盖local.properties,它似乎没有提供配置ndk.dir的方法:(

答案 5 :(得分:0)

Android工作室建议在local.properties中包含ndk的路径