android jni学习
来源:互联网 发布:网络教育 入学考试 编辑:程序博客网 时间:2024/06/10 17:10
现在很多app都不是仅仅的android sdk搭建而成的,都是混合式的居多,也就是说,一个android app,一方面包含有c或者c++的底层代码,一方面还有html5的网页,所以,拓展学习很有必要。
这篇文章,我们就说一下android怎么通过jni来和本地代码(C/C++)通信的。
这里,得先简单说一下NDK和JNI的区别;
(1)NDK是android的特性,JNI是java的特性;
(2)NDK是android一系列工具的集合,通过这些工具,一方面简化jni的编写,一方面简化我们把原生代码和java代码一起混合打包成为一个apk;JNI可以理解为一门语言,Java Native Interface java原生接口
这里,我们主要聊一下JNI的机制问题,至于NDK中的种种工具的用法,这里暂且不谈。
JNI原理
JVM和JNIENV
JVM是一个java虚拟机,JVM中可以有多个java线程,而一个线程对应与一个JNIENV,这些JNIENV保存在TLS中,而且是互相不干扰的。
java访问本地代码
1 通过loadLibrary导入本地代码的so包,这一个过程,把本地代码的函数映射到一个函数表里面;
2 在java端通过声明native方法,告知jvm该方法从本地代码中找寻;
3 jvm从步骤1中找寻相应的原生函数;
本地代码访问java
通过JVM,JVM把java代码隔绝到一个独立的环境中,造就了它的跨平台性,也导致了它和本地代码的通信更加困难,我们可以通过JNI提供的接口函数进行操作。
JNI数据传递
JNI本来就是做数据转化和传递的,可以把它理解为一个胶水,用来连接本地代码(c/c++)和java的接口,所以,我们本地代码工程师只需关注C/C++代码即可,然后通过JNI把C/C++编写好的函数进行转化,提供给android 的java层使用即可。
基本数据类型
我们知道,本地代码的基本数据类型和平台有关,如int,不一定为4个字节,而java的就恒定为4个字节,这对这种情况,JNI充当了个中介的角色,
引用类型
类,实例,所有的数组都是引用类型
JNI访问java成员
java中类成员和指静态属性和静态方法,它们属于类而不属于对象;而对象成员是指非静态属性和非静态方法,它们属于对象而非类,正因为如此,在JNI中访问类成员和对象成员是不一样的。
获取类名
如果java层传入JNI层对象,可以通过(*env)->GetObjectClass(env,obj)来获取该类,否则,通过(*env)->FindClass(env,".../.../...")获取,获取到jclass中。
获取属性ID和方法ID
JNI在jni.h中定义了jFielId和jMethodId用来表示java的属性和方法,其中方法如下:
// 根据属性签名返回clazz类中的该属性ID
jfieldIDGetFieldID(JNIEnv*env,jclass clazz, const char*name, const char *sig);
//如果获得静态属性ID,则调用下面的函数
jfieldIDGetStaticFieldID(JNIEnv*env,jclass clazz, constchar *name, const char *sig);
//根据方法签名返回clazz类中该方法ID
jmethodIDGetMethodID(JNIEnv*env,jclass clazz, const char*name, const char *sig);
//如果是静态方法,则调用下面的函数实现
jmethodIDGetStaticMethodID(JNIEnv*env,jclass clazz, constchar *name, const char *sig);
可见,属性和方法,静态和非静态JNI中都定义了不同的方法获取。其中参数说明,clazz表示该属性或方法所属的类,有前面获取类名得到(注意区分类和对象),name表示该属性或者方法名,sig表示该属性或者方法名的类型签名。JNI类型签名
上面提到,sig参数需要用到类型签名,为啥需要用到类型签名呢,源于java的面向对象的思想,java支持重载,即一个名字完全相同的函数,只要参数或者返回值不同,那也是不同的函数,所以JNI中通过这种方式来区别。
类型签名
Java类型
类型签名
Java类型
Z
boolean
[
[]
B
byte
[I
int[]
C
char
[F
float[]
S
short
[B
byte[]
I
int
[C
char[]
J
long
[S
short[]
F
float
[D
double[]
D
double
[J
long[]
L
类
[Z
boolean[]
V
void
这里需要注意以下几点:
1 类名以为L开头,别想当然的觉得是Long的签名;
2 类名以/分隔,(java的包名本来就是一层层的文件夹),以;结束,别忘了结束符号;
3 括号内()是参数,中间没有分隔,如果没有参数,留空,括号外为返回值 如()V;
JNI操作java属性或方法
前面取得了methodid之后,就可以根据这个id来来获取或者设置属性或方法了。
取得Java属性的JNI方法定义如下:
j<类型>Get<类型>Field(JNIEnv* env,jobject obj,jfieldID fieldID);
j<类型>Get Static<类型>Field(JNIEnv* env,jobject obj,jfieldID fieldID);
设置Java属性的JNI方法定义如下:
voidSet<类型>Field(JNIEnv* env,jobject obj, jfieldID fieldID, j<类型>val);
void SetStatic<类型>Field(JNIEnv* env,jobject obj, jfieldID fieldID, j<类型>val);
JNI提供了下面的方法用来调用Java方法:
// 调用Java成员方法
Call<Type>Method(JNIEnv* env,jobject obj,jmethodID methodID,...);
Call<Type>MethodV(JNIEnv* env,jobject clazz, jmethodIDmethodID,va_listargs);
Call<Type>tMethodA(JNIEnv* env,jobject clazz,jmethodID methodID,constjvalue *args);
// 调用Java静态方法
CallStatic<Type>Method(JNIEnv* env,jclass clazz,jmethodID methodID,...);
CallStatic<Type>MethodV(JNIEnv* env,jclass clazz,jmethodID methodID,va_listargs);
CallStatic<Type>tMethodA(JNIEnv* env,jclass clazz,jmethodID methodID,constjvalue *args);
传入env指针,类或者对象(非静态用类,静态用对象),方法id即可。这里简单说一下带V或A或不带的区别。最常用的是不带的,里面是一个可变参数,带V的传入的是以向量表形式传入参数,带A的表示以jvalue数组提供的列表。
在本地代码操作java对象
jobjectNewObject(JNIEnv* env,jclass clazz, jmethodIDmethodID,...);
jobjectNewObjectV(JNIEnv* env,jclass clazz,jmethodIDmethodID,va_list args);
jobjectNewObjectA(JNIEnv* env,jclassclazz, jmethodIDmethodID,const jvalue *args) ;
这里的方法id,是构造函数的方法id,所以创建一个java对象的简单步骤可为:1 获取到该类;
2 获取构造函数方法id,(*env)->GetMethodId(env,class,"<init>",()V),第三个参数为<init>或者类名
创建java对象
为了返回java环境的字符串,jni提供了相应的方法;
根据传入的宽字符串创建一个JavaString对象
jstringNewString(const jchar *unicode, jsizelen)
根据传入的UTF-8字符串创建一个JavaString对象
jstringNewStringUTF(const char *utf)
为了处理java传入的字符串String,也有类似的方法;将一个jstring对象,转换为(UTF-16)编码的宽字符串(jchar*):
const jchar*GetStringChars(jstring str,jboolean*isCopy)
将一个jstring对象,转换为(UTF-8)编码的字符串(char*):
const char*GetStringUTFChars(jstringstr,jboolean *isCopy)
isCopy可为NULL,JNI_FALSE,JNI_TRUE,为true表示在本地开辟内存,把该java 的string拷贝到该内存中,然后返回指向该内存的指针,为false表示不另外开辟内存,而是直接指向java中string的内存地址。NULL表示不关心是否拷贝。不管怎么样,在用完之后,都得释放该内存。RealeaseStringChars(jstringjstr,const jchar*str)
RealeaseStringUTFChars(jstringjstr,constchar* str)
Java数组在本地代码中的处理数组其实和属性差不多,不过它比较灵活,这里说一下几个要点
1 如果java未传入数组,我们可以先通过GetFieldId获取到该数组id,然后根据把该id传入GetObjectField中,返回值强转成为对应的array。
2 获取数组长度,GetArrayLength;
3 获取数组元素,GetObjectArrayElements,返回的是该数组的地址,可通过该地址操作数组;
4 NewObjectArray新建数组,可用于返回给java;
5 用完干数组元素记得释放 Realease<Type>ArrayElements;
最后补充一下c和java之间怎么传递json格式的数据。有经验的朋友知道,在c中是有用于封装和解封装json格式的库,这里我直接把源码拷过来用,里面把函数已经做了很好的封装,直接调用即可;
c把json格式传到java大概步骤如下:
1 在c中创建json格式 cJSON_CreateObject();
2 添加item cJSON_AddStringToObject
3 json格式转char* cJSON_Print
4 用完之后记得删掉该json cJSON_Delete;
java把json格式传到c大概步骤如下:
1 cJSON_Parse把char*的字符转为cJSON格式;
2 cJSON_GetObjectItem获取到java的json key,再转化即可。
其实就是库的使用,这里不再赘述,可看我的demo。
总结:把android jni的大概内容大概过了一遍,其中包括一些原理,类型转换等,知道这些简单的步骤,可以开始着手相关的工作,在工作中慢慢的磨合即可。当然,要做好android的本地代码部分,关键还是要有c/c++功底,可以找个教程简单复习一下。
我自己写了个demo,里面包含了大部分的数据转化,包含了json格式的转换等,在android studio的环境下编写,大家可下载看看。地址点击打开链接
- android JNI 学习笔记
- android JNI 学习笔记
- android jni 学习1
- android jni 学习笔记
- android JNI 学习笔记
- Android JNI 学习笔记
- Android Jni基础学习
- android JNI 学习笔记
- Android学习之 JNI
- android-----JNI学习 helloworld
- Android(Java):jni学习
- android-----JNI学习 helloworld
- android JNI学习
- android JNI学习一
- android JNI学习二
- android JNI学习三
- android JNI学习四
- android JNI学习五
- IIS是如何处理HTTP请求
- [leetcode]14. Longest Common Prefix
- Android判断当前系统使用的语言
- [leetcode] 422. Valid Word Square 解题报告
- 复习iOS动画-Transaction
- android jni学习
- socketpair理解
- 函数
- php中inclued、include_once和require、require_once
- 获取系统版本
- Oracle如何把数据库表迁移到指定表空间
- Java之CountDownLatch使用
- Hadoop的单机模式、伪分布式模式和完全分布式模式
- linux: 几个常用makefile模板