JNI开发中,你需要知道的一些建议- https://www.jianshu.com/p/09effb7ecde4
JNI中的方法签名;JNI反射调用Java构造方法、成员方法和静态方法。
> 深入理解JNI- https://blog.csdn.net/xsf50717/article/details/51598748
JNI/Native(so文件):
Java程序中的函数可以调用Native语言写的函数,Native一般是指C/C++编写的函数;
Native程序中的函数可以调用Java层的函数,也就是说C/C++程序可以调用Java函数(反射)。
通过JNI可以将底层Native世界和java世界联系起来。
JNI的基本数据类型和引用类型。
基本数据类型:jboolean jbyte jchar jshort jint jlong jfloat jdouble
引用类型:jobject jclass jstring jobjectArray jbooleanArray jbyteArray jthrowable
JNI中提供三种类型的引用来解决垃圾回收问题:
1.Local Reference:本地引用,一旦JNI层函数返回,这些jobject就可能被垃圾回收
2.Global Reference:全局引用,不主动释放,永远不会被回收
3.Weak Global Reference:弱全局引用,在运行过程中可能会被垃圾回收,因此在使用之前,需要调用JNIEnv的isSameObject判断是否被回收
JNI签名:
如在[MedaiScanner.java]processFile函数定义
private native void processFile(String path, String mimeType, MediaScannerClient client);`
对应的JNI函数签名是
(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
在JNI中除了基本类型数组、Class、String和Throwable外其余所有Java对象的数据类型在JNI中都用jobject表示。
操作jobject的本质就是操作这些对象的成员变量和成员函数。在JNI中使用jfieldID和jmethodID来表示Java类的成员变量和成员函数。
-- JNI开发学习之C反射调用java方法,JNI中的反射原理
JNI C反射调用java方法- https://www.jianshu.com/p/4893848a3249
JNI 之C反射调用Java- https://github.com/maoqitian/CcallJava
> JNI签名
为什么会有方法签名这种东西呢?这是因为Java这边支持函数重载,即虽然参数不一样,但是方法名一样,那么在JNI层它们的方法名都会是一样的,那JNI也会犯迷糊了,得找哪个呢?
不过也正是因为其参数类型是不一样的,所以就出现了方法签名,利用方法签名和方法名来唯一确定一个JNI函数的调用。
既然方法签名是基于参数类型的不同而形成的,首先要知道Java各数据类型对应的签名是什么,也就是所谓的类型签名,
在jni.h文件中就已经定义了这样一套规则,如下:
“函数签名”在Android NDK开发中很常见,由于Java支持重载,仅靠函数名无法唯一确定一个方法。因此,JNI提供了一套签名规则,用一个字符串来唯一确定一个Java端定义的Native方法。
具体每一种Java数据类型对应的签名字符串如下所示(来自Oracle官网JNI的介绍):
原理其实并不复杂,每种基本类型对应一个单字符签名,而类则对应为"L"+类的全路径+";",数组类型则对应"["+元素类型的签名,函数的签名则是:(各参数类型签名)+ 返回类型的签名。
搞清楚了基本原理,我们就可以尝试自定义一个Java工具类,为Java的Native函数生成签名字符串了,具体代码如下:
/*
* COPYRIGHT NOTICE
* Copyright (C) 2014, ticktick <lujun.hust@gmail.com>
* http://www.linuxidc.com/
*
* @license under the Apache License, Version 2.0
*
* @file SignatureGen.java
* @brief Implement a java class for jni signature generate
*
* @version 1.0
* @author ticktick
* @date 2014/12/15
*
*/
package com.ticktick.library;
import java.util.HashMap;
public class SignatureGen {
public static final HashMap<String,String> Primitives = new HashMap<String, String>();
static {
Primitives.put(Void.class.getName(),"V");
Primitives.put(Boolean.class.getName(),"Z");
Primitives.put(Byte.class.getName(),"B");
Primitives.put(Character.class.getName(),"C");
Primitives.put(Short.class.getName(),"S");
Primitives.put(Integer.class.getName(),"I");
Primitives.put(Long.class.getName(),"J");
Primitives.put(Float.class.getName(),"F");
Primitives.put(Double.class.getName(),"D");
}
public static String getSignature( Class ret, Class...params ) {
StringBuilder builder = new StringBuilder();
builder.append("(");
for( Class param : params ) {
builder.append(getSignature(param));
}
builder.append(")");
builder.append(getSignature(ret));
return builder.toString();
}
protected static String getSignature( Class param ) {
StringBuilder builder = new StringBuilder();
String name = "";
if( param.isArray() ) {
name = param.getComponentType().getName();
builder.append("[");
}
else {
name = param.getName();
}
if( Primitives.containsKey(name) ) {
builder.append(Primitives.get(name));
}
else {
builder.append("L"+name.replace(".","/")+";");
}
return builder.toString();
}
}
该SignatureGen类提供一个支持变参的函数getSignature来获取一个Java函数的签名字符串,第一个参数为函数返回值类型的class对象,变参为每一个函数参数类型的class对象。
具体用法示例如下,打印出不同类型的函数的签名字符串。
Log.d("Signature","void func() --> " + SignatureGen.getSignature(Void.class));
Log.d("Signature","boolean func() --> " + SignatureGen.getSignature(Boolean.class));
Log.d("Signature","int func(boolean a) --> " + SignatureGen.getSignature(Integer.class,Boolean.class));
Log.d("Signature","int func(boolean a,String b) --> " + SignatureGen.getSignature(Integer.class,Boolean.class,String.class));
Log.d("Signature","int func(byte[] c) --> " + SignatureGen.getSignature(Integer.class,Byte[].class));
Log.d("Signature","long func(int n,String str,int arr) -->" + SignatureGen.getSignature(Long.class,Integer.class,String.class,Integer[].class));
输出结果截屏如下:
关于JNI函数签名生成器就介绍到这儿了,原理并不复杂所以我也没有进行过多的分析,希望这个工具类能够在大家今后的项目中派上用场,有任何疑问欢迎留言或者来信lujun.hust@gmail.com交流。
--------------
有了数据类型之间的对应关系,JNI就可以正确识别并转换Java类型。那JNI又是如何识别Java的方法呢?
Java支持方法重载,仅靠函数名是无法唯一确定一个方法的。于是JNI提供了一套签名规则,用一个字符串来唯一确定一个方法。其规则如下:
(参数1类型签名参数2类型签名……参数n类型签名)返回值类型签名
以上签名字符串之间均没有空格。
类型签名又有一些规则,如表2-3所示。
表2-3 JNI类型签名规则
(续)
注意 类的签名规则是:“L+全限定类名+;” 三部分组成,其中全限定类名以”/”分隔,而不是用“.”或者“_”分隔。
例如,Java 方法:
- long fun (int n, String str, int[] arr);
其方法签名:
- (ILjava/lang/String;[I)J
括号里面的内容分成三部分,之间没有空格,即“I”、“Ljava/lang/String;”和“[I”,分别代表 int、String和int[]。括号外面是返回值类型签名,J代表long型。
回到Log系统的例子,JNINativeMethod结构体中第二个元素便是方法签名信息, 代码如下:
- static JNINativeMethod gMethods[] = {
- { "isLoggable", "(Ljava/lang/String;I)Z",(void*)
- android_util_Log_isLoggable },
- { "println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)
- android_util_Log_println_native },
- };
可以看出isLoggable函数有两个参数,一个是String类型,另一个是int类型,返回值为boolean类型。
至此,我们已经可以正确识别类型信息和函数信息。可如何操作对象并访问它们的成员变量和方法呢?下一节继续介绍。