最近在使用JNI,实现android调用C代码功能并返回结果的功能,于是记下来整个流程以作记录。
JNI(Java Native Interface),可以实现Java代码与C/C++代码之间的相互调用。
本文分为以下几部分:
1.JNI基础使用
2.JNINativeMethod数据类型描述符
3.JNI获取java自定义类中数据
1.JNI的基础使用
使用之前需要使用Android Studio下载NDK(SDK Manager—>SDK Tools—>NDK)。
创建一个用来连接C的类,所有需要C用到的方法都写这个类里面,如下面 native修饰的方法。静态区域内加载了名为libjnitest.so的库,只需要写jnitest即可。创建完native方法会报错,先不理会,继续往下走错误会自动消失。在getTestData中使用native方法,可以在activity中调用getTestData方法。
public class AndroidJni {
static{
System.loadLibrary("jnitest");
}
private native int getTestData(TestData data);
public String getTestData(int testI){
TestData data = new TestData();
data.setData(testI);
int i = getTestData(data);
return "数据:" + i;
}
}
创建一个专门存放C代码的文件夹(右键—>New—>Floder—>JNI Floder),文件夹结构如下
在cpp文件中创建C文件代码(右键—>New—>C/C++ Source File,C选择.C,C++选择.cpp,有可能选择.C但是文件图标显示的c++,没有影响)
static jint jni_getTestData(JNIEnv* env,jclass thiz, jobject data){
jclass jcobs = (*env)->GetObjectClass(env, data);
jfieldID jdata = (*env)->GetFieldID(env, jcobs, "data", "I");
jint ji = (*env)->GetIntField(env, data, jdata);
return ji;
}
static JNINativeMethod nativeMethods[] = {
{"getTestData","(Lcom/ly/jnitest/TestData;)I", (void*)jni_getTestData}
};
int registerNatives(JNIEnv* env) {
/* look up the class */
jclass clazz = (*env)->FindClass(env, "com/ly/jnitest/AndroidJni");
if (clazz == NULL)
return 0;
if ((*env)->RegisterNatives(env, clazz, nativeMethods, sizeof(nativeMethods)
/ sizeof(nativeMethods[0])) != JNI_OK)
return 0;
return 1;
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
(void)reserved;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
return -1;
if (!registerNatives(env))
return -1;
/* success -- return valid version number */
result = JNI_VERSION_1_6;
return result;
}
上面是jnitest.c文件代码,JNI_Onload方法相当于onCreate方法,会自动执行这个方法,执行registerNatives方法找到android中放C使用方法的文件(AndroidJni),然后使用RegisterNatives方法完成C与Java方法的联系。使用JNINativeMethod结构体描述android方法。
typedef struct {
const char* name;//java中方法名
const char* signature;//java方法传值与返回值
void* fnPtr;//C中方法名
} JNINativeMethod;
如{"getTestData","(Lcom/ly/jnitest/TestData;)I", (void*)jni_getTestData}中第一个参数为java文件中Native方法名,第二个参数()中为java方法的传值类型,括号后为java方法的返回值类型,第三个参数为c文件中与java方法对应的方法名。JNINativeMethod中使用数据类型在第二节统一记录。
jni_getTestData方法中主要取出java中TestData类中data数据,然后返回取出的数据。JNI中怎么取出java中数据在第三节统一记录。
再在cpp文件中创建Android.mk、Application.mk(右键—>New—>File)
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jnitest
LOCAL_SRC_FILES := jnitest.c
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH:Android.mk必须以它开始
LOCAL_MODULE:定义生成so库名字,与java中LoadLibrary中一致
LOCAL_SRC_FILES :列出要打包的所有C/C++ 源码
Application.mk:
APP_ABI := all
最后在build.gradle(app)中,添加两段代码 ,点击sync now按钮
android.defaultConfig中添加
ndk {
moduleName "jnitest"
}
android中添加
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
以上代码部分就全部结束,然后运行代码,可能会报找不到生成的so库,这时需要在android studio下方点击Terminal窗口,跳转至jni文件目录(输入 cd E:\XXX\app\src\main\jni),然后输入ndk-build就会生成so库,再运行代码,就会成功。输入ndk-build还可以检查C代码中的错误,有错误将会生成不了so库。
2.JNINativeMethod数据类型描述符
本节主要记录JNINativeMethod结构体中第二个参数该怎么写。以"(Lcom/ly/jnitest/TestData;)I"为例,括号中为java中传递的参数类型。
基本类型:
java类型 | C字符 |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
boolean | Z |
引用类型(需以L开头 ;结尾):
java类型 | C字符 |
String | Ljava/lang/String; |
TestData(自定义类) | Lcom/ly/jnitest/TestData; |
引用类型镶嵌类(使用的是类中的内部类):使用$符号隔开
Lcom/ly/jnitest/TestData$Native;
数组类型:在C字符前添加 [ 符号,如 [B 表示 byte[]类型。二维数组添加[[ 符号,依此类推。
如果java中没有传递参数,括号里空的即可,如果有多个传递参数,可以写成 "(BB)",直接连在一起,也可以为了直观写成
"("
"B"
"B"
")"
括号后面为java方法中定义的返回值,与上面使用的字符一样,直接跟在括号后面,只比传递参数多了一个void返回类型,在括号后面跟上V字符,例如"()V",对应的java就是 void XX()方法。
3.JNI获取java自定义类中数据
本节主要讲java传递参数为自定义类,JNI中怎么获取类中各个参数。通过
jclass jc = (*env)->GetObjectClass(env, data);
获取到 jcalss,data为传递的自定义类。然后
jfieldID jdata = (*env)->GetFieldID(env, jc, "data", "I");
获取到FieldId,GetFieldID函数中第二个参数为jclass类型,第三个参数是自定义类中成员名称,第四个参数是成员对应的类型字符(字符参考第二节内容,使用是相同的规则)。
最后通过FieldId获取数据,如果是自定义类中的基本类型参数可以通过GetIntField、GetDoubleField等对应的基本类型的方法获取到,如
jint str = (*env)->GetIntField(env, data, jdata);
第二个参数为java传递参数,第三个参数为FieldId。
如果自定义类中参数为String,需要使用
jstring jstr = (jstring)(*env)->GetObjectField(env, data, jfieldid);
参数为自定义类
jobject jobj = (*env)->GetObjectField(env, data, jfieldid);
参数为数组
jdoubleArray jarray = (jdoubleArray)((*env)->GetObjectField(env, data, jfieldid));
获取数组中数据并赋值给C类型:
jdouble* array = (*env)->GetDoubleArrayElements(env,jarray, NULL);
uint32_t arraysize = (*env)->GetArrayLength(env, array);//获取长度
int j = 0;
for(j = 0; j < arraysize; j++) {
P[j] = array[j];
}
到这里,基本用到的JNI的内容就已经记录完了。