NDK是Android所提供的工具集合,通过NDK可以方便地通过JNI来访问本地代码,还提供交叉编译器,开发人员只需要简单的修改mk文件就可以生成特定CPU平台的动态库。
NDK好处:
1.在Java中声明native方法
package com.wujingchao.hellojni;
public class HelloJni {
static {
System.loadLibrary("hello-jni");
}
public native static String sendAndReceive(String msg) ;
public static void main(String [] args) {
String result = sendAndReceive("Hello");
System.out.println(result);
}
}
so库的名称为libhello-jni.so,这是加载so库的规范。
2.编译Java源文件得到class文件,javah命令导出JNI的头文件
编译class文件:
javac com/wujingchao/hellojni/HelloJni.java
结果:
└── com
└── wujingchao
└── hellojni
├── HelloJni.class
└── HelloJni.java
导出Jni头文件:
javah com.wujingchao.hellojni.HelloJni
结果:
├── com
│ └── wujingchao
│ └── hellojni
│ ├── HelloJni.class
│ └── HelloJni.java
└── com_wujingchao_hellojni_HelloJni.h
生成的.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_wujingchao_hellojni_HelloJni */
#ifndef _Included_com_wujingchao_hellojni_HelloJni
#define _Included_com_wujingchao_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_wujingchao_hellojni_HelloJni
* Method: sendAndReceive
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_wujingchao_hellojni_HelloJni_sendAndReceive
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
函数名规则:Java包名类名_方法名
JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法
jobject:表示Java对象本身即this
JNIEXPORT和JNICALL:JNI定义的宏,可以在java.h这个头文件里面找到
extern “C” 采用C语言的命名风格来编译。
3.实现JNI方法
实现JNI方法可以选择C++或者C来实现。
在工程目录下新建文件夹jni(名字不固定),将生成的头文件放到jni目录下面,将实现的.c或者.cpp文件放到jni目录下。
HelloJni.c:
#include "com_wujingchao_hellojni_HelloJni.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_wujingchao_hellojni_HelloJni_sendAndReceive
(JNIEnv *env, jclass thiz, jstring str) {
char* message = (char*)(*env)->GetStringUTFChars(env,str,NULL);
printf("receive message = %s\n",message);
return (*env)->NewStringUTF(env,"Bye!");
}
或者HelloJni.cpp:
#include "com_wujingchao_hellojni_HelloJni.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_wujingchao_hellojni_HelloJni_sendAndReceive
(JNIEnv *env, jclass thiz, jstring str) {
char* message = (char*)env->GetStringUTFChars(str,NULL);
printf("receive message = %s\n",message);
return env->NewStringUTF("Bye!");
}
主要区别集中在对env的操作上。
4.编译so库在Java中调用
gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC HelloJni.c -o libhello-jni.so
或者
gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC HelloJni.cpp -o libhello-jni.so
制定so库的路径执行Java程序,-Djava.library.path为so库的路径:
java -Djava.library.path=../jni com.wujingchao.hellojni.HelloJni
C和C++的调用结果是一样的:
wujingchao@N4050:~/WorkSpace/Java/HelloJNI/src$ java -Djava.library.path=../jni com.wujingchao.hellojni.HelloJni
receive message = Hello
Bye!
在HelloJni/local.properties里声明ndk的路径:
ndk.dir=/home/wujingchao/Android/android-ndk-r10e
sdk.dir=/home/wujingchao/Android/Sdk
定义本地方法,编译之后生成HelloJni/app/build/intermediates/classes/debug/com/wujingchao/android/hellojni/NDKUtils.class:
package com.wujingchao.android.hellojni;
public class NDKUtils {
static {
System.loadLibrary("hello-jni");
}
public native static String sendAndReceive(String message);
}
然后在debug目录下导出头文件:
javah com.wujingchao.android.hellojni.NDKUtils
├── com
│ └── wujingchao
│ └── android
│ └── hellojni
│ ├── BuildConfig.class
│ ├── MainActivity$1.class
│ ├── MainActivity.class
│ ├── NDKUtils.class
| /....
└── com_wujingchao_android_hellojni_NDKUtils.h
将其复制到app/src/main/jni,然后新建.c或者.cpp实现方法:
#include "com_wujingchao_android_hellojni_NDKUtils.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_wujingchao_android_hellojni_NDKUtils_sendAndReceive
(JNIEnv *env, jclass clazz, jstring s){
const char* str = (*env)->GetStringUTFChars(env,s,NULL);
printf("%s",str);
return (*env)->NewStringUTF(env,"Hello,Bye");
}
然后在build.gradle声明生成so:
defaultConfig {
applicationId "com.wujingchao.android.hellojni"
minSdkVersion 9
targetSdkVersion 23
versionCode 1
versionName "1.0"
ndk {
moduleName "hello-jni" //和System.loadLibrary("hello-jni");里面加载的一致
abiFilters "armeabi", "armeabi-v7a", "x86" //可以不写,那么就是全平台
}
}
JNI的数据类型基本数据类型和引用数据类型。
基本数据类型:
JNI类型 | Java类型 | 描述 |
---|---|---|
jboolean | boolean | ###### |
jbyte | byte | |
jint | int | |
… | … | .. |
void | void | .. |
引用数据类型:
JNI类型 | Java类型 | 描述 |
---|---|---|
jobject | Object | |
jclass | Class | |
jobjectArray | Object[] | |
jstring | String | |
jintArray | int[] | |
jthrowable | Throwable | |
… | … | .. |
JNI的类型签名标识了Java类型,可以是类,方法,数据类型。
类的签名采用 “L + 包名 + 类名 +;”,例如java.lang.String,签名为 Ljava/lang/String;
基本数据类型采用大写字母表示:
JNI类型 | 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
Long | J |
float | F |
double | D |
void | V |
对象的签名就是所属类名的签名,数组签名为 [ + 类型签名,比如:
int [] : [I
float[] : [F
int[][] : [[I
方法的签名为 (参数类型签名) + 返回值类型签名,例如:
boolean fun1(int a,double b,int[] c) : (ID[C)Z
boolean fun2(int a,String b,int[] c) : (ILjava/lang/String;[I)Z
void fun3(int i) : (I)V
JNI调用Java方法的流程是先通过类名找到类,然后在根据方法名找到方法的id。非构造方法需要构造类的对象才能调用,或者使用已有的对象。
调用静态方法:
jclass clazz = (*env)->FindClass("com/wujingchao/android/NDKUtils");
if(clazz == NULL){
//...
return;
}
jmethodId id = (*env)->GetStaticMethodID(clazz,"testStaticJni","()V");//第三个参数为方法签名
if(id == NULL) {
//...
return;
}
(*env)->CallStaticVoidMethod(clazz,id,NULL);//第三个参数为方法参数
调用对象方法:
jclass clazz = (*env)->FindClass(env, "com/wujingchao/android/NDKUtils");
if (clazz == NULL) {
//...
return;
}
jmethodId construct = (*env)->GetMethodID(env,clazz, "<init>","()V");
if (mid_construct == NULL) {
//...
return;
}
jmethodId testJniMethod = (*env)->GetMethodID(env, clazz, "testJni", "(Ljava/lang/String;I)V");
if (testJniMethod == NULL) {
//...
return;
}
jobject jobj = (*env)->NewObject(env,clazz,mid_construct);
if (jobj == NULL) {
//...
return;
}
jstring arg = (*env)->NewStringUTF(env,"Hello,Bye");
(*env)->CallVoidMethod(env,jobj,testJniMethod,arg);