今天在看android硬件抽象层时需要用到Java的JNI调用,所以在网上找了些资料学习了一下,特别在这里记录一下。Java的JNI调用主要分为两种方式静态调用方式和动态调用方式。
静态调用方式:
首先确保你的pc上安装了jdk和g++,如果没有安装请安装后在试: 打开自己常用的目录,输入以下命令:
mkdir test
vim test/HelloWorld.java
package test; public class HelloWorld { public static void main(String[] args) { System.loadLibrary("HelloWorld"); print_hello(); } public static native final void print_hello(); }
其中最后声明的方法就是我们将要调用的方法,最后会在c++里实现,如果是声明的JNI方法一定要在方法前加上native关键字,否则系统无法识别到。
之后就可以调用java命令编译了。
javac test/HelloWorld.java
之后通过javah命令生成我们的头文件。实现该头文件定义的方法。
vim test/HelloWorld.cpp
#include "Hello.h" #include <cstdio> void Java_test_HelloWorld_print_1hello(JNIEnv *,jclass) { printf("helloworld asianux\n"); }
调用g++编译该头文件,生成一个动态库。
g++ test/HelloWorld.cpp -I /usr/lib/jvm/java-6-sun/include/ -I /usr/lib/jvm/java-6-sun/include/linux/ -fPIC --shared -o test/libHelloWorld.so
如果以上命令没有问题,那就可以运行了
java test.HelloWorld
但是一般都会出点问题,出错信息如下:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1738) at java.lang.Runtime.loadLibrary0(Runtime.java:823) at java.lang.System.loadLibrary(System.java:1028) at test.HelloWorld.main(HelloWorld.java:7)
这个时因为我们没有正确设置java的库路径导致的,执行以下命令设置该变量的值
export LD_LIBRARY_PATH=`pwd`/test
再次运行程序就可以看到正常输出了。
以上就是JNI中的静态调用。
动态调用:
动态调用相比与静态调用的主要区别有以下几点:
1.定义调用的JNINativeMethod
2.定义调用挂钩的函数
3.实现JNI_OnLoad函数
JNI_OnLoad是java jni技术的一个实现,每次java层加载System.loadLibrary之后,自动会查找改库一个叫JNI_OnLoad的函数,动态注册的时候,cpp可以通过实现JNI_OnLoad而完成jni的动态注册。
此处的代码和上面没什么差别,主要的差别在cpp文件的实现中,cpp的实现如下:
#include "Hello.h" #include <cstdio> static void android_print(JNIEnv *env,jclass clazz) { printf("hello asianux\n"); } static JNINativeMethod gMethods [] = { {"print_hello","()V",(void *)android_print}, }; jint JNI_OnLoad(JavaVM *vm, void *reseved) { JNIEnv *env = NULL; jint result = -1; if(vm->GetEnv((void**)&env,JNI_VERSION_1_4) !=JNI_OK) { return -1; } char className[20] = {"dynamic/HelloWorld"}; jclass clazz = (env)->FindClass( (const char*)className); if((env)->RegisterNatives(clazz,gMethods,1)<0) { return -1; } result = JNI_VERSION_1_4; return result; }
所不同的是这次调用时将不是直接查找print_hello方法的实现,而是查找该方法的映射函数。其中JNINativeMethod 的定义主要包含如下:
第一个字段是java层声明的方法也就是我们声明的print_hello方法
第二个字段相对来说就比较复杂一点了,其对应关系如下:
"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func(); "(II)V" 表示 void Func(int, int); 具体的每一个字符的对应关系如下 字符 Java类型 C类型 V void void Z jboolean boolean I jint int J jlong long D jdouble double F jfloat float B jbyte byte C jchar char S jshort short 数组则以"["开始,用两个字符表示 [I jintArray int[] [F jfloatArray float[] [B jbyteArray byte[] [C jcharArray char[] [S jshortArray short[] [D jdoubleArray double[] [J jlongArray long[] [Z jbooleanArray boolean[]
上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。
而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring Ljava/lang/String; String jstring Ljava/net/Socket; Socket jobject
第三个参数就是我们最终调用函数的实现。按照静态调用的方式编译该程序,就可以看到运行结果。
对应到android的实现为:
java文件的实现:
package com.android.server; import android.content.Context; import android.os.IMemdevService; import android.util.Slog; import android.util.Log; public class MemdevService extends IMemdevService.Stub { private static final String TAG = "MemdevService"; private int mPtr = 0; MemdevService() { mPtr = init_native(); } public void setVal(String val) { if(mPtr == 0) { return ; } setVal_native(mPtr,val); } public String getVal() { if(mPtr == 0) { return null; } String str = getVal_native(mPtr); return str; } private static native int init_native(); private static native void setVal_native(int ptr,String val); private static native String getVal_native(int ptr); };
其中我们定义了三个方法,第一个方法返回一个init类型参数,第二个方法无返回值但是需要传递两个int类型的参数,第三个方法返回一个String类型的参数,带一个int类型的参数
其c++文件实现为:
#define LOG_TAG "MemdevServiceJNI" #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include <utils/misc.h> #include <utils/Log.h> #include <hardware/hardware.h> #include <hardware/memdev.h> namespace android { static inline int memdev_device_open(const hw_module_t *module,struct memdev_device_t **device) { int fd = module->methods->open(module,MEMDEV_HARDWARE_MODULE_ID,(struct hw_device_t**)device); return fd; } static void memdev_setVal(JNIEnv *env,jobject clazz,jint ptr,jstring value) { memdev_device_t *device = (memdev_device_t*)ptr; LOGE("*****memdev_setVal ptr=%d\t value=%d\n",ptr,value); if(!device) { LOGE("device memdev is not open"); return ; } const char* chars = env->GetStringUTFChars(value, NULL); char *str = strdup(chars); device->set_val(device,str); } static jstring memdev_getVal(JNIEnv *env,jobject clazz,jint ptr) { memdev_device_t *device = (memdev_device_t*)ptr; if(!device) { LOGE("Device memdev is not open"); } char *str; int value = device->get_val(device,&str); int strLen = strlen(str); jstring jstr = env->NewStringUTF(str); return jstr; } static jint memdev_init(JNIEnv *env,jclass clazz) { memdev_module_t *module; memdev_device_t *device; LOGI("Init HAL stub memdev......"); if(hw_get_module(MEMDEV_HARDWARE_MODULE_ID,(const struct hw_module_t **)&module) == 0); { LOGI("device with find ...."); if(memdev_device_open(&(module->common),&device) == 0) { LOGI("device memdev is open %d\n",device->fd); return (jint)device; } LOGI("Failed to open device"); return 0; } } static const JNINativeMethod method_table[] = { {"init_native","()I",(void*)memdev_init}, {"setVal_native","(ILjava/lang/String;)V",(void*)memdev_setVal}, {"getVal_native","(I)Ljava/lang/String;",(void*)memdev_getVal}, }; int register_android_server_MemdevService(JNIEnv *env) { return jniRegisterNativeMethods(env,"com/android/server/MemdevService", method_table,NELEM(method_table)); } };
在android源码树中编译无问题,正常通过。
参考自:http://blog.chinaunix.net/uid-10275706-id-3241480.html