JNI,称为Java本地接口。它是翻译Java和C之间的中间件。
使用JNI的好处:
①可以进行驱动开发。
Java代码运行在虚拟机上,无法直接和操作系统交互,而C代码可以做驱动开发,就可以利用Java调C的方式来进行驱动开发。
②可以使用很多用C代码写好的函数库。(C代码年代久远,有很多可以复用的函数库)
③C代码运算效率高,调用C代码可以提高效率。
一,JNI开发的第一步,回顾下C语言
C和Java基本数据类型的对比:
可以通过sizeof来获得数据类型的长度。
类型的修饰符,对长度没有影响,对值的范围的有影响 。
signed 有符号位 第一位是符号位
unsigned 无符号位 第一位不是符号位 所有的数全部是正数
void 不确定长度 空
C语言常用的格式化符号:
%d 带符号的十进制数
%c 单个字符
%s 字符串
%f 6位小数
%x 16进制数
%#x 有0x的16进制数
C语言的指针:
int i=3;
int* j=&i;
*j=4;
&i,表示i这个变量在内存中的地址。
int*,表示的是存放内存地址的变量,也称为指针变量或指针。
*j,表示的是指针的运算符,可以找到j指针变量存放的内存地址所对应的值。
交换两个数,如果想要修改主函数变量的值,必须把变量的指针传递到子函数中。
C语言的数组:
①数组中的元素在一块连续的内存空间中
②数组中的变量名的值,就是第一个元素的内存地址。
void printArray(int* array,int len){
int i;//不能在for循环里声明变量
for(i=0;i<len;i++){
//指针可以自动识别要移动多少偏移,不用担心i的值。
printf("数组的第%d个元素为%d\n",i,*(array+i));
}
}
main(){
int array[]={1,2,3};
printArray2(array,3);//array数组中的第一个元素的内存地址
system("pause");
}
C语言的内存结构:
分为栈内存和堆内存。
栈内存:
栈内存从上到下分为
.data区 常量加载.data区
.code区 方法加载在.code区
栈内存
栈内存是连续的内存空间,它是系统自动分配的内存,可以自动回收,效率很高。但是大小是有限制的。
堆内存:
程序员手动去申请的内存在堆内存,在堆内存开辟空间,不会限制大小
(大小的上限取决于堆内存的剩余大小),但是堆内存中容易产生碎片。
会导致效率慢,需要程序员手动回收。
结构体和联合体:
结构体用于封装对象
//声明了一个结构体
struct Student
{
int age;
float score;
char sex;
double d;
};
联合体里的元素共享一块内存
//声明联合体
union mix
{
long i;
int k;
char ii;
} ;
二,NDK
NDK,本地的开发工具包。
NDK的作用,交叉编译,具体可以:
①把C语言写好的代码打包生成可以在手机上(linux平台)可以运行的文件。
②把C语言的函数库(.dll)打包生成手机下的(.so)文件。
JNI的协议规范:
找到NDK的安装目录下的
android-ndk-r9b\platforms\android-9\arch-arm\usr\include的jni.h文件。
jni.h开头有这么一段:
#ifdef HAVE_INTTYPES_H
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
如果HAVE_INTTYPES_H已经被define定义过,那么就为ifdef下的语句,否则执行else下的语句。
这些以"j"开头的别名,表示的是在java类型在jni中的别名。
typedef const struct JNINativeInterface* JNIEnv;
JNIEnv是JNINativeInterface结构体指针的别名。
而结构体JNINativeInterface中有很多可以使用的方法。
在android studio进行JNI的开发过程:
①在Activity中写一个native方法,native方法类似于抽象方法,会在C代码中去实现。
public native String helloFromeC();
//引入库
static{
System.loadLibrary("helloJni");
}
②利用javah命令生成.h头文件。生成头文件的原因是,在C代码中要写出能够匹配java方法的方法名实在费劲而且会很容易出错。而头文件里可以直接生成方法。
在cmd窗口,找到native方法对应的java文件。(由于我使用的是JDK1.7以上的JDK),进入到src/main/包名下,找到指定mActivity.java。
-classpath 要指定路径是包所在的根路径。
javah -classpath 包名.mActivity
③在module下的build.gradle的defaultConfig声明.so库的名称
ndk{
moduleName "helloJni"//指定库的名称
}
在local.properties中声明NDK的路径(指定NDK路径会自动添加)
ndk.dir=D\:\\sdk\\ndk-bundle
④创建jni目录,创建c代码,去写native方法的实现。
#include <jni.h>
#include <android/log.h>//引入log.h
//在C代码中使用LOG
#define LOG_TAG "====>"
//写一个宏定义的方法,把复杂的方法转换为简单的表示的方法
//ANDROID_LOG_DEBUG和ANDROID_LOG_INFO表示Log的类型,LOG_TAG表示过滤的条件,__VA_ARGS__表示不固定的参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__);
JNIEXPORT jstring JNICALL Java_asule_myjniandndk_MainActivity_helloFromeC(JNIEnv* env, jobject obj){
LOGD("ronaldo");
LOGI("hehe");
return (*env)->NewStringUTF(env,"hello");
}
那么现在一个简单的JNI开发过程就完成了。