简介
Java程序通过JNI实现与本地C/C++代码的交互,而NDK则是Android平台上便于进行JNI开发的工具。
流程
NDK的开发流程如下:
- 下载NDK,设置环境变量,项目配置NDK
- Java文件定义native接口
- javah生成C/C++头文件
- 编写C/C++代码
- 编写Android.mk、Application.mk
- 编译出so文件
- 使用so文件
下面在Windows环境下逐项介绍,其他环境也基本大同小异。
1、下载NDK,设置环境变量,项目配置NDK
https://developer.android.com/ndk/downloads/index.html下载NDK并解压,拷贝解压地址如D:\android-ndk-r11b
,粘贴到系统环境变量Path中;新建Android Studio工程,名为NdkDemo,打开工程设置,粘贴到NDK Location中。
设置环境变量后,如果命令行执行ndk-build无效,可以试下重启电脑。
2、Java文件定义native接口
新建一个文件src/main/com.example.ndkdemo/Foo.java
,定义native接口
package com.example.ndkdemo;
public class Foo {
public native String getString();
}
Build -> MakeProject编译一下项目生成Foo.class
3、javah生成C/C++头文件
上一步生成的Foo.class文件在app\build\intermediates\classes\debug
中,使用命令行
javah class文件必须使用完整的包名,所以这里我们执行javah的地方并非在Foo.class的目录,而是在debug目录。这时候就会生成一个C/C++的头文件。
4、编写C/C++代码
新建一个文件夹app/src/jni
,必须命名为jni,否则ndk找不到你的文件。拷贝上一步生成的头文件到此目录下,新建Foo.cpp
#include "com_example_ndkdemo_Foo.h"
#include "stdio.h"
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_Foo_getString(JNIEnv *env, jobject thiz){
return env->NewStringUTF("string from native");
}
5、编写Android.mk、Application.mk
继续在jni目录下新建Android.mk文件
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := Foo.cpp
include $(BUILD_SHARED_LIBRARY)
其中LOCAL_MODULE := foo
表示你要编译出的so文件名,它会自动添加lib前缀以及.so后缀。LOCAL_SRC_FILES 则表示编译的文件。
默认情况下只会编译出armeabi平台下的so文件,如果想要编译出其他平台的so,则需要新建一个Application.mk文件
APP_ABI := all
上面表示编译出所有平台的so文件
6、编译出so文件
使用命令行
生成的so文件默认到同等级的libs文件夹里
7、使用so文件
将生成的so文件拷贝到src/main/jniLibs
,AS默认会在这里依赖so。
代码加载so,一般哪里使用native接口,就在哪加载,所以这里是在Foo文件。
package com.example.ndkdemo;
public class Foo {
static {
System.loadLibrary("foo");
}
public native String getString();
}
最后我们就可以使用native接口了
textView.setText(new Foo().getString());
C/C++调用Java
Java调用C/C++基本流程是,加载so,通过native接口,在JNI环境中找到相应的C/C++函数。C/C++调用Java也是相似的过程,在JNI环境中找到Java的类以及方法,然后就可以调用。
继续沿用上面的例子
1、Foo类中定义一个static方法
package com.example.ndkdemo;
public class Foo {
static {
System.loadLibrary("foo");
}
public native String getString();
public static void methodFromJava(String str){
Log.i("tag", "call java from native: " + str);
}
}
2、C/C++代码中通过JNI环境找到这个类和方法,然后即可调用。
//~ Foo.cpp
#include "com_example_ndkdemo_Foo.h"
#include "stdio.h"
void callJavaMethod(JNIEnv *env);
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_Foo_getString(JNIEnv *env, jobject thiz){
callJavaMethod(env); // 调用Java代码
return env->NewStringUTF("string from native");
}
void callJavaMethod(JNIEnv *env){
jclass clazz = env->FindClass("com/example/ndktest/Foo"); // 找到Java类
jmethodID id = env->GetStaticMethodID(clazz,"methodFromJava","(Ljava/lang/String;)V"); // 找到该类的这个方法
jstring str = env->NewStringUTF("hello java");
env->CallStaticVoidMethod(clazz,id,str); // 调用这个方法
}
两点需要注意:
1. FindClass的参数写法;
2. 找方法过程有点类似Java中的反射,其中最后一个参数代表方法的签名,它的写法是(参数类型签名)返回类型签名
,不同数据类型的签名有不同表示方法。比如上面的(Ljava/lang/String;)V
,就代表void methodName(String str)
方法的签名。
小结
如果不使用NDK,我们需要自己使用编译器将源码编译出so/dll。使用NDK,编写好Android.mk和Application.mk就能很方便地编译出不同平台的so了。通过JNI,Java程序能与本地C/C++代码交互,两者可以互调。