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动态库在另一个工程调用时,务必保证路径完整一致才能正常调用。
5、cpp代码中若要使用std标准库,需要修改Application.mk加入“APP_STL := gnustl_static”。
6、如果导入别人写的jni工程,打开cpp文件时可能会提示以下错误,不过一般不影响编译。
“Unresolved inclusion: <jni.h>”,右击工程依次选择“Properties”——“C/C++ General”——“Paths And Symbols”——“Includes”——“Add”,添加编译平台的头文件目录,例如“D:\android-ndk-r10d\platforms\android-19\arch-arm\usr\include”。
“Unresolved inclusion: <iostream>”、“Symbol 'std' could not be resolved”,在上面步骤的添加目录处补充添加std库的头文件,如“D:\android-ndk-r10d\sources\cxx-stl\gnu-libstdc++\4.9\include”。
“Invalid arguments ' Candidates are: void * memcpy(void *, const void *, ?) '”,在上面步骤的添加目录处补充添加预编译库的头文件,如“D:\android-ndk-r10d\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\lib\gcc\arm-linux-androideabi\4.9\include”。
“Type 'string' could not be resolved”,这个问题我找来找去也没有解决办法,看来追求完美主义也不是个容易的事。

代码示例

网上对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 1024struct 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; //socketunsigned 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 faultsprintf(str_mac, "%s%s", str_mac, NetbiosName);if (i != NumberOfNames-1) {sprintf(str_mac, "%s/", str_mac);}//依次读取netbios nameif (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;
}

点击下载本文用到的使用JNI接口的工程代码

点此查看Android开发笔记的完整目录

Android开发笔记(六十九)JNI实战相关推荐

  1. Android开发笔记(十九)底部标签栏TabBar

    底部标签页实现思路 现在的APP,大多在页面底部显示标签栏Tabbar,用于切换不同栏目的页面.Tabbar起源于iOS,iOS的Tabbar自动位于页面下方,可是Android搬过来的时候做了改动, ...

  2. android开发标签栏应该设置多少,Android开发笔记(十九)底部标签栏TabBar

    底部标签页实现思路 现在的APP,大多在页面底部显示标签栏Tabbar,用于切换不同栏目的页面.Tabbar起源于iOS,iOS的Tabbar自动位于页面下方,可是Android搬过来的时候做了改动, ...

  3. Android开发笔记(十六)秋千摇摆动画SwingAnimation

    上节博主介绍了AlphaAnimation和淡入淡出动画的使用,其实AlphaAnimation只是四种补间动画中的一种.那么为了加深对其他补间动画的理解,我想说说旋转动画RotateAnimatio ...

  4. Android开发笔记(十五)淡入淡出动画TransitionDrawable

    说到淡入淡出动画,可能大家会想到补间动画里面的AlphaAnimation,不过这个深浅动画只能对透明度做渐变效果,也就是只能对一个图形做深浅的颜色变换.如果我们想要从A图片逐渐变为B图片,也就是要实 ...

  5. Android开发笔记(九十九)圆形转盘

    圆形转盘的实现思想 圆形转盘的运用场景常见的有:抽奖转盘.圆形菜单列表.热点客户端环状列表等等.对于圆形转盘的编码实现,主要难点除了手势的触摸控制之外,就在于旋转角度的计算了.下面是旋转角度计算的解决 ...

  6. Android开发笔记(十八)书籍翻页动画PageAnimation

    前面几节的动画都算简单,本文就介绍一个复杂点的动画--书籍翻页动画.Android有自带的翻页动画ViewPager,不过ViewPager只实现了平移效果.即便使用补间组合动画或者属性动画,也只是把 ...

  7. Android开发笔记(十四)圆弧进度动画CircleAnimation

    一个好看的APP,都有不少精致的动画效果.熟练运用各种动画技术,可让我们的APP灼灼生辉.Android在技术上把动画分为了三类,分别是帧动画FrameAnimation.补间动画TweenAnima ...

  8. Android开发笔记(十二)测量尺寸与下拉刷新

    尺寸测量的配置 控件宽和高的设置方式 大家知道,自定义视图的目的就是要在屏幕上显示期望的图案,那在绘制图案之前,我们得先知道这个图案的尺寸(如宽多少高多少). 一般在xml中给控件的宽和高有三种赋值方 ...

  9. 【Visual C++】游戏开发笔记三十九 浅墨DirectX教程之七 他山之石:几种几何体的快捷绘制法

    本篇文章里,我们对Direct3D之中几种几何体的简洁绘制方法进行了详细的剖析,最后依旧是提供文章配套的详细注释的demo源代码的欣赏,并在文章末尾提供了源代码下载.(这标题有些歧义的,这个几种是修饰 ...

  10. Android学习笔记(十五)——实战:强制下线

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 实现强制下线功能的思路也比较简单,只需要在界面上弹出一个对话框, 让用户无法进行任何其他操作, 必须要点击对话 ...

最新文章

  1. oracle 命名空间 用户
  2. hdu5025 状态压缩广搜
  3. Estimathon
  4. nyoj 720 项目安排(dp+二分优化)
  5. c语言.h文件怎么写,关于C语言中.h文件怎么书写?
  6. mx250是什么_来看看联想小新Pro13 2020款和2019款哪个好?区别是什么?
  7. TypeSrcript如何引入第三方库 如果加d.ts以及async await如何使用 demo,只有代码,文字后续补充...
  8. 笔记《JavaScript 权威指南》(第6版) 分条知识点概要1—词法结构
  9. Adobe Premiere Pro 如何打开webm格式媒体
  10. 正方教务系统对服务器的要求,正方软件教务系统功能介绍.docx
  11. ScreenFlow 录制Mac电脑声音
  12. 计算机音乐数字乐谱青芒,弱水三千(戏腔付)
  13. JavaScript防流量劫持-前端安全
  14. xml 解析错误:语法错误 xml解析错误:找不到根元素
  15. linux系统文件复制过程时长,Linux系统I/O操作与零拷贝
  16. 执行引擎的工作过程、Java代码编译和执行的过程、解释器、JIT编译器
  17. d3d纹理过滤器配置
  18. 安徽赛区2022数学建模国赛获奖名单
  19. 通信应用中数字上变频DUC与数字下变频DDC详细原理(带图)
  20. html画布里增添颜色,在HTML5画布中更改笔触颜色

热门文章

  1. ‘utf-8‘ codec can‘t decode byte 0xb8 in position 0: invalid start byte
  2. GBDT、Xgboost、LightGBM对比,异同点,并行策略
  3. oracle 清除参数,IMp回去的时候要把原来的表的记录清空吗?没有什么参数可以省略这个吗...
  4. java api es_ES 常用java api
  5. mysql每天1点执行存储过程_一天一点MySQL复习——存储过程
  6. Eclipse如何关闭在RUN/DEBUG时弹出窗口?
  7. [Spring Boot核心功能]1. SpringApplication 启动引导类(1)
  8. PHP Linux安装
  9. outset边框html,CSS3 border-image-outset属性怎么用?
  10. sublime报错信息乱码_解决Sublime Text 3在GBK编码下的中文乱码问题