新建一个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库)后可以删除的内容
- app/src/main目录下的jni文件夹及其内部文件;
- 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这些,变量和值都是直接存在栈中。