JNI常用知识点总结

来源:互联网 发布:java的框架 编辑:程序博客网 时间:2024/04/29 21:54

JNI是什么

java native interface (java本地接口)
JNI是一个协议,用来沟通Java代码和外部的本地代码(C/C++),通过这个协议,java代码就可以调用外部的c/c++代码;

外部的c/c++也可以调用java代码完成两种语言之间的沟通和交流

为什么要使用JNI

  1. 市场需求
  2. 让java代码和底层代码之间互相调用
    • java调用底层特殊硬件(调用c语言,车载电脑)
    • 效率上c/c++语言效率更高(时间和内存要求严格的场景)
    • 复用已经存在的c代码, c语言发展了几十年有很多优秀的代码库(ffmpeg,opencv,7zip)
    • java反编译非常容易.c语言反编译不容易.关键业务逻辑需要用c实现.
    • 历史遗留问题,复用原来pc端的c代码

如何使用JNI

  • 1.熟悉java语言
  • 2.熟悉c语言
  • 3.JNI的规范、NDK

先来回顾一下C语言的知识点

数据类型

java的数据类型
  • int 4个byte
  • short 2个byte
  • byte 1个byte
  • long 8个byte
  • double 8个byte
  • float 4个byte
  • char 2个byte
  • boolean 1个byte
c语言的数据类型
  • char 的长度为1个byte
  • int 的长度为4个byte
  • float 的长度为4个byte
  • double 的长度为8个byte
  • long 的长度为4个byte
  • short 的长度为2个byte

总结:

  • c语言中的char可以用java的byte代替
  • c语言中的int ,float , double ,short 和java完全一样,可以相互代替
  • c语言的long ,用java的int代替
  • java中的byte和boolean类型,c语言里面没有.
  • java中的byte可以用c语言中的char表示
  • java中的boolean类型,c语言用0 false和非0 true
  • java中的long类型,c语言用 long long类型表示.

输入输出函数

输出:
  • %d—-int
  • %ld—long int
  • %c—-char
  • %f—-float
  • %u—-无符号数
  • %hd—短整形
  • %lf—double
  • %x—-十六进制输出int或long或short
  • %o—-八进制输出
  • %s—-字符串
从键盘输入int len;Scanf("%d",&len);

什么是指针

指针就是一个地址,地址就是一个指针

指针变量:用来存放一个地址的变量.用来存储某种数据在内存中的地址.

世面上书籍一般把指针和指针变量的概念混在一起了.

指针的重要性

  • 可以直接访问硬件
  • 快速传递数据(指针表示地址)
  • 返回一个以上的值(返回一个数组或结构体的指针)
  • 表示复杂的数据结构(结构体)
  • 方便处理字符串

*号的三种含义

* 号的第一种含义表示的是乘法操作.

int i = 3;int j = 5;i * j相乘

* 号的第二种含义

如果一个星号是在一种数据类型的后面代表的就是这种数据类型的指针变量,用来存放这种数据类型在内存中的地址.int i = 3;int*  p; 可以存放i的地址p=&i;

* 号的第三种含义

如果星号在一个指针变量的前面代表的就是把这个地址里面的数据取出来.int*  p;    *p; 把p地址里面的数据取出来
  • 每种数据类型的地址,只能用当前数据类型的指针变量来表示
  • C语言中编译是自上而下的,子函数需要写在main()函数的上面。

值传递和引用传递

准确的讲所有的语言,只有值传递.
如果传递的值是一个地址,通过这个地址可以找到地址对应的引用.
这个值传递可以理解为引用传递.
在java语言中对象实际上存放在某个内存地址里面,传递对象就相当于传递的是内存地址(引用传递)

多级指针用来存指针变量的地址

数组

  • 一块连续的内存空间
  • 数组名其实就是数组的内存空间的首地址
  • 每个元素占据多少个byte的内存空间,跟数组的数据类型有关
  • 所有指针变量在内存中的长度都是一样的,不同的指针类型,为了方便指针做运算
  • 指针的运算,只能运用到连续的内存空间(数组)

内存分配

动态内存:动态内存分配 需要申明头文件:malloc 全称:memory allocate
申明头文件:#include<malloc.h>int* p=malloc(sizeof(int)*10); //一个int4个byte,申请存放10个int类型的内存free(p);  //释放内存
静态内存
int i=3; //静态内存,栈空间申请一空区域   生命周期是函数体结束int arr[20];  //静态的在栈空间申请一块区域,大小能存放20个int
动态内存和静态内存的比较:

静态内存是系统程序编译执行后系统自动分配,由系统自动释放,静态内存是栈分配的,动态内存是堆分配的。

结构体的使用

函数的指针
  • 1、定义int (*pf)(int x,int y)
  • 2、赋值pf=add;
  • 3、引用pf(3,5)
#include<stdio.h>#include<stdlib.h>void  (*pf)();//声明函数的指针 //结构体里面不可以定义方法,只能声明方法struct student{int age;float score;char sex;void  (*study)();};void study(){printf("good good study,good good up!\n");} int main(void){struct student st={20,77.98f,'F',study};printf("学生的年龄:%d\n",st.age);printf("结构体的长度:%d\n",sizeof(st));st.study();system("pause");}
或者这样写
//  st.study();struct student* p=&st;(*p).study();
或者
p->study();

联合体:联合体的长度大小永远与数据类型最大的一致

#include <stdio.h> main() {    union { short i; short k; char ii; long long l;} mix;    printf("mix:%d\n",sizeof(mix));    mix.l = 2132141241;   printf("mix.i=%hd\n",mix.i);   system("pause");} 

枚举的用法

#include <stdio.h>    enum WeekDay{Monday=1,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};   //赋值为1,则从1到7main(void){    enum WeekDay day = Friday;    printf("%d\n",day);    system("pause");}

自定义数据类型

typedef int haha;typedef long gaga;main(){   haha i = 3;         gaga ga = 5;   printf("%d\n",i);   system("pause");
}

现在来介绍下jni技术:

常见概念

交叉编译

在一个平台上编译出来另外一个平台可以运行的二进制代码.
不同的操作系统(windows, mac os,linux),不同的处理器平台(x86,arm,MIPS)

交叉编译的原理

源代码—>编译—->链接—->可执行性程序

模拟另外一种平台的cpu和操作系统的特性.

交叉编译的工具链

一大堆工具,放在一起,链式调用,工具链

常见工具

  • ndk : native develop kits 本地开发工具集(交叉编译工具链)
  • cdt : c/c++ develop tools c和c++开发工具 (eclipse的一个插件) 语法的高亮显示
  • Cygwin : windows下一个linux模拟器(了解)

NDK的目录结构

  • docs : 开发文档
  • build: linux下编译的批处理命令
  • platform : 某种平台下编译需要的头文件和函数库
  • prebuild : 预编译的工具
  • sample: 实例代码
  • sources : 一些工具链的源码
  • toolschains: 工具链
  • ndk-build.cmd: ndk编译的命令脚本

jni开发步骤:

1、先在java代码中声明一个native本地函数
public native int add(int x,int y);//字符串类型的函数public native String helloFromC();
2、在工程目录下建立一个文件夹名称必须叫jni3、在jni文件夹下建立一个.c文件4、现在可以在文件夹下书写C语言代码了
//先定义头文件#include<stdio.h>#include<stdlib.h>#include<jni.h>//必须严格按照要求书写函数的签名//env是虚拟机的环境 obj调用者对象,如果有形参,就在后面加上形参;前面两个参数所有函数都要加上jint Java_包名_类名_方法名(JNIEnv* env,jobject obj,jint x,jint y){    return x+y;}//env是虚拟机的环境 obj调用者对象,如果有形参,就在后面加上形参jstring Java_包名_类名_方法名(JNIEnv* env,jobject obj){    //使用env环境指向的结构体,有自带的函数,利用jvm的函数进行操作    char* arr=("hello from c!");    return (*(*env)).NewStringUTF(env,arr);    //也可以使用下面的方法返回值    return (*env)->NewStringUTF(env,arr);}
5、编译.c文件
首先在jni目录下配置c代码编译的脚本文件 Android.mk文件,里面的例子如下:    LOCAL_PATH := $(call my-dir)    include $(CLEAR_VARS)    LOCAL_MODULE    := hello //编译后的模块名    LOCAL_SRC_FILES := Hello.c //编译的源文件的名称    include $(BUILD_SHARED_LIBRARY)
6、调用ndk指令编译代码
首先要配置androoid-ndk-r9b的环境变量,到jni目录下调用ndk-build.cmd
7、 生成一个.so的文件 ( c代码编译出来的二进制可执行文件)
.so为动态函数库、.a为静态函数库,静态函数库体积大存放在libs包下的armeabi文件下
8、在java代码里面写静态代码块,加载.so文件只写名字,前缀和后缀不写
    static{    System.loadLibrary("hello");    }
9、 像使用一般java方法一样调用native的方法.
String result="3+5="+add(3+5);  String result1=helloFromC();

jni开发的常见错误

10-22 02:18:35.672: E/AndroidRuntime(1552): Caused by: java.lang.UnsatisfiedLinkError: Couldn’t load hello: findLibrary returned null

  • 如果处理器平台不匹配,返回的lib就是空

在jni目录下新建Application.mk文件中编写
这个能在所有的平台运行

APP_ABI := all 
  • 检查lib的名字是否拼写错误

10-22 02:24:50.418: E/AndroidRuntime(1696): Caused by: java.lang.UnsatisfiedLinkError: Native method not found: com.itheima.hello2.MainActivity.add:(II)I

  • 检查c语言里面编写的方法名是否符合规范 Java_包名_类名_方法名(参数)

使用java中命令行的javah可以生成类的头文件,直接可以得到函数的签名。

  • 到工程的bin目录下进入classes中有一个com包,同级下,按住Ctrl+shift右击鼠标打开命令行,执行下面命令

    javah com.包名.类名 //自动生成一个头文件.h,复制到jni下,在里面可以找到函数签名,直接复制,加上参数和逻辑代码的编写就可以

  • 如果上面的方法出现错误:无法访问android.app.MainActivity,那么就切换到工程的sre->com包下执行上述命令就可以生成所需的头文件了

现在有个集成的工具

不过不太稳定,配置ndk;可以自动生成jni环境的文件,在jni文件下的.c文件夹中,我们只要加上函数签名,书写c语言代码,在java中调用c语言

掌握要点

  • 把基本类型的数据传递给c语言.
  • String 字符串 传递给c语言(工具方法 jstring2cstr)
  • 传递java数组给c语言
    *
把java中的String转换成c语言的的字符串数组(已成为一种模板)
//env虚拟机的环境//jstr 要转换的java字符串char* Jstring2cstr(JNIEnv* env,jstring jstr){    char* rtn=NULL;    jclass clsstring=(*env)->FindClass(env,"java/lang/String");    jstring strencode=(*env)->NewStringUTF(env,"GB2312");    jmethodID mid=(*env)->GetMethodID(env,clsstring,"getBytes","(Ljava/lang/String;)[B");    jbyteArray barr=(jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode);    jsize alen=(*env)->GetArrayLength(env,barr);    jbyte* ba=(*env)->GetByteArrayElements(env,barr,JNI_FALSE);    if(alen>0){        rtn=(char*)malloc(alen+1);        memcpy(rtn,ba,alen);        rtn[alen]=0;    }    (*env)->ReleaseByteArrayElements(env,barr,ba,0);    return rtn;}
在C语言中修改java中int型的数组如下:
 JNIEXPORT void JNICALL Java_com_包名_类名_方法名(JNIEnv* env,jobject obj,jintArray jintarr){    //jsize  (*GetArrayLength)(JNIEnv*,jarray);    //得到数组长度    int len=(*env)->GetArrayLength(env,jintarr);    //jint*   (*GetIntArrayElements)(JNIEnv*,jintArray,boolean*);       //得到数组的每个元素    jint* cintarr=(*env)->GetIntArrayElements(env,jintarr,0);    int i;    for(i=0;i<len;i++){        *(cintarr+i)+=10;    }}
修改其它类型的数组也可以:(*env)->Get…..案例银行加密小案例、字符串加密解密案例、调用美图秀秀JNI的美化方法。

c代码回调java

  • 复用已经存在的java代码
  • c语言需要给java一些通知
  • c代码不方便实现的逻辑

回顾一下反射

    //1.把字节码装载进来    Class clazz = Demo.class.getClassLoader().loadClass("Dialog");    //2.查询对话框里面的方法    Method method = clazz.getDeclaredMethod("showDialog", String.class);    //3.调用方法    method.invoke(clazz.newInstance(), "哈哈嘎嘎嘎");

举个例子:c语言中调用java语言弹出一个对话框

先在java中实现对话框的方法
/** * 显示对话框 * @param msg 对话框的消息  */public void showDialog(final String msg){    runOnUiThread(new Runnable() {        @Override        public void run() {            if(dialog!=null){                dialog.dismiss();                dialog = null;            }             dialog = new ProgressDialog(MainActivity.this);            dialog.setTitle("提醒");            dialog.setMessage(msg);            dialog.show();        }    });}
先在c语言中实现方法:三步骤
void showJavaDialog(JNIEnv*   env,jobject obj,char* cstr){    //jclass      (*FindClass)(JNIEnv*, const char*);    //1.查找字节码jclass  jclazz = (*env)->FindClass(env,"com/itheima/alipay/MainActivity");    //2.查找方法id    //  jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jmethodID methodid = (*env)->GetMethodID(env,jclazz,"showDialog","(Ljava/   lang/String;)V");    //3.调用方法    //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);(*env)->CallVoidMethod(env,obj,methodid,(*env)->NewStringUTF(env,cstr));}
最后直接在C中调用方法即可
showJavaDialog(env,obj,"正在加密用户名");sleep(2);showJavaDialog(env,obj,"正在加密密码");sleep(2);showJavaDialog(env,obj,"检查安全支付的环境");sleep(2);showJavaDialog(env,obj,"正在连接淘宝支付服务器...");sleep(2);showJavaDialog(env,obj,"等待服务器反馈数据..");sleep(2);dismissJavaDialog(env,obj);
在命令行中执行
adb shellps //查看进程kill 1334  //杀死进程号为1334的进程

实际开发的流程

  1. java程序员先去定义native的方法.
  2. jni程序员根据native的方法生成函数的签名
  3. 找个c工程师 给我添代码.
  4. 配置编译环境,编译调用

1、原来做pc端,window、phone。

2、移植一个Android版本。

3、c代码已经很久以前都已经写好的。

4、自已去包装一个c代码,通过jni调用(只关心接收的值、函数名、返回值)

0 0
原创粉丝点击