写在前面的:
一年前,在 android A20下面弄一个应用,当时要操作硬件串口uart,需要java与c进行交互,于是按照经典的流程: 用javah 生成一个头文件,然后我去实现头文件里面的函数。再后来,来了一哥们,弄了另外一种方法写Jni,搭建了一个框架,飘然而去,然后留下几个c的API让我去维护....半年过去了,最近,自己开始学习 c++部分的东西,于是开始来学习他的这部分代码。并在网上找了学习资料。
下面是我找到的资料和笔记。
/**************************************************************************************************************/
先把自己找的资料贴在这里,感谢作者分享!
http://blog.csdn.net/jianguo_liao19840726/article/details/6719224
简而言之:
1 android(java)代码中的本地方法列表(c/c++ 库提供的API),示例如下:
public class DataProvider { private static final class DataProviderHolder { private static final DataProvider instance = new DataProvider(); } private DataProvider() { } public static DataProvider getInstance() { return DataProviderHolder.instance; } /*本地方法实例一*/ public synchronized native int Operator(String deviceMac, int deviceType, int state); /*本地方法实例二*/ public synchronized native int OperatorCmd(String deviceMac, int deviceType, int cmd, int state); /*本地方法实例三*/ public synchronized native String OperatorCmdString(String deviceMac, int deviceType, int cmd, int state); /*本地方法实例四*/ public native int OpenTty(String serialPort); /*本地方法实例五*/ public synchronized native String WriteDevice(String value,int type,int cmd,int val);
/*开始呼唤上面列出来的本地实例*/
static { System.loadLibrary("zigbee_r_lock"); } }
2 在程序执行到
System.loadLibrary("zigbee_r_lock");
这句代码的时候,会执行 loadLibrary()中的一个回调函数(callback),这个回调名字是:JNI_OnLoad().
3 c/c++的程序员来实现 JNI_OnLoad()
示例如下:
1 jint JNI_OnLoad(JavaVM* vm, void* reserved) 2 { 3 JNIEnv* env = NULL; 4 jint result = -1; 5 6 device = new Device() ; 7 8 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 9 return result; 10 } 11 assert(env != NULL); 12 13 if (register_tuner_jni(env) < 0) 14 { 15 return result; 16 } 17 18 result = JNI_VERSION_1_4; 19 20 return result; 21 }
在 JNI_OnLoad()中,可以实现资源的分配,比如 new 一个对象;
注册本地方法;
指定jni 所使用的版本。
4 因为要注册的本地方法是各种各样的,于是自己实现一个注册方法即可: register_tuner_jni(JNIEnv *env),代码如下:
static int register_tuner_jni(JNIEnv *env){ return registerNativeMethods(env,"cn/acadiatech/telecom/box/engine/DataProvider", gMethods, sizeof(gMethods) / sizeof(gMethods[0])); }
registerNativeMethods(env,"cn/acadiatech/telecom/box/engine/DataProvider", gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
这句代码中的第二个参数,是一个类的名字。比如在eclipse某个xx.java文件中新建一个类后,它会生成一个pack或者类的路径。比如上面的DataProvider这个类就放在了eclipse的 src/cn/acadiatech/telecom/box/engine/DataProvider.java 文件中。于是,registerNativeMethods()的第二个参数<类名> 就写成了 "cn/acadiatech/telecom/box/engine/DataProvider" 如果这个类名搞错了,是跑不起来的。
看看代码吧:
1 static int registerNativeMethods(JNIEnv* env, const char* className, 2 JNINativeMethod* gMethods, int numMethods) 3 { 4 jclass clazz; 5 6 7 clazz = env->FindClass(className); 8 if (clazz == NULL) { 9 return JNI_FALSE; 10 } 11 if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { 12 return JNI_FALSE; 13 } 14 15 return JNI_TRUE; 16 }
最关键的就是这句代码:
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { return JNI_FALSE; }
其中的 gMethods 是一个函数指针列表 <数组>,如下:
1 static JNINativeMethod gMethods[] = { 2 {"OperatorCmd","(Ljava/lang/String;III)I",(void *)Operator_device_Cmd}, 3 {"OpenTty","(Ljava/lang/String;)I",(void*)Open_tty_device}, 4 {"CloseTty","()V",(void*)Close_tty_device}, 5 {"SetTtySpeed","(I)I",(void*)Set_speed_tty_device}, 6 {"SetTtyParity","(I)I",(void*)Set_parity_tty_device}, 7 {"SetTtyBits","(I)I",(void*)Set_bits_tty_device}, 8 {"SetTtyStopBits","(I)I",(void*)Set_stop_bits}, 9 {"SetTtyFlowControl","(I)I",(void*)Set_flow_control_tty_device}, 10 {"ListDevice","()Ljava/lang/String;",(void*)List_device}, 11 {"ReadDevice","()Ljava/lang/String;",(void*)Read_device}, 12 {"ListDevice_new","(Ljava/lang/String;III)Ljava/lang/String;",(void*)List_device_new}, 13 {"WriteDevice","(Ljava/lang/String;III)Ljava/lang/String;",(void*)Write_device}, 14 {"LedControl","(Ljava/lang/String;IIIII)Ljava/lang/String;",(void*)Led_Control}, 15 // {"OperatorCmdString","(Ljava/lang/String;III)Ljava/lang/String;",(void*)Operator_device_Cmd_String}, 16 };
额... 对,数量是没有限制的,只是,自己需要将要注册函数的个数传输给 registerNativeMethods() 即可。
这个函数指针列表每一项分为三部分 :
[1] 在java中的 Native名字--- java使用的时候,就使用这个名字
[2] 类型 ---- 包括参数类型 + 返回值类型
[3] c/c++ 中函数的名字,c/c++程序员要实现的,就是实现这个函数名字里面的东西
说说类型部分吧:
第一点:
" () " 中的东西,表示了函数的参数类型,紧跟在括号后面的表示了返回值的类型。如果类型为 java 中的 String 类型,那么必须以下面这种格式写:
Ljava/lang/String;
注意它的格式: 以 L 开头,以 " ;" 分号结尾。
无论是返回值函数参数类型,都必须这种写法。
第二点:
注意类型的转换: 比如自己要在c/c++中给 java层返回 char * / string 这种类型的数据,是不能直接返回的,需要经过类型转换才行的。此为后话,后面再提。
5 实现函数指针列表中的函数(c/c++部分的函数名字)
static jint Open_tty_device(JNIEnv *env ,jobject obj ,jstring tty) { const char* tty_name = env->GetStringUTFChars(tty,0) ; return device->open_tty_device(tty_name) ; }
/* ** if success return this open device fd else error is -1 */ int Device::open_tty_device(const char* tty_name ) { this->fd = ngb_Open((char*)tty_name) ; if (this->fd < 0) { } return this->fd ; }
当然了,Device是一个类,里面包括了很多成员函数。于是,整个jni 的脉络框架就这样了。唯里面的细节部分需要注意了,比如资源的申请、类型的转换、具体的c++部分代码的实现了---这已经于jni 没关系了,而是c++的范畴了。
写在最后: 如果要调用硬件成功,android层需要添加一行 “加访问权限” 的代码,否则将无权限打开底层硬件---这仅仅是针对要限制权限的情况下。
笔记中所用到的代码位于github上:
https://github.com/boyisgood86/libzigbee