NDK开发

构建NDK项目简易方法

Posted by yuchen on August 16, 2016

新建一个Android空项目

在该项目中新建一个java类NdkUtils, 增加两个native方法,内容如下:

package io.github.wzzju.ndksimple;

public class NdkUtils {

    public static native String getVer();

    public static native int max(int a, int b);

}

编译该类以得到对应的.h文件

切换到Terminal,进入到该工程的java目录下进行编译,即输入如下命令:

cd app/src/main/java
javah -jni -encoding utf-8 -d ../jni io.github.wzzju.ndksimple.NdkUtils

若指定目录jni不存在,则会创建jni目录。

编译成功后,刷新下工程,可以在app/src/main/jni目录下看到编译生成的.h文件(io_github_wzzju_ndksimple_NdkUtils.h)。该.h文件的内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class io_github_wzzju_ndksimple_NdkUtils */

#ifndef _Included_io_github_wzzju_ndksimple_NdkUtils
#define _Included_io_github_wzzju_ndksimple_NdkUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     io_github_wzzju_ndksimple_NdkUtils
 * Method:    getVer
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_io_github_wzzju_ndksimple_NdkUtils_getVer
  (JNIEnv *, jclass);

/*
 * Class:     io_github_wzzju_ndksimple_NdkUtils
 * Method:    max
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_io_github_wzzju_ndksimple_NdkUtils_max
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

注意:该头文件的文件名本身并不重要,可以打将其重命名为更易管理的名称。唯一用于确认共享库的标识是每个方法的长方法名。

编写.c文件

在app/src/main/jni目录下建立一个utils.c的c文件,并在utils.c文件中编写测试代码如下:

#include "io_github_wzzju_ndksimple_NdkUtils.h"

JNIEXPORT jstring JNICALL Java_io_github_wzzju_ndksimple_NdkUtils_getVer
  (JNIEnv *env , jclass cls){
        return (*env)->NewStringUTF(env,"版本号是V1.0.0");
  }
/*
 * Class:     io_github_wzzju_ndksimple_NdkUtils
 * Method:    max
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_io_github_wzzju_ndksimple_NdkUtils_max
  (JNIEnv *env , jclass cls , jint a , jint b){
        return (a >= b)?a : b;
  }

注意:这里的函数名一定要和.h头文件中的函数名严格一致,否则ndk-build不会报错,但是运行后会报出”找不到函数”的错误。此点,从别处复制代码时尤其要注意。

编写Android.mk文件

在jni目录下新建Android.mk(必须是这个名称Android.mk)文件,编辑其内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := utilsLib //要生成的so库的名称,但实际为utilsLib.so,复制时该注释要删除
LOCAL_SRC_FILES := utils.c  //要使用的文件,刚才编写的utils.c文件,复制时该注释要删除

include $(BUILD_SHARED_LIBRARY)

注意:so库的名称不可取系统可能用到的库名,如:此处将库名取为utils即会出错。

编写Application.mk文件

在jni目录下新建Application.mk(必须是这个名称Application.mk)文件,编辑其内容如下:

APP_ABI := all

Application.mk文件只是通知构建系统,它应该为每个至此的ABI架构生成一个output.so文件。若是没有此文件(或者在命令行上传递APP_ABI),默认构建代码将仅为ARMv5输出一个.so文件。

生成so文件

在控制台中,进入到工程的app/src/main目录下,然后输入ndk-build命令。
编译完成后刷新工程,可以看到在app/src/main目录下生成的libs和obj文件夹,其中libs是有用的,obj文件夹无用可以删除。libs中的可以看到生成的libutilsLib.so文件。

设置app下的build.gradle

在app下的build.gradle的android节点下设置内容如下:

android{
	...

	//禁止构建JNI代码,仅在libs中复制
    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs'
    }

	...
}

使用so文件

如何使用调用so库文件? 即,在NdkUtils类中添加加载so库的代码,名称要和Android.mk中定义的LOCAL_MODULE值一致。修改后NdkUtils类的内容如下:

package io.github.wzzju.ndksimple;

public class NdkUtils {

    static {
        System.loadLibrary("utilsLib");
    }
    public static native String getVer();

    public static native int max(int a, int b);

}

至此,可以运行安装该APP。

项目构建成功(即已经生成了.so库)后可以删除的内容

  1. app/src/main目录下的jni文件夹及其内部文件;
  2. app/src/main目录下的obj文件夹及其内部文件。

NDK函数中参数JNIEnv *env的用法说明

参数JNIEnv *env在.cpp文件和.c文件中的用法不同:
.cpp文件中的(*env).NewStringUTF("C String");
等价于.c文件中的(*env)->NewStringUTF(env,"C String");

关于Java中返回对象引用

在Java中,对象分配在堆上,而对象的引用分配在栈上。在一个函数内部,return返回了对象引用的拷贝,(返回后)函数内部原来的引用(局部变量)便被销毁了,但它的拷贝还是指向堆上的原对象。
在Java中,八种基本数据类型,如int这些,变量和值都是直接存在栈中。

参考文献


本文总阅读量