Android开发笔记(六十九)JNI实战
2016-02-24 14:57
507 查看
NDK
NDK的用途
NDK全称为Native Development Kit,意即原生的开发工具,NDK允许开发者在APP中通过C/C++代码执行部分程序。它是Android提供的方便开发者通过JNI接口进行Java与C/C++交叉编译的工具集。NDK的用于概括来说主要分为以下几种情况(以下三点摘自百度百科):
1. 代码的保护,由于apk的Java层代码很容易被反编译,而C/C++库反编译难度较大;
2. 在NDK中调用第三方C/C++库,因为很多的开源库都是用C/C++代码编写的,例如:OpenGL,FFmpeg等;
3. 便于移植,用C/C++写的库可以很方便在其它的嵌入式平台上再次使用。
NDK环境搭建
NDK与SDK是分开的,所以需要另外下载NDK,下载下来的NDK无需安装只需解压。然后打开ADT,依次打开菜单“Window”——“Preferences”——“Android”——“NDK”,在弹窗中输入本地的NDK目录。接着新建一个Android工程,右击工程名,右键菜单依次选择“Android Tools”——“Add Native Support”,即在工程中添加了NDK支持,可以进行JNI开发了。
JNI
JNI的概念
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C/C++)。虽然JNI是java的平台标准,但要想在Android上使用JNI,还得配合NDK才行。NDK提供了C/C++标准库的头文件,以及标准库的动态链接文件(主要是.a文件和.so文件)。而JNI是在自己工程下面编写JNI接口的C/C++代码以及mk编译文件,代码中要包含NDK的头文件,然后mk文件又依据规则把标准库链接进去,编译通过形成最终的so动态库文件。这样才能在APP中调用JNI接口。
JNI的开发步骤
下面是本人总结的jni开发步骤:1、首先确保NDK环境搭建完成,并且Android工程已经添加了NDK的支持。
2、在要调用jni接口的Activity代码中添加jni接口定义,以及加载jni动态库,代码示例如下:
public native String abiFromJNI(int i1, float f1, double d1, boolean b1); public native String unimplementedAbiFromJNI(int i1, float f1, double d1, boolean b1); static { System.loadLibrary("test_jni"); }3、转到工程的jni目录下,在c/cpp文件中编写C/C++代码。注意C代码中对接口名称的命名规则是:Java_包名_Activity类名_函数名,其中包名中的点号要替换为下划线。
4、在Android.mk中添加cpp文件名称,告知编译器有新的c代码需要编译。
5、jni默认只会生成armeabi版本的so,如果还需要其它版本的so,要新建编译文件Application.mk,补充内容“APP_ABI := armeabi armeabi-v7a x86”等等,如想要生成所有版本的so,可填写“APP_ABI := all”。
JNI与C/C++数据类型的转换
JNI作为Java与C/C++之间的联系桥梁,需要对基本数据类型进行转换,下面是几种基本数据类型的对应关系:整型:int(Java),jint(JNI),int(C/C++)
浮点数:float(Java),jfloat(JNI),float(C/C++)
双精度:double(Java),jdouble(JNI),double(C/C++)
布尔型:boolean(Java),jboolean(JNI),unsigned char(C/C++)
字符串:String(Java),jstring(JNI),const char*(C/C++)
其中整型、浮点数、双精度三种可以直接使用,布尔型和字符串需要处理后才能使用。
布尔类型中,Java的false对应C/C++的0,Java的true对应C/C++的1。
字符串类型的处理有点麻烦,JNI使用env->GetStringUTFChars方法将jstring类型转为const char*,使用env->NewStringUTF方法将const char*转为jstring类型。
JNI编码的注意事项
下面是本人在实际开发中,总结出来的几个注意事项(不完整,在实际工作中持续更新):1、每个接口必须写在不同的c文件中,同时要修改Android.mk,在LOCAL_SRC_FILES处补充编译新增加的c文件。
2、socket操作要设置上网权限,否则socket函数总是返回-1。
3、c代码中的变量尽量都初始化。因为发现有的变量在linux和模拟器都没问题,但在真机上若不初始化,其值就不可预知。
4、由于jni接口名称包含包名、类型、函数名,因此生成的so动态库在另一个工程调用时,务必保证路径完整一致才能正常调用。
代码示例
网上对jni例子的代码讲解多是测试性质,没有多少实际开发意义。现在刚好工作有个根据ip查找对方电脑名称的要求,这可算是把jni派上用场了。根据ip查找对方电脑名称及MAC地址,CSDN上有现成的c代码,当然那是linux环境下的c代码,倘若移植到Android,还是得做些修改处理。下面就是改好的代码示例:#include <jni.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netdb.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define send_MAXSIZE 50 #define recv_MAXSIZE 1024 struct NETBIOSNS { unsigned short int tid; //unsigned short int 占2字节 unsigned short int flags; unsigned short int questions; unsigned short int answerRRS; unsigned short int authorityRRS; unsigned short int additionalRRS; unsigned char name[34]; unsigned short int type; unsigned short int classe; }; char *getMacFromIp(const char *ip); extern "C" jstring Java_com_example_exmjni_ApActivity_macFromJNI( JNIEnv* env, jobject thiz, jstring ip) { const char* str_ip; str_ip = env->GetStringUTFChars(ip, 0); return env->NewStringUTF(getMacFromIp(str_ip)); } char *getMacFromIp(const char *ip) { char str_mac[1024] = {0}; struct sockaddr_in toAddr; //sendto中使用的对方地址 struct sockaddr_in fromAddr; //在recvfrom中使用的对方主机地址 char send_buff[send_MAXSIZE]; char recv_buff[recv_MAXSIZE]; memset(send_buff, 0, sizeof(send_buff)); memset(recv_buff, 0, sizeof(recv_buff)); int sockfd; //socket unsigned int udp_port = 137; int inetat; if ( (inetat = inet_aton(ip, &toAddr.sin_addr)) == 0) { sprintf(str_mac, "[%s] is not a valid IP address\n", ip); return str_mac; } if ( (sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) { sprintf(str_mac, "%s socket error sockfd=%d, inetat=%d\n", ip, sockfd, inetat); return str_mac; } bzero((char*)&toAddr,sizeof(toAddr)); toAddr.sin_family = AF_INET; toAddr.sin_addr.s_addr = inet_addr(ip); //toAddr.sin_addr.s_addr = htonl(INADDR_ANY); //toAddr.sin_addr.s_addr = inet_addr("192.168.48.129"); toAddr.sin_port = htons(udp_port); //struct ss; //包含 UDP结构体长度 和 字符串 //memcpy(send_buff, &ss, sizeof(ss)); //构造netbios结构包 struct NETBIOSNS nbns; nbns.tid=0x0000; nbns.flags=0x0000; nbns.questions=0x0100; nbns.answerRRS=0x0000; nbns.authorityRRS=0x0000; nbns.additionalRRS=0x0000; nbns.name[0]=0x20; nbns.name[1]=0x43; nbns.name[2]=0x4b; int j=0; for (j=3;j<34;j++) { nbns.name[j]=0x41; } nbns.name[33]=0x00; nbns.type=0x2100; nbns.classe=0x0100; //memset(send_buff,..,sizeof(send_buff)); 把send_buff设置为udp包格式 memcpy(send_buff, &nbns, sizeof(nbns)); int send_num =0; send_num = sendto(sockfd, send_buff, sizeof(send_buff), 0, (struct sockaddr *)&toAddr, sizeof(toAddr) ); if (send_num != sizeof(send_buff)) { //sizeof(nbns)=50 ? sprintf(str_mac, "%s sendto() error sockfd=%d, send_num=%d, sizeof(send_buff)=%d\n", ip, sockfd, send_num, sizeof(send_buff)); //close(sockfd); shutdown(sockfd, 2); return str_mac; } int recv_num =0; recv_num = recvfrom(sockfd, recv_buff, sizeof(recv_buff), 0, (struct sockaddr *)NULL, (int*)NULL); if (recv_num < 56) { sprintf(str_mac, "%s recvfrom() error sockfd=%d, recv_num=%d\n", ip, sockfd, recv_num); //close(sockfd); shutdown(sockfd, 2); return str_mac; } unsigned short int NumberOfNames=0; //这里要初始化。因为发现linux和模拟器都没问题,真机上该变量若不初始化,其值就不可预知 memcpy(&NumberOfNames, recv_buff+56, 1); int i=0; sprintf(str_mac, "%s%-12s : %s\n", str_mac, "IP Address", ip); sprintf(str_mac, "%s%-12s : ", str_mac, "Host Name"); //sprintf(str_mac, "%s\nNumberOfNames=%d", str_mac, NumberOfNames); char str_name[1024] = {0}; for (i=0; i<NumberOfNames; i++) { char NetbiosName[16]; memcpy(NetbiosName, recv_buff+57+i*18, 16); //Segmentation fault sprintf(str_mac, "%s%s", str_mac, NetbiosName); if (i != NumberOfNames-1) { sprintf(str_mac, "%s/", str_mac); } //依次读取netbios name if (i == 0) { sprintf(str_name, "%s", NetbiosName); } } unsigned short int mac[6]={0}; sprintf(str_mac, "%s\n%-12s : ", str_mac, "MAC Address"); sprintf(str_mac, "%s|", str_name); //如要完整信息,可把此行注释 for (i=0; i<6; i++) { memcpy(&mac[i], recv_buff+57+NumberOfNames*18+i,1); sprintf(str_mac, "%s%02X", str_mac, mac[i]); if (i != 5) { sprintf(str_mac, "%s-", str_mac); } } return str_mac; }
点此查看Android开发笔记的完整目录
相关文章推荐
- 38.Android之ListView简单学习(一)
- android paint cap join 理解 图示
- Unable to resolve target 'android-14'
- [置顶] 获取android手机的屏幕分辨率 android开发
- android静默更新
- 对Android内部储存的理解
- Android之三种实现自定义ProgressBar的方式
- Android各组件/控件间通信利器之EventBus
- android ndk调用OpenGL 实现纹理贴图Texture
- Android字符串进阶之三:字体属性及测量(FontMetrics)
- Android之粘性广播理解
- 自定义 音量view
- 打开Android本地Api文档缓慢的问题
- androidstudio常见问题
- Android之退出整个应用方法之二
- View事件分发源码分析
- Android-Universal-Image-Loader三大组件DisplayImageOptions、ImageLoader、ImageLoaderConfiguration详解
- Android之退出整个应用方法之一
- ios和Android的远程调试(Remote Debugging)
- Activity之间传递数据