• JNI 的学习(三)JNI 层访问 Java 端对象


    JNI 的学习(三)JNI 层访问 Java 端对象

      前面两篇文章简单介绍了 JNI 层跟 Java 层的一些对应关系,包括方法名,数据类型和方法名称等,相信在理论层面,能够很好地帮助我们去了解 JNI 在 Native 本地开发中的作用,对 JNI 的一些概念也有了一个初步的认识

      概念上的理解有助于我们更好地认识JNI,而一些实际点的例子则能够更好地帮我们从代码上去掌握并应用 JNI。

      在第一篇文章,我们是从一个小例子来入门学习的,在其中,我们通过JNI层函数返回了一字符串,如下:

    1 JNIEXPORT jstring JNICALL Java_com_clay_example_JNITest_getJNIString
    2 (JNIEnv* env, jobject obj)
    3 {
    4     return (*env)->NewStringUTF(env, "Hello From JNITest Function(getJNIString)");
    5 }

      这是一种最简单的情况,但更多时候,我们需要 在JNI 层获得 Java 对象,对其进行操作,最后将结果返回到 Java 端,所以这个时候我们就要利用到 JNI 函数定义的第二个参数 jobject 了。

      上一篇文章,我们说过,JNIEnv * 和 jobejct 参数都是 JNI 层方法添加的参数,关于 JNIEnv* 我们已经在前面的文章简单介绍过,而 jobject 参数呢,则我们这一篇文章要操作到的参数了。

      对于本地方法,即在 Java 中定义的 native 方法,有静态(static)和非静态的方法,而我们知道,静态方法它是属于这个类的方法,对象不能操作它,而非静态方法则刚好相反,所以在 JNI 层的方法参数中:

        1)对于静态(static)方法,jobject 参数表示的是对应 Java 类的引用。

        2)对于非静态方法,jobject 表示的是对应 Java 对象的引用。

      这一点,应该不难理解。

      接下来,我们通过一个小 Demo 来学习怎么在 JNI 层操作 Java 端的对象,并且改变其中的值。

      首先,我们在 Java 类中定外一个 static 的变量 testval,还有一个方法 changeTestVal(),用来改变 testval 的值,如下:

    1 package com.clay.example;
    2 
    3 public class JNITest {
    4 
    5     public static int testVal = 1;
    6 
    7     public native int changeTestVal();
    8 }

      当然,首先,第一步,我们要在 C 中实现其对应的函数了,如下:

     1 //
     2 // Created by zhengchuanyu on 20-12-9.
     3 //
     4 #include <stdio.h>
     5 #include <stdlib.h>
     6 #include <jni.h>
     7 #include "utils/android_log_print.h"
     8 
     9 JNIEXPORT jint JNICALL Java_com_clay_example_JNITest_changeTestVal
    10 (JNIEnv* env, jobject obj)
    11 {
    12     /**
    13      * jclass GetObjectClass(JNIEnv* env, jobjcet obj);
    14      * 返回值:jclass
    15      * 参数:
    16      *      env:JNI 接口指针
    17      *      obj:Java 对象(不能为 NULL)
    18      */
    19     jclass clazz = (*env)->GetObjectClass(env, obj);
    20 
    21     /**
    22      * jfieldID  GetStaticFieldID(JNIEnv* env, jclass clazz, const char* name, const char* sig);
    23      * 返回值:jfieldID,域 ID。如果找不到指定的静态域,则为 NULL。
    24      * 参数:
    25      *      env:JNI 接口指针
    26      *      clazz:Java 类对象
    27      *      name:0 终结的 UTF-8 字符串中的静态域名
    28      *      sig:0 终结的 UTF-8 字符串中的静态域名
    29      */
    30     jfieldID jfileIDtestVal = (*env)->GetStaticFieldID(env, clazz, "testVal", "I");
    31 
    32     /**
    33      * jint GetStaticIntField(JNIEnv* env, jclass clazz, jfieldID id);
    34      * 返回值:jint
    35      * 参数:
    36      *      env:JNI 接口指针
    37      *      clazz:Java 类对象
    38      *      id:域 ID,这里是静态域 ID
    39      */
    40     jint val = (*env)->GetStaticIntField(env, clazz, jfileIDtestVal);
    41     LOGI("before change testVal = %d", val);
    42     val = val + 1;
    43     LOGI("after change testVal = %d", val);
    44     /**
    45      * void SetStaticIntField(JNIEnv* env, jclass clazz, jfieldID id, jint val);
    46      * 返回值:jint
    47      * 参数:
    48      *      env:JNI 接口指针
    49      *      clazz:Java 类对象
    50      *      id:域 ID,这里是静态域 ID
    51      *      val: jint 类型变量
    52      */
    53     (*env)->SetStaticIntField(env, clazz, jfileIDtestVal, val);
    54 }

      7 #include "utils/android_log_print.h"

      第 7 行中我们定义一个自己的 log.h 的工具

     1 //
     2 // Created by zhengchuanyu on 20-12-11.
     3 //
     4 
     5 #ifndef _ANDROID_LOG_PRINT_H_
     6 #define _ANDROID_LOG_PRINT_H_
     7 
     8 #include <android/log.h>
     9 
    10 #define IS_DEBUG
    11 
    12 #ifdef IS_DEBUG
    13 
    14 #define LOG_TAG ("CUSTOMER_NDK_JNI")
    15 
    16 #define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
    17 
    18 #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG  , LOG_TAG, __VA_ARGS__))
    19 
    20 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO   , LOG_TAG, __VA_ARGS__))
    21 
    22 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN   , LOG_TAG, __VA_ARGS__))
    23 
    24 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__))
    25 
    26 #else
    27 
    28 #define LOGV(LOG_TAG, ...) NULL
    29 
    30 #define LOGD(LOG_TAG, ...) NULL
    31 
    32 #define LOGI(LOG_TAG, ...) NULL
    33 
    34 #define LOGW(LOG_TAG, ...) NULL
    35 
    36 #define LOGE(LOG_TAG, ...) NULL
    37 
    38 #endif
    39 
    40 #endif //_ANDROID_LOG_PRINT_H_

      代码目录如下:

      引用日志系统需要在 mk 文件中添加共享库,mk 修改如下:

     1 LOCAL_PATH := $(call my-dir)
     2 
     3 include $(CLEAR_VARS)
     4 
     5 LOCAL_MODULE := JNITest
     6 LOCAL_SRC_FILES := 
     7 JNITest.c 
     8 
     9 LOCAL_LDLIBS += -llog  //库的引用
    10 
    11 include $(BUILD_SHARED_LIBRARY)

      后面看看有没有时间写一下有关这个 JNI 中使用 log 日志系统的博客吧,这种前期学习的博客,感觉要补充的东西很多,全都带上来又感觉头重脚轻了,失去了文章的重点,嗯,先这些写,后面再单独补充博客,然后超链接到这里来

      这个工具集其实也不是我写的,它是在安卓代码中有定义的,具体可以去看 Android log 日志系统

      但是我们可以分析它,摘抄出来方便自己使用,当然你也可以在 JNITest.c 中加入这个头函数 :#include <android/log.h>

      我们在对应的c文件中来实现这个 native 方法,因为实现的是非静态方法,所以 jobject 传过来的就是对该对象的引用,所以我们需要通过 GetObjectClass 方法来获得该对象对应的类。

      一般在 JNI 中,我们会利用 FindClass 和 GetObjectClass 两个方法来获得对应的类,并放到 jclass 类型的变量中去,不过在这里注意一点,用 C 实现和用 C++ 实现的代码对于 JNI 的调用方法是不一样的。

      在前面文章中说过,C++ 对 JNINativeInterface 定义的方法进行了一层包装,所以其参数不再需要传递 env 进去,而 C 则是需要的,比如上面 *env 调用的方法,如果是用 C++实现的话,那么是不再需要传递 env 参数进去的,即 GetObjectClass(jobject) 就可以了。

        1)利用 GetObjectClass 方法获得 jclass。

        2)调用 GetStaticIntFieldID 获得对应 class 对应的变量,即 jclass 中的类型为 I(即 int )的静态(static)变量 testval。

        3)调用 GetStaticIntField 获得对应变量的值 val。

        4)改变 val 的值,在这里,我们进行加 1 操作。

        5)调用 SetStaticIntField 来设置对应变量的值。

      所以,在这里我们发现,Env 其实提供了很多的方法,对于访问对象变量值的,分为静态非静态的,基本上就是 Get<Type>Field 和 GetStatic<Type>Field,

      而相应的,也有 Set<Type>Field 和 SetStatic<Type>Field 方法。

      而如果调用方法呢,就是利用 Call<Type>Method 和 CallStatic<Type>Method 方法了,这些大家可以自己去 jni.h 文件中自己看一下,当然我也会在后面的博客中专门罗列,一是方便大家学习,写博客嘛本着共享精神,二是方便自己查阅。

      JNI 层这边实现好了之后,我们利用 ndk-build 工具重新生成一个 so 库,加载到 Android 中,在 Activity 中直接调用方法,如下:

     1 package com.clay.example;
     2 
     3 import androidx.appcompat.app.AppCompatActivity;
     4 
     5 import android.os.Bundle;
     6 import android.widget.TextView;
     7 
     8 public class MainActivity extends AppCompatActivity {
     9 
    10     static {
    11         System.loadLibrary("JNITest");
    12     }
    13 
    14     @Override
    15     protected void onCreate(Bundle savedInstanceState) {
    16         super.onCreate(savedInstanceState);
    17         setContentView(R.layout.activity_main);
    18         TextView textView = (TextView) findViewById(R.id.text);
    19         JNITest jniTest = new JNITest();
    20         jniTest.changeTestVal();
    21         textView.setText("changeVal: " + JNITest.testVal);
    22     }
    23 }

      我们调用方法之后,在屏幕上将调用方法后的值,显示出来,结果应该是 1 + 1 = 2,对吧,看下面结果:

      的确如我们所想像的,它的值已经变化成2了,对吧,说明我们的确是通过 native 方法在 JNI 层改变了其值。

      我们刚才也在 JNI 中添加了 log,来展示其改变前后的值,如下:

      通过这样一个简单的小例子,相信大家应该就知道了怎么样在 JNI 层来操作 Java 端的数据了,对吧。

  • 相关阅读:
    关于lua扩展库lpack的使用指南
    mac下安装LuaSocket
    lua_の_进阶总结之基础篇
    minicap_工具使用
    AutoSense脚本日志总结(微信搜索【水勺子】公众号获取详情)
    六:Spring Security 中使用 JWT
    五:Spring Security 中的角色继承问题
    四:Spring Security 登录使用 JSON 格式数据
    二:整合Spring Security
    三:Spring Security 登录添加验证码
  • 原文地址:https://www.cnblogs.com/Reverse-xiaoyu/p/14123368.html
Copyright © 2020-2023  润新知