在Android中 NDK是实现JNI的手段
JNI定义:JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)
JNI缺陷:使用java与本地已编译的代码交互,通常会丧失平台可移植性
Java调用JNI调用步骤:
1.创建一个类,用以声明本地方法
2.使用Android.mk编译apk
3.使用javah生成头文件头文件
4.编写.c文件,实现.h中的本地方法(如果是C++(.cpp或.cc),需要使用extern "C"{} 把本地方法括进去)
5.将.c文件编译为本地库
6.使用apk,调用库,以运行程序
javah使用方法:
javah -classpath .h文件生成路径 -jni 包名+类名,需要在包的同级目录下执行
例如:/home/root/test/com/hello/jni/test/TestJNI.java,需要在/home/root/test目录下执行
javah -classpath /home/root/test -jni com.hello.jni.test.TestJNI
C/C++调用JNI步骤:
1.通过findClass找到Java对应的jclass
2.根据jclass,调用GetMethodID获取要调用方法的jmethodID
3.根据jmethodID和方法描述符,调用CallTypeMethod方法实现调用java方法,其中Type表示返回值类型,如CallStringMethod
下面先介绍一下JNI的一些概念,再通过一个简单的例子说明:
1.原始数据类型匹配
2.复杂数据类型匹配
3.类描述符
4.域描述符
5.方法描述符
其中3、4、5在C调用java中会用到
原始数据类型匹配:
复杂数据类型匹配:
类描述符:将包名+类名中的“.”换成“/”
如:com/github/jni/AndroidJni,对应的为com.github.jni.AndroidJni
数据类型描述符:
其中重点强调一下Z对应的是bool;J对应的是long;[对应的是数组,如int[]对应[I;Lobject;对应object,如String对应Ljava/lang/String;(注意,这里面的“;”不是标点)
方法描述符:
方法描述符,将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符。对于,没有返回值的,用V表示
(参数1类型签名参数2类型签名参数3类型签名参数N类型签名...)返回值类型
String test(); ----------------------------->()Ljava/lang/String;
int f(int i, Object j);---------------------->(ILjava/lang/Object;)I
void set(byte[] bs);---------------------------->([B)V
下面通过一个简单的例子说明java通过jni调用C/C++过程及C/C++调用java过程,为简约起见,只列举关键代码
- 编写java类,并定义native方法
package demo.jni;
public class TestJNI {
//传递给一个String对象
private native String getLine(String str);
}
- 通过javah生成.h文件(工具生成,不详述)
- 实现.c文件
#include <stdio.h>
#include <stdlib.h>
#include "demo_jni_TestJNI.h"
JNIEXPORT jstring JNICALL Java_demo_jni_TestJNI_getLine
(JNIEnv *env, jobject clazz, jstring prompt)
{
/*JNIEXPORT jstring JNICALL Java_demo_jni_TestJNI_getLine(JNIEnv *env, jobject clazz, jstring prompt)
jstring: java方法返回值类型在jni层匹配的数据类型
Java_demo_jni_TestJNI_getLine: Java(前缀)_demo_jni_TestJNI(包名+类名)_getLine(方法名)
JNIEnv *env: 指向JNI函数表,JNI通过函数表查找调用的方法
jobject clazz: 静态方法使用jclass;一般方法调用使用jobject
*/
char buf[128] = "本地代码字符串"; //字符缓冲
jbyte *str;//jbyte类型对应C中的unsigned char
str = (*env)->GetStringUTFChars(env, prompt, NULL);
//这个函数就是将jstring类型的字符串转换为本地字符串,返回jbyte*类型
//Java代码使用的是Unicode编码,C使用的UTF-8编码,所以需要使用GetStringUTFChars转换
if(str == NULL)
{
/**
GetStringUTFChars方法可能会抛出一个OutOfMemoryError的异常,在jni中的异常机制和java中的并不一样
在java中抛出异常,如果没有捕获,则程序结束运行;但是,在jni中,即使抛出异常,在本地代码的执行顺序依然不变。
所以,这里判断NULL是必须的
**/
return NULL;
}
printf("%s",str);
/**
**使用完了utf-8类型的字符后,我们需要释放由上面方法返回的字符串,这样可以释放被这些字符占用的内存空间,避免造成内存瘫痪
**/
(*env)->ReleaseStringUTFChars(env, prompt, str);
//下面我们看看如何将本地的代码传到java
return (*env)->NewStringUTF(env, buf); //该方法实例化一个UTF-8编码的本地字符串为java.lang.String类型,新创建的就是java中
//Unicode类型的代表同一字符串的实例
//该方法同样可能抛出一个OutOfMemoryError的异常并返回NULL。
}
C/C++调用JAVA
- 编写java类和方法
public class TestJNI {
private String getStringfromJava(String str){
return "java 字符串";
}
}
- 编写C代码调用java方法
#include <stdio.h>
#include <stdlib.h>
#include "demo_jni_TestJNI.h"
void callJava()
{
char* classname = "demo/jni/TestJNI";
//获取java对应的class
jclass dpclazz = (*env)->FindClass(env, classname);
//获取方法的jmethodID,其中dpclazz为上一步获取的到jclass,"(Ljava/lang/String;)Ljava/lang/String;"为方法描述符
jmethodID methodID = (*env)->GetMethodID(env, dpclazz,"getStringfromJava", "(Ljava/lang/String;)Ljava/lang/String;");
//调用java方法,其中obj表示调用一般方法,
调用static方法,使用CallStaticIntMethod(env,clazz,methodID,"C本地字符串"));
jstring result = (*env)->CallIntMethod(env,obj,methodID,"C本地字符串"));
char *str = (*env)->GetStringUTFChars(env, result,0);
printf("%s",str);
}
最后,附上jni处理字符串常用方法
GetStringUTFChars 将jstring转换成为UTF-8格式的char*
GetStringChars 将jstring转换成为Unicode格式的char*
ReleaseStringUTFChars 释放指向UTF-8格式的char*的指针
ReleaseStringChars 释放指向Unicode格式的char*的指针
NewStringUTF 创建一个UTF-8格式的String对象
NewString 创建一个Unicode格式的String对象
GetStringUTFLength 获取UTF-8格式的char*的长度
GetStringLength 获取Unicode格式的char*的长度
以上文章参考http://blog.csdn.net/chenjie19891104/article/details/6412471
http://blog.csdn.net/quan648997767/article/details/65444138
http://blog.csdn.net/carson_ho/article/details/73250163
,请指正
919353569@qq.com