• 深入理解JNI


    1.JNI

    1.1 JNI实现步骤

    JNI:全名 Java Native Interface,是Java本地接口,属于Java体系

    以下方式在Android中,NDK已经帮我们配置好了;

    1. 在Java中声明Native方法(需要调用的本地方法)
    2. 通过javac编译java源文件(生成.class文件)
    3. 通过javah生成头文件(生成.h头文件)
    4. 通过Native语言实现Java中声明的Native方法
    5. 编译.so库文件
    6. 通过java执行Java程序,最终实现java调用native本地代码(借助.so文件)

    1.2 NDK

    NDK:Native Development Kit,本地开发工具包,属于Android体系

    快速开发 C/C++ 动态库,自动将.so和应用一起打包成apk

    1. 配置NDK环境(下载NDK、CMake、LLDB)
    2. 创建Android项目,选择(C++ support)
    3. 在Java代码中声明 native方法
    4. 使用c/c++实现native方法
    5. 通过 ndk-build编译产生.so库文件

    在AndroidStudio中查看ProjectStructure中的 sdk location,发现 android ndk location 无法手动选择,通过local.properties文件添加 ndk 路径,再去查看 sdk location 发现 ndk location 已经有显示

    sdk.dir=D\:\\Android\\SDK
    // 添加这样的配置
    ndk.dir=D\:\\Android\\SDK\\ndk-bundle
    
    • 1
    • 2
    • 3

    进入gradle.properties文件下,添加对旧版本 ndk 的支持

    // 对旧版本的 ndk 支持
    android.useDeprecatedNdk=true
    
    • 1
    • 2

    1.3 CMakeList.txt

    为本地库做一些配置,包括版本、名称、日志库关联

    # 设置用来构建本地库的CMake最低版本
    cmake_minimum_required(VERSION 3.18.1)
    # 设置本地库名字、分享类型、本地库的路径
    add_library(CPP SHARED src/main/cpp/native-lib.cpp)
    # 定义一个路径变量,log-lib这个变量中的值就是Android中Log库的路径
    find_library(log-lib log)
    # 将本地库与日志库关联,这样就能在本地库中使用log库方法
    target_link_libraries(CPP ${log-lib})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.4 HelloWorld

    img

    通过 AndroidStudio 新建一个项目(CPlusPlusApp),选择最底部的 Native C++,一直 next 等待编译完成后,运行项目

    当然手动创建也是可以的

    1. 在 src\main 目录下新建 cpp目录

    2. 在 src\main\cpp 下新建 CMakeLists.txt,按照上面说的格式新建一份

    3. 在module下的 build.gradle 配置 externalNativeBuild

      android{
          externalNativeBuild {
              cmake {
                  path file('src/main/cpp/CMakeLists.txt')
                  version '3.18.1'
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    4. 在应用中初始化库文件,并创建 native 方法,选择合适的地方调用该方法运行即可

      // src/main/java/com/monk/cpp/MainActivity.kt
      compaion object{
          init{
              // CMakeLists.txt中声明的库名称
              Syste.loadLibrary(libname:"cpp")
          }
      }
      
      // 创建完成后,编译器会自动提示需要在创建一个与之对应的 c++ 方法
      external fun stringFromJni():String
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    5. 创建native-lib.cpp标准文件,上述定义的本地方法,可以按照编辑器提示,自动创建实现方法

      具体的实现方法遵循的方法名称是规定好的,可以参考以下例子

      // src\main\cpp\native-lib.cpp
      extern "C"
      JNIEXPORT jstring JNICALL
      Java_com_monk_cpp_ActMain_stringFromJni(
              JNIEnv *env,
              jobject /* this */) {
          std::string hello = "Hello from C++";
          return env->NewStringUTF(hello.c_str());
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    2. native-lib.cpp

    来分析分析这个cpp文件

    // src\main\cpp\native-lib.cpp
    #inlcude <jni.h> // 定义了 jni 所支持的类型与接口
    #inlcude <string>
    
    extern "C"
    JNIEXPORT jstring JNICALL// 这里的 jstring 类似于java中方法返回值,这里表示该c++方法返回的是字符串
    Java_com_monk_cpp_ActMain_stringFromJni(JNIEnv *env, jobject thiz) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中JNIEXPORT jstring JNICALL语句,表示的c++函数的返回,其中jstring与java声明的native 方法是对应关系,这里也表面java中的对应函数返回的也是string.

    2.1 调用java静态方法

    // src\main\java\com\monk\cpp\ActMain.kt 
    override fun initData(savedInstanceState: Bundle?) {
            mBinding.test.text = "${stringFromJni()}\n" +
                    "${callJavaStaticMethod()}\n"   
        }
     external fun callJavaStaticMethod(): String
     companion object {
            init {
                System.loadLibrary("cpp")
            }
    
            @JvmStatic
            fun staticMethod(cppStr: String): String {
                Toast.makeText(App.INSTANCE, "i am java static method: $cppStr", Toast.LENGTH_LONG).show()
                return "i am java static method: $cppStr"
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    对应的 c++ 函数

    // src\main\cpp\nativ-lib.cpp
    // 演示 c++ 调用 java 静态方法
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_monk_cpp_ActMain_callJavaStaticMethod(JNIEnv *env, jobject thiz) {
        // 找到对应的类
        jclass cls_main = env->FindClass("com/monk/cpp/ActMain");
        // 获取methodId,kotlin需要@JvmStatic才能识别,最后一个参数是字节码中的显示,例如String的字节码为:(Ljava/lang/string;)V
        jmethodID method_static_id = env->GetStaticMethodID(cls_main, "staticMethod", "(Ljava/lang/String;)Ljava/lang/String;");
        // 构建String 变量
        jstring str = env->NewStringUTF("来自c++的字符串");
        // 调用java的static方法
        env->CallStaticObjectMethod(cls_main, method_static_id, str);
        //释放
        env->DeleteLocalRef(cls_main);
    //    env->DeleteLocalRef(str);
        return str;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    c++如何调用 java 静态方法,使用GetStaticMethodID函数获取java的静态方法,使用CallStaticObjectMethod调用java静态方法

    在这里插入图片描述

    2.2 调用java实例方法

    // src\main\java\com\monk\cpp\ActMain.kt 
    override fun initData(savedInstanceState: Bundle?) {
        mBinding.test.text = "${stringFromJni()}\n" +
        "${callJavaStaticMethod()}\n"+
        "${callJavaMethod("monk", 24)}\n"
    }
    external fun callJavaMethod(name: String, age: Int): Any
    
    // src\main\java\com\monk\cpp\JavaBean.kt 
    data class JavaBean(val name: String, val age: Int){
        override fun toString(): String {
            return "JavaBean(name='$name', age=$age)"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    对应的 c++ 代码:

    // src\main\cpp\native-lib.cpp
    extern "C"
    JNIEXPORT jobject JNICALL
    Java_com_monk_cpp_ActMain_callJavaMethod(JNIEnv *env, jobject thiz,jstring name, jint age) {
        jclass java_bean = env->FindClass("com/monk/cpp/JavaBean");
        // 获取构造方法
        jmethodID init = env->GetMethodID(java_bean, "", "(Ljava/lang/String;I)V");
        // 调用构造方法
        jobject bean = env->NewObject(java_bean, init, name, age);
        //获取 toString 方法
        jmethodID to_string = env->GetMethodID(java_bean, "toString", "()Ljava/lang/String;");
        // 调用toString 方法
        jobject result = env->CallObjectMethod(bean, to_string);
        // 回收
        env->DeleteLocalRef(java_bean);
    //    env->DeleteLocalRef(to_string);
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.3 创建引用

    引用上面例子中进行解释:

    // src\main\cpp\native-lib.cpp
    extern "C"
    JNIEXPORT jobject JNICALL
    Java_com_monk_cpp_ActMain_callJavaMethod(JNIEnv *env, jobject thiz,jstring name, jint age) {
        ...
        // 调用toString 方法,这里使用一个 jobjcet 创建一个局部引用
        jobject result = env->CallObjectMethod(bean, to_string);
       	
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当然也可以将局部引用转成全局引用,在成员中声明即可。


    3.CMake

    CMake 是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。

    以前做 NDK 开发都是基于 Android.mk、Application.mk 来构建项目的,但从 AS 2.2 之后便开始采用CMake 的这种方式来构建,采用 CMake 相比与之前的 Android.mk、Application.mk 方便简单了许多。


    4.JNI使用全解

    JNI:全名 Java Native Interface,是Java本地接口,JNI是Java调用Native 语言的一种特性,通过JNI可以使得Java与C/C++机型交互。简单点说就是 JNI是Java中调用C/C++的统称

    NDK :全名Native Develop Kit,官方说法:Android NDK 是一套允许您使用 C 和 C++ 等语言,以原生代码实现部分应用的工具集。在开发某些类型的应用时,这有助于您重复使用以这些语言编写的代码库

    JNIEnv类型
    代表的是Java环境,通过JNIEnv*指针就可以对Java端的代码进行操作。比如我们可以使用JNIEnv来创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等

    jobject类型
    jobject可以看做是java中的类实例的引用。当然,情况不同,意义也不一样。

    1. 如果native方法不是static, obj 就代表native方法的类实例
    2. 如果native方法是static, obj就代表native方法的类的class 对象实例(static 方法不需要类实例的,所以就代表这个类的class对象)

    代码展示

    // src\main\java\com\monk\capplication\MainActivity.kt
    Class MainAct:AppCompatActivity(){
       companion object{
           // 相当于java static{}静态代码块
           init{
               // CMakeLists.txt 指定的该名称
              System.loadLibrary("CApplication")
           }
       }
        
       private external fun stringFromJNI(): String
       private external fun changeField()
    
       private val number = 0
    
       override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            changeField()
            // Hello from C++
            // 100
            binding.sampleText.text = "${stringFromJNI()} " +
                    "\n ${number}" +
                    ""
        }
    }
    
    // src\main\cpp\Hello.cpp
    #include <jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_monk_capplication_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_monk_capplication_MainActivity_changeField(JNIEnv *env, jobject thiz) {
        jclass a_class = env->GetObjectClass(thiz);
        jfieldID a_field = env->GetFieldID(a_class,"number", "I");
        env->SetIntField(thiz,a_field,100);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    4.1 JNI 类型签名介绍

    JNI 规范定义的函数签名信息看起来很别扭,不过习惯就好了。它的格式是:

    (参数1类型标示;参数2类型标示;...参数n类型标示;)返回值类型标示
    例子:(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
    
    当参数的类型是引用类型时,其格式是 “L包名;”,其中包名中的 "." 换成 "/",比如 Ljava/lang/String;表示一个 Java String 类型。
    
    • 1
    • 2
    • 3
    • 4

    4.2 abi

    ABI是英文 Application Binary Interface 的缩写,及应用二进制接口。不同Android设备,使用的CPU架构可能不同,因此支持不同的指令集。CPU 与指令集的每种组合都有其自己的应用二进制界面(或 ABI)。ABI 中还包括如何重整 C++ 名称

    Android Q目前支持以下7种ABIs:

    1. mips
    2. mips64
    3. x86
    4. x86-64
    5. arm64-v8a
    6. armeabi
    7. armeabi-v7a

    当我们想要在项目中使用 native(C/C++) 类库,我们必须对要支持的处理器架构提供对应编译包。每个处理器架构需要我们提供一个或多个包含native代码的.so文件。

    实际中不可能全部支持,否则apk size太大,对于用户来说,目标设备只需要其中一个版本,但当用户下载APK时,会全部下载(对用户来说相当的不友好)。

    abifilters 为我们提供了解决方案,abifilters为我们提供了选择适配指定CPU架构的能力,只需要在app下的build.gradle添加如下配置:

    android {
            defaultConfig {
                ndk {
                    // 指定 arm64-v8a,x86_64 两种 abi
                    abiFilters 'arm64-v8a', 'x86_64'
                }
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.3 so文件

    .a文件:A文件是UNIX系统和类似UNIX系统(例如Linux和macOS)中的静态链接库文件。所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。

    .so文件:动态链接库。

    不同操作系统的动态链接库文件格式稍有不同,Linux称之为共享目标文件(Shared Object),文件后缀为.so,Windows的动态链接库(Dynamic Link Library)文件后缀为.dll

    so文件后面往往跟着很多数字,这表示了不同的版本。so文件命名规则被称为SONAME:

    libname.so.x.y.z
    
    • 1

    lib是前缀,这是一个约定俗成的规则。x为主版本号(Major Version),y为次版本号(Minor Version),z为发布版本号(Release Version)。

  • 相关阅读:
    Java--SpringMVC之处理器方法返回值
    GCC 编译器常用选项
    Word背景图片插入,5个简单方法快速完成!
    双向控制舵机(树莓派版)
    阿里云X魔搭社区Create@AI创客松第四届冠军:MumuLab
    8.11 可解析AI
    grpc多语言通信之GO和DART
    云原生架构体系
    STM32中的printf重定向uart1串口输出
    JavaScript 71 JavaScript JSON 71.9 JSON 服务器
  • 原文地址:https://blog.csdn.net/qq_37776700/article/details/133918818