Android Studio, gradle and NDK integration

With the recent changes (release 0.7.3 around Dec 27), the new Android Build System starts to be really interesting also if you are using the NDK!

Now this is really easy to integrate native libraries in your package and generate APKs for different architectures while correctly handling version codes (for more information on why this may be important, please refer to my first article).

update 2014/12/09: this article remains fully up-to-date if you’re using the recent android studio and gradle plugin’s 1.0+ releases.

update 2014/11/1: changed output.abiFilter to output.getFilter(com.android.build.OutputFile.ABI) in order to work with gradle 0.14

update 2014/09/19: I’ve added information and modified my samplebuild.gradle to demonstrate the brand new APK Splits feature introduced by the latest android gradle plugin (0.13)

update 2: I’ve modified the sample build.gradle file to make it call ndk-buildscripts by itself.

update 1: Here is a screencast on how to set up a project with NDK sources from Android Studio:

Integrating .so files into your APK

If you are using Android Studio and need to integrate native libraries in your app, you may have had to use some complex methods before, involving maven and .aar/.jar packages… the good news is you don’t need these anymore 

You only need to put your .so libraries inside the jniLibs folder under sub-directories named against each supported ABI (x86, mips, armeabi-v7a, armeabi), and that’s it !

Once it’s done, all the .so files will be integrated into your apk when you build it:

If the jniLibs folder name doesn’t suit you (you may generate your .so files somewhere else), you can set a specific location in build.gradle:

build.gradle

android {
    ...
    sourceSets.main {
        jniLibs.srcDir ‘src/main/libs‘
    }
}

Building one APK per architecture, and doing it well !

You can use flavors to build one APK per architecture really easily, by usingabiFilter property.

ndk.abiFilter(s) is by default set to all. This property has an impact on the integration of .so files as well as the calls to ndk-build (I’ll talk about it at the end of this article).

Let’s add some architecture flavors in build.gradle:

build.gradle

android{
  ...
  productFlavors {
        x86 {
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            ndk {
                abiFilter "armeabi"
            }
        }
        fat
    }
}

And then, sync your project with gradle files:

You should now be able to enjoy these new flavors by selecting the build variants you want:

Each of these variants will give you an APK for the designated architecture:

app-x86-release-unsigned.apk

The fat(Release|Debug) one will still contain all the libs, like the standard package from the beginning of this blog post.

But don’t stop reading here! These arch-dependent APKs are useful when developing, but if you want to upload several of these to the Google Play Store, you have to set a different versionCode for each. And thanks to the latest android build system, this is really easy:

Automatically setting different version codes for ABI dependent APKs

The property android.defaultConfig.versionCode holds the versionCode for your app. By default it’s set to -1 and if you don’t change it, the versionCodeset in your AndroidManifest.xml file will be used instead.

Hence if you want to be able to dynamically modify your versionCode, you need to first specify it inside your build.gradle:

build.gradle

android {
    ...
    defaultConfig{
        versionName "1.1.0"
        versionCode 110
    }
}

But this is still possible to keep setting this variable only inside yourAndroidManifest.xml if you retrieve it “manually” before modifying it:

build.gradle

import java.util.regex.Pattern

android {
    ...
    defaultConfig{
        versionCode getVersionCodeFromManifest()
    }
    ...
}

def getVersionCodeFromManifest() {
    def manifestFile = file(android.sourceSets.main.manifest.srcFile)
    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def matcher = pattern.matcher(manifestFile.getText())
    matcher.find()
    return Integer.parseInt(matcher.group(1))
}

Once it’s done, you can prefix the versionCode inside your different flavors:

build.gradle

android {
    ...
    productFlavors {
        x86 {
            versionCode Integer.parseInt("6" + defaultConfig.versionCode)
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            versionCode Integer.parseInt("4" + defaultConfig.versionCode)
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            versionCode Integer.parseInt("2" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            versionCode Integer.parseInt("1" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi"
            }
        }
        fat
    }
}

Here I’ve prefixed it with 6 for x86, 4 for mips, 2 for ARMv7 and 1 for ARMv5. If you’re asking yourself why!? please refer to this paragraph I wrote before on architecture dependent APKs on the Play Store.

Improving multiple APKs creation and versionCode handling with APK Splits

Since version 0.13 of the android plugin, instead of having a product flavors to get multiple APKs, you can use splits to have a single build (and variant) that will produce multiple APKs (and it’s much cleaner and faster).

splits {
        abi {
            enable true // enable ABI split feature to create one APK per ABI
            universalApk true //generate an additional APK that targets all the ABIs
        }
    }
    // map for the version code
    project.ext.versionCodes = [‘armeabi‘:1, ‘armeabi-v7a‘:2, ‘arm64-v8a‘:3, ‘mips‘:5, ‘mips64‘:6, ‘x86‘:8, ‘x86_64‘:9]

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

Compiling your C/C++ source code from Android Studio

If you have a jni/ folder in your project sources, the build system will try to call ndk-build automatically.

As of 0.7.3, this integration is only working on Unix-compatible systems, cfbug 63896. On Windows you’ll want to disable it so you can call ndk-build.cmd yourself. You can do so by setting this in build.gradle: this has been fixed 

The current implementation is ignoring your Android.mk makefiles and create a new one on the fly. While it’s really convenient for simple projects (you don’t need *.mk files anymore !), it may be more annoying for projects where you need all the features offered by Makefiles. You can then disable this properly in build.gradle:

build.gradle

android{
    ...
    sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
}

If you want to use the on-the-fly generated Makefile, you can configure it first by setting the ndk.moduleName property, like so:

build.gradle

android {
...
defaultConfig {
        ndk {
            moduleName "hello-jni"
        }
    }
}

And you’re still able to set these other ndk properties:

  • cFlags
  • ldLibs
  • stl (ie: gnustl_shared, stlport_static…)
  • abiFilters (ie: “x86″, “armeabi-v7a”)

You can also set android.buildTypes.debug.jniDebuggable to true so it will pass NDK_DEBUG=1 to ndk-build when generating a debug APK.

If you are using RenderScript from the NDK, you’ll need also to set the specific property defaultConfig.renderscriptNdkMode to true.

If you rely on auto-generate Makefiles, you can’t easily set different cFlagsdepending on the target architecture when you’re building multi-arch APKs. So if you want to entirely rely on gradle I recommend you to generate different libs per architecture by using flavors like I’ve described earlier in this post:

build.gradle

  ...
  productFlavors {
    x86 {
        versionCode Integer.parseInt("6" + defaultConfig.versionCode)
        ndk {
            cFlags cFlags + " -mtune=atom -mssse3 -mfpmath=sse"
            abiFilter "x86"
        }
    }
    ...

My sample .gradle file

Putting this altogether, Here is one build.gradle file I’m curently using. It’s using APK Splits to generate multiple APKs, it doesn’t use ndk-build integration to still rely on Android.mk and Application.mk files, and doesn’t require changing the usual location of sources and libs (sources in jni/, libs inlibs/). It’s also automatically calling ndk-build script from the right directory:

build.gradle

import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: ‘com.android.application‘

android {
    compileSdkVersion 21
    buildToolsVersion "21.1"

    defaultConfig{
        minSdkVersion 16
        targetSdkVersion 21
        versionCode 101
        versionName "1.0.1"
    }

    sourceSets.main {
        jniLibs.srcDir ‘src/main/libs‘
        jni.srcDirs = [] //disable automatic ndk-build call
    }

   project.ext.versionCodes = [‘armeabi‘:1, ‘armeabi-v7a‘:2, ‘arm64-v8a‘:3, ‘mips‘:5, ‘mips64‘:6, ‘x86‘:8, ‘x86_64‘:9] //versionCode digit for each supported ABI, with 64bit>32bit and x86>armeabi-*

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

    // call regular ndk-build(.cmd) script from app directory
    task ndkBuild(type: Exec) {
        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
            commandLine ‘ndk-build.cmd‘, ‘-C‘, file(‘src/main‘).absolutePath
        } else {
            commandLine ‘ndk-build‘, ‘-C‘, file(‘src/main‘).absolutePath
        }
    }

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

Troubleshooting

NDK not configured

If you get this kind of error:

Execution failed for task ‘:app:compileX86ReleaseNdk‘.
> NDK not configured

This means the tools haven’t found the NDK directory. You have two ways to fix it: set the ANDROID_NDK_HOME variable environment to your NDK directory and delete local.properties, or set it manually insidelocal.properties:

ndk.dir=C\:\\Android\\ndk

No rule to make target

If you get this kind of error:

make.exe: *** No rule to make target ...\src\main\jni

This may come from a current NDK bug on Windows, when there is only one source file to compile. You only need to add one empty source to make it work again.

Other issues

You may also find some help on the official adt-dev google group:https://groups.google.com/forum/#!forum/adt-dev

Getting more information on NDK integration

The best place to get more information is the official project page:  http://tools.android.com/tech-docs/new-build-system.

You can look at the changelog and if you scroll all the way down you’ll also get access to sample projects dealing with NDK integration, inside the latest “gradle-samples-XXX.zip” archive.

时间: 2024-11-10 14:53:24

Android Studio, gradle and NDK integration的相关文章

Android Studio 2.1 NDK断点调试方法

Android Studio 2.1 NDK断点调试方法(基于Android 2.1, gradle 2.1.0) 1.创建一个Android Studio Project 2.设置NDK路径. 菜单中选择: File->Project Structure 在弹出的窗口的左侧选项框中,选择SDK Location,在右侧设置Android NDK location. 2.修改.\build.gradle如下: replase classpath 'com.android.tools.build:

Android Studio Gradle:Resolvedependencies':app:_debugCompile' 问题解决纪录

问题描述: 第一次使用AndroidStudio打开已经存在的AndroidStudio项目,卡在Gradle:Resolvedependencies':app_debugCompile'步骤,即使进入了AndroidStudio界面也无法正常下载Gradle依赖,无法编译运行. 1.首先确认gradle依赖都声明在app下的build.gradle中,而不是在全局项目下的build.gradle文件中,这是使用gradle管理AndroidStudio项目的常识. 2.确认AndroidStu

Android Studio Gradle太慢 解决方案

为了解决Android Studio Gradle太慢这个问题,我们不得不设置下代理来加速Gradle. 用ss,程序员这种动物大多都知道的吧,不解释了.开启ss本地客户端后,依次打开下列菜单   FIle -> Settings -> Appearence&Behavior -> System setting -> HTTP Proxy 如图,填写下信息,保存 保存以后,速度就变快很多了. 这里分享几个免费ss账号的邀请码,如果没有ss账号或客户端的,自行打开 https

转载_加速Android Studio/Gradle构建

转自:加速Android Studio/Gradle构建 随着项目的增大,依赖库的增多,构建速度越来越慢,现在最慢要6分钟才能build一个release的安装包,在网上查找资料,发现可以通过一些配置可以加快速度,这里跟大家分享一下. 开启gradle单独的守护进程 在下面的目录下面创建gradle.properties文件: /home/<username>/.gradle/ (Linux) /Users/<username>/.gradle/ (Mac) C:\Users\&l

[转]加速Android Studio/Gradle构建

加速Android Studio/Gradle构建 android android studio gradle 已经使用Android Studio进行开发超过一年,随着项目的增大,依赖库的增多,构建速度越来越慢,现在最慢要6分钟才能build一个release的安装包,在网上查找资料,发现可以通过一些配置可以加快速度,这里跟大家分享一下. 开启gradle单独的守护进程 在下面的目录下面创建gradle.properties文件: /home/<username>/.gradle/ (Lin

Android Studio上面使用Ndk JNI 开发工程

     Ps:最近比较闲,so.多更新几篇博客算是总结一下.顺便鄙视一下有的programmer照搬网上面文章,并没有自己去进行相关的实践验证.导致网上面的博客千篇一律,只要最初写博客的人踩坑后面的人全都踩坑.对这类不经过实际检验就大抄特抄的人深深的鄙视一下 ok  接下来我们进入今天的正题,也就是在Android studio里面如何正确的使用和配置ndk.如果有对jni不了解的童鞋建议先去学习下JNI技术再来看本篇博客. 转载请标明出处:http://blog.csdn.net/unrel

加速Android Studio/Gradle构建

已经使用Android Studio进行开发超过一年,随着项目的增大,依赖库的增多,构建速度越来越慢,现在最慢要6分钟才能build一个release的安装包,在网上查找资料,发现可以通过一些配置可以加快速度,这里跟大家分享一下. 开启gradle单独的守护进程 在下面的目录下面创建gradle.properties文件: /home/<username>/.gradle/ (Linux) /Users/<username>/.gradle/ (Mac) C:\Users\<

Android Studio Gradle 增加对.so 文件的支持

最近在开发Android Wear 手表项目,官方给的Demo全都是gradle 项目.当然我也用eclipse配置了一个可行的环境. 问题来了,eclipse,android studio 开发 android wear 哪家技术更强? 目前的开发体验是studio更强. 开发中碰到一个问题android studio在用gradle build wear app 的时候,不把.so文件打入apk包中,因为gradle不认识.so. Google了很多,有很多hack的方式,但是最简单的一种:

android studio(2.0)NDK开发配置

android studio 版本:2.0gradle 版本:2.8 详情看gradle配置: eclipse项目导出============================================================================================= buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:g