JNI 调用 JAVA 接口

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 类型对应值说明示例
booleanjbooleanZ
bytejbyteB
charjcharC
shortjshortS
intjintI
longjlongJ
floatjfloatF
doublejdoubleD
int[][I一维数组形式,以一个 “[” 表示一维数组
byte[][][[BN 维数组则以 N 个 “[” 表示N维
StringjstringLjava/lang/String;类参数,则以 “Lxxx/yyy;” 的形式表示,注意 “L” 开头和 “;” 结尾。xxx/yyy 则是类的路径(都属于 jobject )
返回值 voidV
函数形式(xxx)yyy“()”内为输入参数,右侧为输出参数

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

附录2:MethodID 和 FieldID 部分对比

ID 类型JNI Type获取非静态ID获取静态ID获取值 / 调用函数
MethodIDjmethodIDGetMethodIDGetStaticMethodIDCallFloatMethod / CallObjectMethod / CallStaticIntMethod … (视函数类型选择)
FieldIDjfieldIDGetFieldIDGetStaticFieldIDGetFloatField / GetObjectField / GetStaticIntField …(视ID对应参数的类型而选择)
    原文作者:ZONE画派
    原文地址: https://www.jianshu.com/p/1dfb31bc1577
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞