在 JNI 调用中,不仅仅 Java 可以调用本地方法,本地方法也可以调用 Java 中的方法和成员变量。

Java 中的类封装了属性和方法,想要访问 Java 中的属性和方法,首先要获得 Java 类或 Java 对象,然后再访问属性、调用方法。

在 Java 中类成员指静态属性和静态方法,它们属于类而不属于对象。而对象成员是指非静态属性和非静态方法,他们属于具体一个对象,不同的对象其成员是不同的,所以在本地代码中,对类成员的访问和对对象成员的访问是不同的。

1、获取 Java 类的两种方式

(1)通过传入JNI中的完整类名来获取类

// name:类全名,包含包名,包名间隔符用 “/”
jclass FindClass(const char *name); // JNI获得Android中的类并保存在jActivity中
jclass jcls = env->FindClass("com/aaron/link/LedNative");

(2)通过传入JNI中的一个java的对象来获取该对象的类

// obj: 引用类型
jclass GetObjectClass(jobject obj); // JNI获得引用obj所对应的类
jclass myCls = env->GetObjectClass(obj);

2、获取 Java 属性 ID 和方法 ID

在本地代码中要访问设置 Java 属性和方法,首先要在本地代码中取得代表该 Java 属性的 jfieldID 和代表该 Java 方法的 jmethodID,然后才能进行属性操作和方法调用。

// clazz:要取的成员对应的类
// name:要取的方法或者属性
// sig:要取的方法或属性的签名 // 根据属性签名返回 clazz 类中的该属性 ID
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig);
// 根据属性签名返回 clazz 类中的静态属性 ID
jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig);
// 根据方法签名返回 clazz 类中的该方法 ID
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig);
// 根据方法签名返回 clazz 类中的静态方法 ID
jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig);

举例:

Java代码

class MyClass {
private int mNumber;
private static String mName = "Aaron";
public MyClass() {
mNumber = 100;
} public void printNum() {
System.out.println("Number:" + mNumber);
} public static void printName() {
System.out.println("Name:" + mName);
}
} class NativeCallJava {
static {
System.loadLibrary("native_callback");
} private static native void callNative(MyClass cls); public static void main(String arg[]) {
callNative(new MyClass());
}
}

本地代码

void JNI_callNative(JNIEnv *env, jclass thiz, jobject obj)
{
// 获取对象对应的类
jclass myCls = env->GetObjectClass(obj);
// 获取属性
jfieldID mNumFieldID = env->GetFieldID(myCls, "mNumber", "I");
// 获取静态属性
jfieldID mNameFieldID = env->GetStaticFieldID(myCls, "mName", "java/lang/String");
// 获取方法
jmethodID printNumMethodID = env-GetMethodID(myCls, "printNum", "(V)V");
// 获取静态方法
jmethodID printNameMethodID = env-GetStaticMethodID(myCls, "printName", "(V)V");
}

 3、JNI 类型签名

Java 语言是面向对象的语言,支持重载机制,即允许多个具有相同的方法名不同的方法签名的方法存在。

不能只通过方法名明确的让 JNI 找到 Java 对应的方法,还要指定方法的签名,即参数列表和返回值类型。

JNI 签名类型
类型签名 Z B C S I J F D L V [ [I [F [B [C [S [D [J [Z
Java 类型 boolean byte char short int long float double void [] int[] float[] byte[] char[] short[] double[] long[] boolean[]

基本类型

以特定的单个大写字母表示

Java类类型

Java 类类型以 L 开头,以 “/” 分隔包名,在类名后加上 “;” 分割符,例如:String 的签名为:Ljava/lang/String

在 Java 中数组是引用类型,数组以 “[” 开头,后面跟数组元素类型签名,例如:int[] 的签名是 [I,对于二维数组,int[][] 签名是 [[I,object 数组签名就是 [Ljava/lang/Object

对于方法签名,在 JNI 中有特定的表示方式:(参数1类型签名参数2类型签名参数3类型签名... ...)返回值类型签名

注意:

(1)方法名在方法签名中没有体现出来。

(2)括号内表示参数列表,参数列表紧密相连,中间没有逗号,没有空格。

(3)返回值出现在括号后面。

(4)没有返回值也要加上 V 类型。

JNI 方法签名举例
Java 方法 JNI 方法签名
boolean isLedOn(void); (V)Z
void setLedOn(int ledNo); (I)V
String substr(String str, int idx, int count); (Ljava/lang/String;II)Ljava/lang/String
char fun(int n, String s, int[] value); (ILjava/lang/String;[I)C
boolean showMsg(android.View v, String msg); (Lanfroid/View;Ljava/lang/String;)Z

4、JNI 操作 Java 属性和方法

(1)获取、设置属性值和静态属性值

取得了代表属性和静态属性的 jfieldID,就可以使用 JNIEnv 中提供的方法来获取、设置属性值和静态属性值。

// <type>表示 Java 中的基本类型
// 获取属性值的 JNI 方法
j<type> Get<type>Field(jobject obj, jfieldID fieldID);
j<type> GetStatic<type>Field(jobject obj, jfieldID fieldID);
// 设置属性值的 JNI 方法
void Set<type>Field(jobject obj, jfieldID fieldID, j<type> val);
void SetStatic<type>Field(jobject obj, jfieldID fieldID, j<type> val);

(2)通过 JNI 调用 Java 中的方法

取得了代表方法的 jmethodID,就可以使用 JNIEnv 中提供的方法来调用 Java 中的方法。

// type 是这个方法的返回值类型,首字母大写
// 第一个参数代表调用的这个方法所属于的对象,或者这个静态方法所属的类。
// 第二个参数代表 jmethodID,后面的表示调用方法的参数列表,...表示变长参数。
// 调用 Java 成员方法
Call<type>Method(jobject obj, jmethodID method, ...);
// 调用 Java 静态成员方法
CallStatic<type>Method(jobject obj, jmethodID method, ...);

代码举例

// 静态方法不依赖于任何对象就可以进行访问
// 静态的直接通过类 myCls 来调用,非静态需要通过对象 obj 来调用
void JNI_callNative(JNIEnv *env, jclass thiz, jobject obj)
{
// 获取对象对应的类
jclass myCls = env->GetObjectClass(obj);
// 获取属性
jfieldID mNumFieldID = env->GetFieldID(myCls, "mNumber", "I");
// 获取静态属性
jfieldID mNameFieldID = env->GetStaticFieldID(myCls, "mName", "java/lang/String"); // 获取.设置 Java 成员的属性值
jint mNum = env->GetIntField(obj, mNumFieldID);
env->SetIntField(obj, mNumFieldID, mNum*); // 获取.设置 Java 静态属性值
jstring mName = (jstring)(env->GetStaticObjectField(myCls, mNameFieldID));
jstring newStr = env->NewStringUTF("Hello Native");
env->SetStaticObjectField(myCls, mNameFieldID, newStr); // 获取方法
jmethodID printNumMethodID = env-GetMethodID(myCls, "printNum", "(V)V");
// 获取静态方法
jmethodID printNameMethodID = env-GetStaticMethodID(myCls, "printName", "(V)V"); // 调用 MyClass 对象中的 printNum 方法
CallVoidMethod(obj, printNumMethodID);
// 调用 Myclass 类的静态 printName 方法
CallStaticVoidmethod(myCls, printNameMethodID);
}

5、在 JNI 中创建 Java 对象

(1)在 JNI 中创建 Java 对象

// JNIEnv 中创建 Java 对象的方法
// clazz:要创建的对象的类
// jmethodID:创建对象对应的构造方法ID
// 参数列表:...表示是变长参数,以“V”结尾的方法名表示向量表表示参数列表,以“A”结尾的方法名表示以 jvalue 数组提供参数列表
jobject NewObject(jclass clazz, jmethodID methodID, ...);
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
jobject NewObjectA(jclass clazz, imethodID methodID, const jvalue *args);

获得构造方法 ID 的方法 env->GetMethodID(clazz, method_name, sig) 中的第二个参数固定为类名(也可以用“<init>”代替类名),第三个参数和要调用的构造方法有关,默认的构造方法没有参数和返回值。

void JNI_callNativa(JNIEnv *env, jclass thiz, jobject obj)
{
jclass myCls = env->GetObjectClass(obj);
// 也可以通过完整类名获取
//jclass myCls = env->FindClass("com/test/native/MyClass");
// 获得 MyClass 的构造方法 ID
jmethodID myClassMethodID = env->GetMethodID(myCls, "MyClass", "(V)V");
// 创建 MyClass 对象
jobject newObj = NewObject(myCls, myClassMethodID);
}

(2)在 JNI 中创建 Java String 对象

在 Java 中,字符串 String 对象是 Unicode(UTF-16)编码,每个字符不论是中文还是英文还是符号,一个字符总是占用两个字节。在 C/C++ 中一个字符是一个字节,C/C++ 中的宽字符是两个字节的。

在本地 C/C++ 代码中我们可以通过一个宽字符串,或是一个 UTF-8 编码的字符串创建一个 Java 端的 String 对象。这种情况通常用于返回 Java 环境一个 String 返回值等场合。

// 根据传入的宽字符串创建一个 Java String 对象
jstring NewString(const jchar *unicode, jsize len);
// 根据传入的 UTF-8 字符串创建一个 Java String 对象
jstring NewStringUTF(const char *utf);

在 Java 中 String 类有很多对字符串进行操作的方法,在本地代码中通过 JNI 接口可以将 Java 的字符串转换到 C/C++ 的宽字符串(wchar_t*),或是传回一个 UTF-8 的字符串(char*)到 C/C++,在本地代码中操作。

// 在 Java 端有一个字符串 String str = "abcd"; ,在本地代码中取得并输出
void native_string_operation(JNIEnv *env, jobject obj)
{
// 取得该字符串的 jfieldID
jfieldID id_string = env->GetFieldID(env->GetObjectClass(obj), "str", "Ljava/lang/String");
// 取得该字符串,强制转换为 jstring 类型
jstring string = (jstring)(env->GetObjectField(obj, id_string));
printf("%s\n", string);
}

JNIEnv 提供了一系列的方法来操作字符串:

// str:传入一个指向 Java 中 String 对象的 jstring 引用
// isCopy:传入一个 jboolean 的指针,其值可以为 NULL/JNI_TRUE/JNI_FALSE
// JNI_TRUE:表示在本地开辟内存,然后把 Java 中的 String 复制到这个内存中,然后返回指向这个内存地址的指针
// JNI_FALSE:表示直接返回指向 Java 中 String 的内存指针,这时不要改变这个内存的内容,这将破坏 String 在 Java 中始终是常量的规则
// NULL:表示不关心是否复制字符串 // 将一个 jstring 对象,转换为(UTF-16)编码的宽字符串(jchar*)
const jchar *GetStringChars(jstring str, jboolean *isCopy);
// 将一个 jstring 对象,转换为(UTF-8)编码的宽字符串(char*)
const char *GetStringUTFChars(jstring str, jboolean *isCopy);

使用这两个方法取得的字符串,在不用的时候都要释放,分别对应下面连个方法。

// jstr:需要释放的本地字符串的资源
// str:需要释放的本地字符串
RealeaseStringChars(jstring jstr, const jchar *str);
RealeaseStringUTFChars(jstring jstr, const char *str);

6、在 JNI 中处理 Java 数组

可以使用 GetFieldID 获取一个 Java 数组变量的 ID,然后用 GetObjectField 取得该数组到本地方法,返回值为 jobject,然后可以强制转换为 j<type>Array 类型。

j<type>Array 类型是 JNI 定义的一个对象类型,它并不是 C/C++ 的数组,如 int[]等,所以要把 j<type>Array 转换为 C/C++ 中的数组来操作。

JNIEnv 定义了一系列的方法来把一个 j<type>Array 类型转换为 C/C++ 数组或把 C/C++ 数组转换为 j<type>Array。

(1)获取数组长度

jsize GetArrayLength(jarray array);

(2)对象类型数组操作

// len:新创建对象数组长度
// clazz:对象数组元素类型
// init:对象数组元素的初始值
// array:要操作的数组
// index:要操作数组元素的下标
// val:要设置的数组元素的值 // 创建对象数组
jobjectArray NewObjectArray(jsize len, jclass clazz, jobject init);
// 获得元素
jobject GetObjectArrayElement(jobjectArray array, jsize index);
// 设置元素
void SetObjectArrayElement(jobjectArray array, jsize index, jobject val);

JNI 没有提供直接把 Java 的对象类型数组(Object[])直接转到 C++ 中的 jobject[] 数组的方法,而是直接通过 Get/SetObjectArrayElement 这样的方法来对 Java 的 Object[] 数组进行操作。

(3)对基本数据类型数组的操作

// 获得指定类型的数组
j<type>* Get<type>ArrayElement(j<type>Array array, jboolean *isCopy);
// 释放数组
void Release<type>ArrayElements(j<type>Array array, j<type> *elems, jint mode);

这类函数可以把 Java 基本类型的数组转换到 C/C++ 中的数组。有两种处理方式,一是复制一份传回本地代码,另一种是把指向 Java 数组的指针直接传回到本地代码,处理完本地化的数组后,通过 Realease<type>ArrayElements 来释放数组。处理方式有 Get 方法的第二个参数 isCopy 来决定(取值为 JNI_TRUE 或 JNI_FALSE)。

第三个参数 mode 可以取下面的值:

<1> 0:对 Java 的数组进行更新并释放 C/C++ 的数组

<2> JNI_COMMIT:对 Java 的数组进行更新但是不释放 C/C++ 的数组

<3> JNI_ABORT:对 Java 的数组不进行更新,释放 C/C++ 的数组

Java:

class ArrayTest {
static {
System.loadLibrary("native_array");
} private int[] array = new int[]{1, 2, 3, 4, 5}; public native void show(); public static void main(String[] args) {
new ArrayTest().show();
}
}

JNI:

void JNI_Array_show(JNIEnv *env, jobject obj)
{
jfieldID id_array = env->GetFieldID(env->GetObjectClass(obj), "array", "[I");
jintArray arr = (jintArray)(env->GetObjectField(obj, id_array));
jint *int_arr = env->GetIntArrayElements(arr, NULL);
jsize len = env->GetArrayLength(arr); for(int i; i<len; i++)
cout << int_arr[i] << endl; env->ReleaseIntArrayElements(arr, int_arr, JNI_ABORT);
}

Android JNI访问Java成员的更多相关文章

  1. Android 通过 JNI 访问 Java 字段和方法调用

    在前面的两篇文章中,介绍了 Android 通过 JNI 进行基础类型.字符串和数组的相关操作,并描述了 Java 和 Native 在类型和签名之间的转换关系. 有了之前那些基础,就可以实现 Jav ...

  2. [转]ANDROID JNI之JAVA域与c域的互操作

    本文讲述AndroidJava域与C域互操作:Java域调用c域的函数:c域访问Java域的属性和方法:c域生成的对象的保存与使用.重点讲解c域如何访问Java域. 虽然AndroidJNI实现中,c ...

  3. Android JNI之JAVA与C++对象建立对称关联(JNI优化设计,确保JNI调用的稳定性)

    转载请声明:原文转自:http://www.cnblogs.com/xiezie/p/5930503.html Android JNI之JAVA与C++对象建立对称关联 1.JAVA对象持有C++对象 ...

  4. Android JNI之JAVA调用C/C++层

    转载请声明:原文转自:http://www.cnblogs.com/xiezie/p/5929996.html 一.java调用本地函数的开发步骤: 1.编写本地方法的类(可以说是用来叙述本地方法的类 ...

  5. android jni与java之间数据传输时怎么转换

    1.c中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char*. Java 类型 本地c类型 说明 boolean jboolean 无符号 8 位 b ...

  6. Android浏览器访问java web的方法

    以前自己也做过Android程序,可以和服务器通信,通过json来存取数据,当时是在APP中直接存取数据的,而这次我打算在手机浏览器中获得服务器传过来的Json参数,后来才发现其实很简单的,首先需要手 ...

  7. Android Jni 调用

    Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:j ...

  8. Android jni 编程(参数的传递,成员,方法的)相互访问

    package com.test.androidjni; import android.app.Activity; import android.os.Bundle; import android.u ...

  9. Android Jni(Java Native Interface)笔记

    首先记录一个问题,关于如何用javah生成头文件. 为什么要生成头文件?在含有 static{ System.loadLibrary("hellojni"); } 这样代码的类下面 ...

随机推荐

  1. rabbitmq&&erlang 安装

    # yum install epel-release CentOS and Red Hat Enterprise Linux 6.x wget https://dl.fedoraproject.org ...

  2. iOS-不用微信SDK唤起微信支付

    作者:TianBai 原文链接:http://www.jianshu.com/p/8930b4496023 要想知道微信SDK是如何调起微信客户端,那么咱们先看看微信SDK到底做了什么 前期准备 接入 ...

  3. 文件上传及时显示, 前端js和后端php相互结合使用

    文件读取 javascript 绑定文件上传变化事件 onchange 利用window对象 FileReader 调用方法 readerAsDataURL onload 方法 异步读取 属性:fil ...

  4. springmvc日期格式化

    jsp页面String类型转Controller后台Date类型 方法1.在实体中加入日期格式化注解 @DateTimeFormat(pattern="yyyy-MM-dd") p ...

  5. HDU 3473 Minimum Sum 划分树,数据结构 难度:1

    http://acm.hdu.edu.cn/showproblem.php?pid=3473 划分树模板题目,需要注意的是划分树的k是由1开始的 划分树: 参考:http://blog.csdn.ne ...

  6. java语言基础-变量

    一丶变量的基本概念 1.什么是变量 (1).内存中的一个存储区域 (2).该区域有自己的名称(变量名),和类型(数据类型) (3.)该区域的数据可以在同一类型范围内不断变化(定义变量的主要目的是因为数 ...

  7. gradle Could not create service of type CrossBuildFileHashCache using BuildSessionScopeServices.crea

    gradle Could not create service of type CrossBuildFileHashCache using BuildSessionScopeServices.crea ...

  8. java入门学习(4)— 类,对象理解,如何创建类,对象

    1.什么是类?具有一定相同的属性的对象的集合就叫类.2.对象:类的具体实例,就是类的实例化.比如学生是一个类(student),那学生里面的小红就是一个对象,一个有学生的属性的对象.3.如何定义一个类 ...

  9. 高德地图 Android编程中 如何设置使 标记 marker 能够被拖拽

    由于本人对智能手机真心的不太会用,我本人大概是不到3年前才买的智能手机,用以前的索尼爱立信手机比较方便小巧,平时学习工作打个电话发个短信也就够了,出去吃饭一般都是朋友拿手机去弄什么美团团购啥的,然后我 ...

  10. 【Python爬虫学习笔记(3)】Beautiful Soup库相关知识点总结

    1. Beautiful Soup简介     Beautiful Soup是将数据从HTML和XML文件中解析出来的一个python库,它能够提供一种符合习惯的方法去遍历搜索和修改解析树,这将大大减 ...