android 接口的调用方法参数类型转换,Android NDK开发之JNI基础
前言
之前写了一篇文章简单的介绍了Android NDK的组件和结构,以及在Android studio中开发NDK,NDK是Android底层的c/c++库,然而要在java中调用c/c++的原生功能,则需要使用JNI来实现。
什么是JNI
JNI(Java Native Interface)是java本地接口,它主要是为了实现Java调用c、c++等本地代码所封装的一层接口。大家都知道java是跨平台开发语言,它的狂平台特性导致与本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,所以Java提供了JNI用于和Native代码进行交互。通过JNI,Java可以调用c、c++,相反,c、c++也可以调用Java的相关代码。
创建NDK工程
开发环境
Mac
Android studio:3.3.2
新建工程
本地的Android studio版本为3.3.2,当你创建项目的时候有一个选项是选择Native C++的模板
点击next,配置项目的信息
点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可。
点击finish即可完成工程的创建。
工程结构
这时候主工程目录下会有cpp文件夹和.externalNativeBuild文件夹。
.externalNativeBuild文件夹:用于存放cmake编译好的文件,包括支持的各种硬件等信息,有点类似于build.gradle文件明确Gradle如何编译APP;
cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;
cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。
CMakeLists.txt的相关配置如下:
# 设置构建本地库所需的最小版本的cbuild。
cmake_minimum_required(VERSION 3.4.1)
# 创建并命名一个库,将其设置为静态
# 或者共享,并提供其源代码的相对路径。
# 您可以定义多个库,而cbuild为您构建它们。
# Gradle自动将共享库与你的APK打包。
add_library( native-lib #设置库的名称。即SO文件的名称,生产的so文件为“libnative-lib.so”, 在加载的时候“System.loadLibrary("native-lib");”
SHARED # 将库设置为共享库。
native-lib.cpp # 提供一个源文件的相对路径
helloJni.cpp # 提供同一个SO文件中的另一个源文件的相对路径
)
# 搜索指定的预构建库,并将该路径存储为一个变量。因为cbuild默认包含了搜索路径中的系统库,所以您只需要指定您想要添加的公共NDK库的名称。cbuild在完成构建之前验证这个库是否存在。
find_library(log-lib # 设置path变量的名称。
log # 指定NDK库的名称 你想让CMake来定位。
)
#指定库的库应该链接到你的目标库。您可以链接多个库,比如在这个构建脚本中定义的库、预构建的第三方库或系统库。
target_link_libraries( native-lib # 指定目标库中。与 add_library的库名称一定要相同
${log-lib} # 将目标库链接到日志库包含在NDK。
)
#如果需要生产多个SO文件的话,写法如下
add_library( natave-lib # 设置库的名称。另一个so文件的名称
SHARED # 将库设置为共享库。
nataveJni.cpp # 提供一个源文件的相对路径
)
target_link_libraries( natave-lib #指定目标库中。与 add_library的库名称一定要相同
${log-lib} # 将目标库链接到日志库包含在NDK。
)
build.gradle中有CMake的相关配置
代码结构
java调用c、c++代码分为三个步骤:
加载so库
编写java函数
编写c函数
在MainActivity.java,static{}语句中使用了加载so库,此语句在类加载中只执行一次。
static {
System.loadLibrary("native-lib");
}
然后,编写了原生的函数,函数名中要带有native。
public native String stringFromJNI();
最后,编写相对应的c函数,注意函数名的构成Java_com_example_myapplication_MainActivity_stringFromJNI为Java_加上包名、类型、方法名的下划线连成一起。
native-lib.cpp文件
#include
#include
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
这就是一个JNI方法调用示例。
虽然Java函数不带参数,但是原生方法却带了两个参数,第一个参数JNIEnv是指向可用JNI函数表的接口指针,第二个参数jobject是Java函数所在类的实例的Java对象引用。
JNIEnv接口指针
原生代码(c)通过JNIEnv接口指针提供的各种函数来使用虚拟机的功能,JNIEnv是一个指向线程-局部数据的指针,线程-局部数据中包含指向函数表的指针。
原生代码是c与原生代码是c++的调用JNI函数的语法不同,在c代码中,JNIEnv是指向JNINativeInterface结构的指针,而在c++代码中,JNIEnv是c++类实例,这两种方式调用函数的方式是不一样的。例如:
c代码中:
(*env)->NewStringUTF(env,"Hello from JNI");
c++代码中:
env->NewStringUTF("Hello from JNI");
实例方法与静态方法
Java程序设计有两类方法,实例方法和静态方法。实例方法与类实例相关,只能在类实例中调用。静态方法不与类死里相关,它们可以在静态上下文中直接调用。在原生代码中可以获取Java类的实例引用和类引用。例如:
类实例引用
extern "C" JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject thiz) {
}
类引用
extern "C" JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jclass clazz) {
}
从函数中看出来,JNI提供了自己的数据类型从而让原生代码了解Java数据类型。
JNI数据类型
JNI的数据类型包含两种:基本类型和引用类型。与Java数据类型的对应关系如下:
基本数据类型:
| JNI类型 | Java类型 |
| ------ | ------ |
| jboolean | boolean |
| jbyte | byte |
| jchar | char |
| jshort | short |
| jint | int |
| jlong | long |
| jfloat | float |
| jdouble | double |
| void | void |
引用类型:
| JNI类型 | Java类型 |
| ------ | ------ |
| jobject | Object |
| jclass | Class |
| jstring | String |
| jobjectArray | Object[] |
| jbooleanArray | boolean[] |
| jbyteArray | char[] |
| jshortArray | short[] |
| jintArray | int[] |
| jlongArray | long[] |
| jfloatArray | float[] |
| jdoubleArray | double[] |
| jthrowable | Throwable |
引用数据类型的操作
JNI提供了与引用类型密切相关的一组API,这些API通过JNIEnv接口指针提供给原生函数。例如:
字符串
数组
NIO缓冲区
字段
方法
字符串操作
JNI把Java字符串当成引用类型处理,提供了Java与c字符串之间相互转换的必要函数,由于Java字符串对象是不可变得,所以JNI不提供修改现有Java字符串内容的函数。
创建字符串
可以在原生代码中使用NewString函数构建Unicode编码格式的字符串实例,也可以中NewStringUTF函数构建UTF-8编码格式的字符串实例,这些函数以C字符串为参数,并返回一个Java字符串引用类型jstring值。例如:
jstring javaStr = (*env)->NewStringUTF(env,"Hello");
把Java字符串转换成C字符串
为了在原生代码中使用Java字符串,需要将Java字符串转换成C字符串。用GetStringChars函数可以将Unicode格式的Java字符串转换成C字符串,用GetStringUTFChars函数可以将UTF-8格式的Java字符串转换成C字符串。例如:
const jbyte* str
jboolean isCopy;
str = (*env)->GetStringUTFChars(env,javaString,&isCopy);
释放字符串
通过JNI GetStringChars函数和GetStringUTFChars函数获得的C字符串在原生代码中使用完后要释放,否则会引起内存泄漏。JNI提供了ReleaseStringChars函数和ReleaseStringUTFChars函数来释放Unicode编码和UTF-8编码格式的字符串。例如:
(*env)->ReleaseStringUTFChars(env,javaString,str);
数组操作
创建数组
用New"Type"Array函数在原生代码中创建数组实例,其中"Type"可以是Int、Char等类型,例如:
jintArray javaArray = (*env)->NewIntArray(env,10);
访问数组元素
将数组的代码复制成C数组或者让JNI提供直接指向数组元素的指针方式来访问Java数组元素。
对副本的操作
Get"Type"ArrayRegion函数将给定的基本Java数组复制到给定的C数组中,例如:
jint nativeArray[10];
(*env)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);
原生代码可以使用和修改数组元素,使用Set"Type"ArrayRegion函数将C数组复制回Java数组中。例如:
(*env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);
NIO操作
JNI提供了在原生代码中使用NIO的函数,与数组操作相比,NIO性能较好,更适合在原生代码和Java应用程序之间传送大量数据。
创建直接字节缓冲区
unsigned char* buffer = (unsigned char*) malloc(1024);
jobject directBuffer = (*env)->NewDirectByteBuffer(env,buffer,1024);
注意:原生函数应用通过释放未使用的内存分配以避免内存泄漏。
获取直接字节缓冲区
unsigned char* buffer;
buffer = (unsigned char*)(*env)->GetDirectBufferAddress(env,directBuffer);
访问域
Java有两类域:实例域和静态域,这两个的区别就是有没有static声明静态。
获取域ID
JNI提供了用域ID访问两类域的方法,可以通过给定实例的class对象获取域ID,用GetObjectClass函数来获取class对象。例如:
jclass clazz = (*env)->GetObjectClass(env,instance);
用GetFieldId函数来获取实例域。
jfieldId instanceFieldId = (*env)->GetFieldId(env,clazz,"instanceField","Ljava/lang/String");
用GetStaticFieldId获取静态域ID。
jfieldID staticFieldId = (*env)->GetStaticFieldID(env,clazz,"staticField","Ljava/lang/String");
其中最后一个参数是Java中表示域类型的域描述符,"Ljava/lang/String"表明域类型是String。
获取域
获得域ID之后可以用Get"Type"Field函数获取实际的实例域。例如:
jstring instanceField = (*env)->GetObjectField(env,instance,instanceFieldId);
用GetStatic"Type"Field函数获得静态域。例如:
jstring staticField = (*env)->GetStaticObjectField(env,clazz,staticFieldId);
调用方法
与域类似,Java中有两类方法:实例方法和静态方法。
获取方法ID
JNI提供了用方法ID访问两类方法的途径,可以用给定实例的class对象获取方法ID,用GetMethodID函数获得实例方法的方法ID。例如:
jmethodID instanceMethodId = (*env)->GetMethodID(env,clazz,"instanceMethod","()Ljava/lang/String;");
用GetStaticMethodID函数获得静态域的方法ID,例如:
jmethodID staticMethodId=(*env)->GetStaticMethodID(env,clazz,"staticMethod","()Ljava/lang/String;");
调用方法
以方法ID为参数通过Call"Type"Method类函数调用实际的实例方法。例如:
jstring instanceMethodResult = (*env)->CallStringMetthod(env,instance,instanceMethodId);
用CallStatic"Type"Field类函数调用静态方法,例如:
jstring staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,staticMethodId);
域和方法描述符
在上面获取域ID和方法ID均分别需要域描述符和方法描述符,域描述符和方法描述符可以通过下表Java类型签名映射获取:
| Java类型 | 签名|
| ------ | ------ |
| Boolean | Z |
| Byte | B |
| Char | C |
| Short | S |
| Long | J |
| Int | I |
| Float | F |
| Double | D |
| void | V |
| fully-qualified-class | Lfully-qualified-class |
| type[] | [type |
| method type | (arg-type)ret-type |
类的签名采用"L+包名+类名+;"的形式,将其中的.替换为/即可,比如java.lang.String,它的签名为Ljava/lang/String;数组的签名就是[+类型签名,比如int数组,签名就是[I,多维数组就是[[I。
方法的签名为(参数类型签名)+返回值类型签名,例如:boolean fun1(int a,double b,int[] c),其中参数类型的签名为ID[I,返回值类型的签名为Z,所以这个方法的签名就是(ID[I)Z。
android 接口的调用方法参数类型转换,Android NDK开发之JNI基础相关推荐
- Android NDK开发之 NEON基础介绍
原文:http://blog.csdn.net/app_12062011/article/details/50434259 Android NDK开发之 NEON基础介绍 这是官方介绍: http:/ ...
- android jni 调用java对象_Android NDK开发之Jni调用Java对象
本地代码中使用Java对象 通过使用合适的JNI函数,你可以创建Java对象,get.set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函 ...
- NDK 开发之 JNI 方法静态注册与动态注册
1 前言 上文说到,进行 NDK 开发的时候,我们首先需要把 Java 方法声明为 native,然后编写对应的 C/C++ 代码,并编译成为动态链接库,在调用 Java 方法前加载动态链接库即可调用 ...
- 第二代居民身份证阅读器GTICR-100(国腾)接口类调用方法
最近做了一个项目,客户需求要用第二代居民身份证阅读器GTICR-100(国腾)来读取用户的信息.我做的这个项目是C/S开发的(C# windowsForms),用到的一些参考资料以及这个国腾硬件驱动 ...
- 有道翻译多语言接口的调用方法(Python)
相关链接:3种谷歌多语言翻译接口的调用方法(Python)在日常的生活和工作中,我们经常会需要使用到翻译工具.在诸多翻译工具中,我个人更青睐谷歌翻译,因此在这里整理通过Python调用谷歌翻译接口的3 ...
- Android开发之JNI(一)--HelloWorld及遇到的错误解析
Android开发之JNI(一)--HelloWorld及遇到的错误解析 1.NDK环境搭建 參考http://blog.csdn.net/xiaoliouc/article/details/8705 ...
- Android NDK开发之旅31 FFmpeg音频解码
###前言 #####基于Android NDK开发之旅30--FFmpeg视频播放这篇文章,我们已经学会视频解码基本过程.这篇文章就对音频解码进行分析. #####音频解码和视频解码的套路基本是一样 ...
- android js接口调用方法,详解Android JS相互调用
最近在研究Android.JS相互调用,之前没怎么接触过,只知道loadUrl()就可以加载一个网页了,研究过之后发现Android可以调JS,JS也可以调Android原生控件,很开心啊.下面小编就 ...
- Android 接口的default 方法运行时报错AbstractMethodError
[问题描述:接口default方法AbstractMethodError] 记录一个Android项目中遇到的问题,我们通过exclude方式重写了一个依赖,改用本地的实现,其中一个接口的defaul ...
- android封装全局调用的toast_【Android】Android中WebView实现Java与JS交互
现在混合式开发是大趋势,H5不断蚕食移动互联网的份额,有的公司甚至只用H5就搞了一个APP,我们搞Android的不说会点H5,至少要懂怎么和H5(和JavaScript)交互,费话不多说. 一.先看 ...
最新文章
- 在Ubuntu14.04 64位上编译CMake源码操作步骤
- [Dnode]基于Node.js给浏览器提供异步远程方法调用
- tracepro应用实例详解_建筑安装工程造价,高清PPT图文详解,小白也能学会的简单步骤...
- cdrx4自动排版步骤_现在的大学生,都不会论文排版了
- 【Linux 内核 内存管理】Linux 内核内存布局 ④ ( ARM64 架构体系内存分布 | 内核启动源码 start_kernel | 内存初始化 mm_init | mem_init )
- c语言switch编写计算器,超级新手,用switch写了个计算器程序,求指导
- Linux 系统下显示文件内容(查看文件内容)的命令 more/less/cat/head/tail 比较
- 第二十期:黄金三步法 | 汇报时,如何让老板快速抓住重点?
- java基础—IO流——转换流的操作
- 开篇~试试word写博客
- 基于jsp+mysql+Spring+SpringMVC+mybatis的ssm学生网上请假系统
- 淘客发单机器人wztools_在淘客的迷茫中,拼多多给了我一丝光明
- SAM2195和SAM2695 和SAM5704硬音源设备在三四十年前MIDI技术刚刚起步之时
- css子元素和后代元素选择器
- winrar命令行加压解密
- pythonRuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing
- 深入了解一下C语言scanf()库函数
- 第四十一章 SQL命令 DROP VIEW
- java 去掉空行_java 去掉空行
- Mybatis-Plus如何使用
热门文章
- Codeforces 374A - Inna and Pink Pony
- 一行代码解决各种IE兼容问题,IE6,IE7,IE8,IE9,IE10 (转)
- Android创建并响应选项菜单
- 《嵌入式Linux基础教程》补充阅读建议电子数目下载
- 【转】Java中重载和重写的区别
- HCIE-RS面试---STP拓扑变化过程
- 战神背光键盘如何关系_苹果新专利:未来键盘或用彩色背光向用户提供有用的反馈...
- C/C++ 基础算法2
- PIM SM建立SPT树过程与实验
- OSPF区域间路由计算规则与实验