Android Studio、Gradle 和 NDK

157

我对Gradle和Android Studio的支持非常陌生。通过导出选项,我成功将我的Android项目转换为Gradle。

但我正在寻找一些文档或起点,以了解如何将NDK构建集成到Gradle构建过程中。

如果可能,我还需要某种“后续”阶段,将构建二进制文件(.so文件)复制到资源目录中。


我已在下面提到的链接中发布了我的答案 https://dev59.com/HWEi5IYBdhLWcg3w2PSB - Ahmad Ali Nasir
25
新读者注意:这个问题最初是在 Android Studio beta 期间提出的;随着时间的推移,答案已经发生了变化。请注意回答中提到的 Gradle 版本以及实际发布回答的时间。 - Sean Beach
如果有什么真正的变化,我会编辑问题以反映当前状态。 - plaisthos
Android Studio 1.3在金丝雀频道完全支持NDK。参考:http://tools.android.com/download/studio/canary/latest - Vikasdeep Singh
这个解决方案有效,无需代码 在Android Studio 1.0.2中添加.so库 - tarn
显示剩余3条评论
23个回答

86

2
еҰӮжһңжҲ‘иғҪеңЁAndroid StudioдёӢдҪҝз”ЁNDKе’Ңе‘Ҫд»Өе®ҢжҲҗи°ғиҜ•пјҲ并ж”ҜжҢҒGradleпјүпјҢйӮЈе°ұеӨӘеҘҪдәҶгҖӮ - powder366
7
@DirtyBeach 为什么它已经过时了?现在还没有将NDK集成进工作室。我们正在努力解决,但目前还没有预计完成时间。 - Xavier Ducrohet
2
我的行动是基于我对“集成”的定义。我理解它的意思是“使用NDK与gradle的一种方式”,现在虽然存在,但它们都不是很好的解决方案。然而,根据您的评论,似乎您的团队对真正的集成有其他想法。我撤回之前的声明。 - Sean Beach
2
NDK集成是在2015年Google IO大会上宣布的。它可以在Android Studio 1.3中使用(预览版即将推出,一旦可用我会发布链接)。 - Cypress Frankenfeld
1
Android-Studio 1.3 RC1带有NDK支持现在可以在金丝雀频道中下载 https://sites.google.com/a/android.com/tools/download/studio/canary/latest - fastr.de
显示剩余10条评论

44

更新:现在支持NDK的Android Studio已经发布,访问http://tools.android.com/tech-docs/android-ndk-preview

如果您希望使用脚本进行构建,则下面的gradle解决方案应该可以工作:

我正在使用自己的构建脚本,并将以下内容添加到我的文件中(对于0.8+版本似乎有效):这似乎等同于下面的解决方案(但在gradle文件中看起来更好):

 android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['native-libs']
            jni.srcDirs = [] //disable automatic ndk-build
        }
    }
 }

如果目录不存在或者不包含.so文件,构建过程将不幸地不会失败。


5
新版Android Studio已经不再适用该方法,有解决方法吗? - powder366
2
一点Groovy的魔法:tasks.withType(com.android.build.gradle.tasks.PackageApplication) { it.jniFolders = [file("libs")] as Set }感谢大家的帮助! - trnl
它应该仍然能够在Android 0.8.9上运行。请注意,没有真正的官方好的解决方案(例如从Android Studio开发ndk)。 - plaisthos
@plaisthos:你能给我展示一下项目结构吗?这些.so文件应该放在哪里?谢谢。 - Pandiri Deepak
1
@plaisthos 非常感谢您指出正确的方向! gradle脚本中的第二行 jni.srcDirs = [] //disable automatic ndk-build 非常重要,因为它将防止Android Studio重新构建C / C ++源代码。 我一直在尝试解决这个问题两天,直到看到您的帖子,这解决了我的问题。 我真的认为NDK构建最好由Android.mk命令行makefile单独构建,而不是由gradle脚本构建,因为C / C ++已经通过Makefile构建了40多年! - tonga
显示剩余6条评论

41
随着Android Studio更新至1.0,NDK工具链的支持得到了极大的改善(请注意:请查看本文底部我的更新内容,以了解在新实验版Gradle插件和Android Studio 1.5中的使用方法)。
Android Studio和NDK集成得足够好,因此您只需要在您模块的build.gradle中创建一个ndk{}块,并将您的源文件设置到(module)/src/main/jni目录中 - 完成!
不再需要通过命令行使用ndk-build。
我在我的博客文章中写了所有关于此事的内容:http://www.sureshjoshi.com/mobile/android-ndk-in-android-studio-with-swig/ 要点如下:
引用:
有两件事情你需要知道。默认情况下,如果您有外部库想要加载到Android应用程序中,则默认情况下会在(module)/src/main/jniLibs中查找它们。您可以通过在模块的build.gradle中使用setting sourceSets.main.jniLibs.srcDirs 来更改这一点。您需要为每个目标架构(例如x86、arm、mips、arm64-v8a等)都有一个带有库的子目录。
您想让NDK工具链默认编译的代码将位于(module)/src/main/jni中,与上面类似,您可以通过在模块的build.gradle中设置sourceSets.main.jni.srcDirs来更改它。
并将以下内容放入您模块的build.gradle中:
ndk {
  moduleName "SeePlusPlus" // Name of C++ module (i.e. libSeePlusPlus)
  cFlags "-std=c++11 -fexceptions" // Add provisions to allow C++11 functionality
  stl "gnustl_shared" // Which STL library to use: gnustl or stlport
}
那就是编译C++代码的过程,从那里开始,您需要加载它并创建包装器——但根据您的问题,您已经知道如何做到这一点,所以我不会重复讲述。

此外,我在这里放了一个示例的Github仓库:http://github.com/sureshjoshi/android-ndk-swig-example

更新:2015年6月14日



当Android Studio 1.3发布时,通过JetBrains CLion插件应该有更好的支持C++。我目前的假设是,这将允许在Android Studio中进行Java和C++开发;然而,除非CLion会自动执行这些操作,否则我认为我们仍然需要使用Gradle NDK部分来写Java<->C++包装文件。

更新:2016年1月5日



我已更新我的博客和Github repo(位于develop分支)以使用Android Studio 1.5和最新的实验Gradle插件(0.6.0-alpha3)。Gradle用于NDK部分的构建现在看起来像这样:

http://www.sureshjoshi.com/mobile/android-ndk-in-android-studio-with-swig/
http://github.com/sureshjoshi/android-ndk-swig-example

android.ndk {
    moduleName = "SeePlusPlus" // Name of C++ module (i.e. libSeePlusPlus)
    cppFlags.add("-std=c++11") // Add provisions to allow C++11 functionality
    cppFlags.add("-fexceptions")
    stl = "gnustl_shared" // Which STL library to use: gnustl or stlport
}

另外,令人惊叹的是,Android Studio带有针对C++-Java生成包装器的“native”关键字自动完成功能:

C++-Java wrapper 自动完成示例

然而,并非完全没有问题... 如果您正在使用SWIG封装库以自动生成代码,然后尝试使用'native'关键字自动生成,它会把代码放在Swig _wrap.cxx文件的错误位置...因此需要将其移动到“extern C”块中:

C++-Java wrapper 移动到正确的位置

更新:2017年10月15日

如果不提及Android Studio 2.2开始基本上通过Gradle和CMake支持NDK工具链就会失职。现在,当您创建一个新项目时,只需选择C ++支持即可。

您仍需要生成自己的JNI层代码或使用我上面提到的SWIG技术,但现在Android项目中的C ++脚手架非常简单。

对CMakeLists文件(其中放置C ++源文件)进行更改将被Android Studio捕获,并自动重新编译任何相关库。


1
将*.so文件放入(module)/src/main/jniLibs中。 - fawkes
为什么在使用Android Studio时,即使是调试版本,NDEBUG也总是被设置了呢? - pt123

35

在Google IO 2015上,Google宣布Android Studio 1.3完全集成了NDK。

现在已经不是预览版了,对所有人都可用:https://developer.android.com/studio/projects/add-native-code.html

原回答:如果您的项目源代码中有一个jni目录,Gradle会自动调用ndk-build

这适用于Android Studio 0.5.9(金丝雀版)。

  1. 下载NDK

  2. 要么将ANDROID_NDK_HOME添加到环境变量中,要么在Android Studio项目的local.properties中添加ndk.dir=/path/to/ndk。这样Android Studio就可以自动运行ndk。

  3. 下载最新的gradle示例项目以查看一个NDK项目的示例(它们在页面底部)。一个好的示例项目是ndkJniLib

  4. 复制NDK示例项目中的gradle.build。它看起来像这样。这个gradle.build为每个架构创建了不同的apk。您必须使用build variants窗格选择要使用的架构。 build variants pane

    apply plugin: 'android'
    
    dependencies {
        compile project(':lib')
    }
    
    android {
        compileSdkVersion 19
        buildToolsVersion "19.0.2"
    
        // This actual the app version code. Giving ourselves 100,000 values [0, 99999]
        defaultConfig.versionCode = 123
    
        flavorDimensions "api", "abi"
    
        productFlavors {
            gingerbread {
                flavorDimension "api"
                minSdkVersion 10
                versionCode = 1
            }
            icecreamSandwich {
                flavorDimension "api"
                minSdkVersion 14
                versionCode = 2
            }
            x86 {
                flavorDimension "abi"
                ndk {
                    abiFilter "x86"
                }
                // this is the flavor part of the version code.
                // It must be higher than the arm one for devices supporting
                // both, as x86 is preferred.
                versionCode = 3
            }
            arm {
                flavorDimension "abi"
                ndk {
                    abiFilter "armeabi-v7a"
                }
                versionCode = 2
            }
            mips {
                flavorDimension "abi"
                ndk {
                    abiFilter "mips"
                }
                versionCode = 1
            }
            fat {
                flavorDimension "abi"
                // fat binary, lowest version code to be
                // the last option
                versionCode = 0
            }
        }
    
        // make per-variant version code
        applicationVariants.all { variant ->
            // get the version code of each flavor
            def apiVersion = variant.productFlavors.get(0).versionCode
            def abiVersion = variant.productFlavors.get(1).versionCode
    
            // set the composite code
            variant.mergedFlavor.versionCode = apiVersion * 1000000 + abiVersion * 100000 + defaultConfig.versionCode
        }
    
    }
    

请注意,这将忽略您的Android.mk和Application.mk文件。作为解决办法,您可以告诉Gradle禁用自动调用ndk-build,然后手动指定ndk源目录。

sourceSets.main {
    jniLibs.srcDir 'src/main/libs' // use the jni .so compiled from the manual ndk-build command
    jni.srcDirs = [] //disable automatic ndk-build call
}
此外,由于您刚刚禁用了自动调用,您可能希望在Gradle构建脚本中显式调用ndk-build。

此外,您可能需要在Gradle构建脚本中明确调用ndk-build,因为您刚刚禁用了自动调用。

task ndkBuild(type: Exec) {
   commandLine 'ndk-build', '-C', file('src/main/jni').absolutePath
}

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

是的,它将自动生成makefiles,用于gradle构建文件中可以设置的有限内容,但是有一个解决方法。我已经将其添加到我的答案中。 - Cypress Frankenfeld
1
调用 ndk-build 只能在命令行中使用,而不能在 Android Studio 中使用。 - Cameron Lowell Palmer
@CameronLowellPalmer,你是否将NDK路径添加到local.properties中了?ndk.dir=/path/to/ndk - Cypress Frankenfeld
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Cameron Lowell Palmer
4
为了能够在IDE中编辑C/C++文件,我使用这个技巧来替代禁用src目录的方法:tasks.all { task -> if (task.name.contains('Ndk')) task.enabled = false } - sherpya
显示剩余2条评论

23
我发现“gradle 1.11 com.android.tools.build:gradle:0.9.+”现在支持预构建NDK,你只需将*.so文件放在“src/main/jniLibs”目录下即可。当构建Gradle时,它会将NDK打包到正确的位置。
这是我的项目:
Project: |--src |--|--main |--|--|--java |--|--|--jniLibs |--|--|--|--armeabi |--|--|--|--|--.so文件 |--libs |--|--other.jar

18

我们如何查看在0.7.2 ndkJniLib中提到的示例? - Ryan Heitner
适用于使用诸如SqlCipher等库的情况。 - personne3000

16

到目前为止(Android Studio v0.8.6),这非常简单。以下是创建“Hello world”类型应用程序的步骤:

  1. 下载Android NDK并将根文件夹放在一个合理的位置 - 可以与SDK文件夹放在同一位置。

  2. 将以下内容添加到您的local.properties文件中:ndk.dir =

  3. 将以下内容添加到您的build.gradle文件中的defaultConfig闭包内,在versionName行后面:ndk { moduleName="hello-world" }

  4. 在应用程序模块的main目录中,创建一个名为jni的新文件夹。

  5. 在该文件夹中,创建一个名为hello-world.c的文件,您将在下面看到它。

  6. 参见下面的示例Activity代码,了解如何调用hello-world.c中的方法(或函数?)。


hello-world.c

#include <string.h>
#include <jni.h>

jstring
Java_me_mattlogan_ndktest_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz)
{
    return (*env)->NewStringUTF(env, "Hello world!");
}

MainActivity.java

public class MainActivity extends Activity {

    static {
        System.loadLibrary("hello-world");
    }

    public native String stringFromJNI();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String testString = stringFromJNI();

        TextView mainText = (TextView) findViewById(R.id.main_text);
        mainText.setText(testString);
    }
}

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "me.mattlogan.ndktest"
        minSdkVersion 15
        targetSdkVersion 20
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "hello-world"
        }
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

在这里找到一个非常相似的应用程序的完整源代码(不包括 NDK)。


我正在按照当前项目的指示进行操作,但NDK部分仍未构建。有什么想法吗?它似乎正在构建其他所有内容,但只是跳过了jni部分。 - alice.harrison
@NannuoLei 谢谢,我已经尝试了,但是我遇到一个问题,就是.so文件没有生成。其他所有东西都似乎正常工作,但当我在模拟器中运行apkg时,它抱怨无法加载共享对象。 - aaa90210
@aaa90210 是基于 x86 镜像的模拟器吗?默认情况下,NDK 只会生成 ARMEABI 库,如果您想构建 x86 镜像,可以将以下行添加到 Application.mk 中:APP_ABI := armeabi x86。 - Leo supports Monica Cellio
1
它在我这里运行正常。PS:任何看到这个答案的人,不要忘记将Java_me_mattlogan_ndktest_MainActivity_stringFromJNI更改为你自己的 :) - AbdulMomen عبدالمؤمن

8

如果您使用的是Unix系统,则最新版本(0.8)已经添加了ndk-build。以下是如何添加它:

android.ndk {
    moduleName "libraw"
}

它期望在“src/main/jni”下找到JNI,否则您可以使用以下方式定义它:
sourceSets.main {
    jni.srcDirs = 'path'
}

截至2014年1月28日,版本0.8在Windows上构建存在问题,你需要使用以下命令禁用构建:--disable-build
sourceSets.main {
    jni.srcDirs = [] //disable automatic ndk-build call (currently broken for windows)
}

1
有针对该功能的任何文档吗?我找不到任何资料。目前似乎完全忽略了我的Android.mk/Application.mk文件。 - plaisthos
我还没有找到。它可能已经在构建过程中被悄悄地半成品状态插入。我使用的是Windows系统,所以我只能确认它在尝试调用Unix ndk-build脚本时失败了。除了将本地编译集成到Gradle中之外,没有其他原因需要调用它。你在Unix系统上吗? - Anthony
请参见以下链接:https://dev59.com/GmIj5IYBdhLWcg3wERNx - Alex Cohn
它实际上期望在jniLibs.srcDirs中找到预构建的*.so文件。 - Alpine
基于它在调用ndk-build时崩溃的事实,我不同意这个观点,如果需要构建库,则绝对不是必需的。我无法确认,因为现在没有时间虚拟机Linux。 - Anthony

7

是的,只有在您不经常更改代码时才能很好地工作。您还需要在存储库中包含二进制jar文件。否则,您最终会得到一个创建临时jar的构建脚本。 - plaisthos
1
我已经使用一个简单的bash脚本修改了Android的Hello-JNI示例,并将其包装在ndk-build中,为每个.so生成.jar文件并将它们放置在gradle的构建路径中以减轻这种痛苦。快来看看吧。 - dbro

4

@plaisthos的答案在最新版gradle中出现了问题,但仍有方法可以解决。

在您的项目目录根目录下创建一个native-libs目录,并将所有库文件复制到此目录中。

将以下代码添加到您的build.gradle中。构建并愉快地使用吧。

task copyNativeLibs(type: Copy) {
    from(new File(project(':<your project>').getProjectDir(), 'native-libs')) { include '**/*.so' }
    into new File(buildDir, 'native-libs')
}

tasks.withType(Compile) { compileTask -> compileTask.dependsOn copyNativeLibs }

clean.dependsOn 'cleanCopyNativeLibs'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接