文档章节

Android Studio, gradle and NDK integration

Jerikc
 Jerikc
发布于 2015/05/28 13:40
字数 1467
阅读 1595
收藏 4

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 :)

jniLibs

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:

fatbinary

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:

sync

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

buildVariants

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

thinbinaryapp-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_sharedstlport_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.

本文转载自:http://ph0b.com/android-studio-gradle-and-ndk-integration/

Jerikc
粉丝 97
博文 246
码字总数 22757
作品 0
浦东
程序员
私信 提问
用Android Studio进行NDK编程入门实例

版权声明:欢迎关注我的微信公众号:「easyserverdev」,中文名:『高性能服务器开发』。 https://blog.csdn.net/analogous_love/article/details/79979073 参考了网上各种教程,跌跌撞撞最终...

analogous_love
2018/04/17
0
0
Android:随笔——新的 NDK/JNI 调用方式

转载请标明地址 QuincySx: http://www.jianshu.com/p/c6108131ba0f 众所周知现在 Android 开发已经到了 Android Studio 的时代,那么 JNI 的调用方式也经过了好几代 在eclipse 时代就存在的...

quincy
2017/04/05
0
0
Android JNI学习(二)——实战JNI之“hello world”

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头
2018/05/09
0
0
使用Android Studio编译cocos2dx的测试用例

最近笔者在学习如何为游戏引擎添加Android平台的支持。首先从已有的游戏引擎开始研究,将引擎中的示例用Android Studio打包成apk文件。笔者选用了目前风头正紧的cocos2dx,版本是最新的3.16,...

闪电的蓝熊猫
2018/04/06
0
0
Android Studio 0.2.7 发布

Android Studio 是一个全新的 Android 开发环境,基于 IntelliJ IDEA. 类似 Eclipse ADT,Android Studio 提供了集成的 Android 开发工具用于开发和调试,在 IDEA 的基础上,Android Studio ...

打杂程序猿
2013/09/07
2.9K
12

没有更多内容

加载失败,请刷新页面

加载更多

新手转行学java难吗?新手学java需要注意的6个方面!

新手转行在成都学java到底难不难,对于这个问题,我们专门做过一个调查,超过1000名已经在职的java从业者,其中有80%的程序员觉得学java不难,20%的程序员觉得前期有点难,其中对于50%自学的...

Java领航员
48分钟前
3
0
动态规划-硬币问题分析

什么是动态规划 上次对动态规划已经有了个大概的分析。引用维基百科的话就是: dynamic programming is a method for solving a complex problem by breaking it down into a collection of...

AI考拉
56分钟前
1
0
谈谈lucene的DocValues特性之SortedSetDocValuesField

SortedSetDocValuesField与SortedDocValuesField类似但它是一键多值的(注意:lucene的数据模型是支持一键多值的即key-values模型),lucene在实现时会判断是一键一值还是多值,如果单值就调...

FAT_mt
今天
1
0
生产者消费者模式

//尚学堂视频里,不是完整的 public class Movie { /** * 共同的资源 */ private String pic; //flay为true生产,false消费 private boolean flag=true; public synchronized void play(Str......

南桥北木
今天
1
0
使用阿里云镜像安装kubernetes

参考阿里云镜像 https://opsx.alibaba.com/mirror?lang=zh-CN 系统: CentOS / RHEL / Fedora cat <<EOF > /etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https......

北漂的我
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部