JNI 调用 JAVA 接口

介绍

JNI 是本地语言编程接口。它允许运行在JVM中的Java代码和用C、C++或汇编写的本地代码相互操作。

由于一些加密等情况的需要,需要在 so 层获取一些信息用于生成 license 的部分密钥。需要在 JNI 层调用 Java 接口获取一些信息。

JNI 层调用 JAVA 接口需要一步步声明 class 的路径,method 路径(包括静态 method ),method 输入输出参数等一系列信息。

调用规则稍显复杂。下面简单介绍一些用到的方法(系统知识还在学习中,欢迎交流分享)

例子:以获取设备的 IMEI 为例

// 注意,仅用于示例。获取 IMEI 的方法不一定适用(需要考虑 Android 6.0 等版本差异,以及设备具有多个 IMEI 等情况)
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String imei = telephonyManager.getDeviceId();

0. 声明变量(非必要)

jclass cls_Context;
jclass cls_TelephonyManager;
jobject obj_telephonyManager;
jstring str_TELEPHONY_SERVICE;
jstring str_imei;
jmethodID mid_getSystemService;
jmethodID mid_getDeviceId;
jfieldID fid_TELEPHONY_SERVICE;

定义了一些接下来需要用到的变量。

个人习惯的问题,主要是方便后面偷懒用。(详细情况下方)

有更好的做法不妨交流一下。

1. 获取 getSystemService 接口

cls_Context = env->FindClass("android/content/Context");
mid_getSystemService = env->GetMethodID(cls_Context, "mid_getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");

其中 getSystemService 来源于 Content 类。

因此需要先获取 Content 类(jclass)。再通过 method 名称,method 的输入输出参数获取对应的 methodID(jmethodID)。

详细 methodID 的声明形式见 附录1:MethodID 参数对应列表

2. 获取 TelephonyManager 对象

fid_TELEPHONY_SERVICE = env->GetStaticFieldID(cls_Context, "TELEPHONY_SERVICE", "Ljava/lang/String;");
str_TELEPHONY_SERVICE = (jstring) env->GetStaticObjectField(cls_Context, fid_TELEPHONY_SERVICE);
obj_telephonyManager = env->CallObjectMethod(mContext, mid_getSystemService, str_TELEPHONY_SERVICE);

TELEPHONY_SERVICE 是 Context 类里的一个静态常量。

调用 getSystemService 接口需要的参数是 TELEPHONY_SERVICE 对应的字符串。

因此需要先拿到该 fieldID,然后获取其内容。

可以看一下 附录2:MethodID 和 FieldID 部分对比 的一些对比。

3. 获取 IMEI

cls_TelephonyManager = env->FindClass("android/telephony/TelephonyManager");
mid_getDeviceId = env->GetMethodID(cls_TelephonyManager, "getDeviceId", "()Ljava/lang/String;");
str_imei = (jstring) env->CallObjectMethod(obj_telephonyManager, mid_getDeviceId);

类似于第 2 步,想要调用函数必须先从拿到 class 开始,然后声明 method ,传入参数获取返回值。

4. 手动释放 jobject 资源。

env->DeleteLocalRef(cls_Context);
env->DeleteLocalRef(cls_TelephonyManager);
env->DeleteLocalRef(obj_telephonyManager);
env->DeleteLocalRef(str_TELEPHONY_SERVICE);

凡是继承于 jobject 的都需要手动释放,以防内存泄漏。

主要有:

jclass, jstring, jarray, jobjectArray, jbooleanArray, jbyteArray, jcharArray, jshortArray, jintArray, jlongArray, jfloatArray, jdoubleArray, jthrowable, jweak

当然还有 jobject 。

所以第 0 步的初始化,主要是为了在释放内存时使用。

JNI 层获取 IMEI 方法参考

JNIEXPORT jstring JNICALL
Java_org_xxx_DeviceInfo_JniGetIMEI(
JNIEnv* env, jobject /* this */
, jobject mContext
) {
/*
* TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
* String imei = telephonyManager.getDeviceId();
*/
jclass cls_Context;
jclass cls_TelephonyManager;
jobject obj_telephonyManager;
jstring str_TELEPHONY_SERVICE;
jstring str_imei;
jmethodID mid_getSystemService;
jmethodID mid_getDeviceId;
jfieldID fid_TELEPHONY_SERVICE;
/* TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); */
cls_Context = env->FindClass("android/content/Context");
mid_getSystemService = env->GetMethodID(cls_Context, "mid_getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
fid_TELEPHONY_SERVICE = env->GetStaticFieldID(cls_Context, "TELEPHONY_SERVICE", "Ljava/lang/String;");
str_TELEPHONY_SERVICE = (jstring) env->GetStaticObjectField(cls_Context, fid_TELEPHONY_SERVICE);
obj_telephonyManager = env->CallObjectMethod(mContext, mid_getSystemService, str_TELEPHONY_SERVICE);
/* String imei = telephonyManager.getDeviceId(); */
cls_TelephonyManager = env->FindClass("android/telephony/TelephonyManager");
mid_getDeviceId = env->GetMethodID(cls_TelephonyManager, "getDeviceId", "()Ljava/lang/String;");
str_imei = (jstring) env->CallObjectMethod(obj_telephonyManager, mid_getDeviceId);
quick_release: // class, object, string
env->DeleteLocalRef(cls_Context);
env->DeleteLocalRef(cls_TelephonyManager);
env->DeleteLocalRef(obj_telephonyManager);
env->DeleteLocalRef(str_TELEPHONY_SERVICE);
return str_imei;
}

这里没有做变量获取出错的判断,理论上是需要做的。

出错时 goto 到 quick_release 释放资源并退出。(也可以增加报错)

还有创建新对象的部分,这里暂不展开。

后面有空再补充吧!

附录1:MethodID 参数对应列表

Java 类型

JNI 类型

对应值

说明

示例

boolean

jboolean

Z

byte

jbyte

B

char

jchar

C

short

jshort

S

int

jint

I

long

jlong

J

float

jfloat

F

double

jdouble

D

int[]

[I

一维数组形式,以一个 "[" 表示一维数组

byte[][]

[[B

N 维数组则以 N 个 "[" 表示N维

String

jstring

Ljava/lang/String;

类参数,则以 "Lxxx/yyy;" 的形式表示,注意 "L" 开头和 ";" 结尾。xxx/yyy 则是类的路径(都属于 jobject )

返回值 void

V

函数形式

(xxx)yyy

"()"内为输入参数,右侧为输出参数

注意:jarray, jclass, jstring, jthrowable, jweak 等都继承于 jobject。

附录2:MethodID 和 FieldID 部分对比

ID 类型

JNI Type

获取非静态ID

获取静态ID

获取值 / 调用函数

MethodID

jmethodID

GetMethodID

GetStaticMethodID

CallFloatMethod / CallObjectMethod / CallStaticIntMethod ... (视函数类型选择)

FieldID

jfieldID

GetFieldID

GetStaticFieldID

GetFloatField / GetObjectField / GetStaticIntField ...(视ID对应参数的类型而选择)