掌握 Android JNI 基础

写在前面

最近在看一些底层源码,发现 JNI 这块还是有必要系统的看一下,索性就写一写博客,加深加深印象🍻

本文重点聊一聊一些干货,避免长篇大论

JNI 概述

JNI 是什么?

定义:Java Native Interface ,即 Java 本地接口 作用:使得 Java 与本地其他类型语言(如 C、C++ )进行交互

注意:

  • JNI 是 Java 调用 Native 语言的一种特性

  • JNI 是属于 Java 的,与 Android 平台无直接关系

以 Java 8 为例,JNI 最新在线 API: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html

为什么会有 JNI ?

实际的使用中,Java 需要与本地代码进行交互,因为 Java 项目具备跨平台的特点,所以 Java 与本地代码交互的能力非常弱,采用 JNI 特性,增强 Java 与本地代码交互的能力

JNI 和 NDK 的关系

JNI 是 Java 平台提供的一套非常强大的框架 Java Native Interface,用于与本地代码进行相互调用

NDK 是 Android 平台提供的 Native 开发工具集,Native Development Kit的缩写。NDK 其中包含了 JNI 并对其进行了封装

关于 JNI 的入口头文件会有两份,分别在 JDK 和 NDK 中,进一步说明 Android 的 NDK 对 JDK 的 JNI 进行了二次封装

常见所在目录:

  • JDK: JAVA_HOME/include/jni.h

  • NDK: ~/Library/Android/sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/jni.h

环境准备

  • Android Studio 版本:Android Studio Hedgehog | 2023.1.1 Patch 1

  • Gradle 版本:gradle-8.0

  • targetSdk:33

Android Studio 安装相关工具:

创建示例

File -> New,选择 Native++ 模板:

检查 NDK 相关配置是否正常:

native-lib.cpp C++ 示例代码:

MainActivity.java 示例代码:

CMake 构建

在 Android 开发中,CMake 用于编译 C/C++ 代码。从 Android Studio 2.2 版本开始,Google 引入了对 CMake 的支持,使得开发者可以通过 CMake 和 NDK 将 C/C++ 代码编译成底层的库,然后再配合 Gradle 的编译将库打包到 APK 中

CMake 具体的配置信息如下:

# cmake最低版本要求
cmake_minimum_required(VERSION 3.22.1)

# 配置库生成路径
# CMAKE_CURRENT_SOURCE_DIR是指 cmake库的源路径,通常是build/.../cmake/
# /../jniLibs/是指与CMakeList.txt所在目录的同级目录:jniLibs (如果没有会新建)
# ANDROID_ABI 生成库文件时,采用gradle配置的ABI策略(即:生成哪些平台对应的库文件)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

# 添加库
add_library( # 库名
        native-lib

        # 类型:
        # SHARED 是指动态库,对应的是.so文件
        # STATIC 是指静态库,对应的是.a文件
        # 其他类型:略
        SHARED

        # native类路径
        native-lib.cpp)

# 查找依赖库
find_library(
        # 依赖库别名
        log-lib

        # 希望加到本地的NDK库名称,log指NDK的日志库
        log)


# 链接库,建立关系( 此处就是指把log-lib 链接给native-lib使用 )
target_link_libraries(
        # 目标库名称(native-lib就是咱们要生成的so库)
        native-lib

        # 要链接的库(上面查找的log库)
        ${log-lib})

摘自: https://www.cnblogs.com/qixingchao/p/11911787.html

默认 so 的目录(注意 AS 版本,不同版本不一致):

安装包反编译 so 路径位置:

示例解读

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

JNICALL
Java_org_lulu_jnilearning_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "这是 C++ 中的代码";
    char * str = "这是 C++ 中的代码";

    return env->NewStringUTF(str);
}
  • extern "C"避免按照 C++ 的方式去编译 C 函数

    为什么需要使用它呢? 这是因为 C++ 支持函数重载,所以编译器在编译函数时会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名。而 C 语言并不支持函数重载,因此编译 C 语言代码的函数时不会带上函数的参数类型,一般只包括函数名

  • JNIEXPORT :用来表示该函数是否可导出(宏定义)

  • JNICALL:(可以缺少)jni call 约束函数入栈顺序和堆栈内存清理规则,在 Linux 中置空

  • jstring:代表 Java 中的 String

  • JNIEnv:C 和 Java 相互调用的桥梁,代表 Java 环境,内部包含了众多函数(后续详细介绍)

  • jobject:( Java 侧声明的 native 方法为非静态,传递时为 jobject)Java 传递过来的示例对象,即当前 Java 类的对象,示例中 MainActivity.this 就是它

  • jclass:( Java 侧声明的 native 方法为静态,传递时为 jclass)Java 传递过来的类对象,即当前 Java 类的 Class 对象,示例中 MainActivity.class 就是它

JNIEnv

C 和 Java 相互调用的桥梁,代表 Java 环境

通过 JNIEnv 就可以对 Java 端的代码进行操作:

  • 创建 Java 对象

  • 调用 Java 对象方法

  • 获取 Java 对象的属性

  • ...

C++ 中 JNIEnv 指向_JNIEnv,而_JNIEnv是定义的一个结构体,包裹了 JNINativeInterface,而在 C 中JNIEnv就是 JNINativeInterface

常用的方法:

函数名称作用
NewObject创建Java类中的对象
NewString创建Java类中的String对象
NewArray创建类型为Type的数组对象
GetField获得类型为Type的字段
SetField设置类型为Type的字段
GetStaticField获得类型为Type的static的字段
SetStaticField设置类型为Type的static的字段
CallMethod调用返回值类型为Type的static方法
CallStaticMethod调用返回值类型为Type的static方法
FindClass通过类路径获取 Java 类的类对象
GetObjectClass通过类对象获取 Java 类的类对象

Java、JNI、C/C++ 基本类型映射

Java 、C/C++都有一些常用的数据类型,分别是如何与JNI类型对应的呢?做以下整理:

JNI中定义的别名Java类型C/C++类型
jint / jsizeintint
jshortshortshort
jlonglonglong / long long (__int64)
jbytebytesigned char
jbooleanbooleanunsigned char
jcharcharunsigned short
jfloatfloatfloat
jdoubledoubledouble
jobjectObject_jobject*

JNI描述符(签名)

JNI 开发时,我们除了写本地C/C++实现,还可以通过JNIEnv *env 调用 Java 层代码,如获得某个字段、获取某个函数、执行某个函数等:

//获得某类中定义的字段id
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }

//获得某类中定义的函数id
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

以上函数和 Java 的反射比较类似,参数说明:

  • clazz:类的 class 对象

  • name:字段名称、函数名称

  • sig:字段描述符(字段签名),函数描述符(函数签名)

特别的,对 sig 进行解释:

  1. 如果是字段,表示字段类型的描述符

  2. 如果是函数,表示函数结构的描述符,即:每个参数类型描述符 + 返回值类型描述符

后面会结合实际案例进一步说明

整理字段类型签名:

Java类型字段描述符(签名)备注
intIint 的首字母、大写
floatFfloat 的首字母、大写
doubleDdouble 的首字母、大写
shortSshort 的首字母、大写
longLlong 的首字母、大写
charCchar 的首字母、大写
byteBbyte 的首字母、大写
booleanZ因 B 已被 byte 使用,所以 JNI 规定使用 Z
objectL + /分隔完整类名String 如: Ljava/lang/String
array[ + 类型描述符int[] 如:[I

整理函数类型签名:

Java函数函数描述符(签名)备注
voidV无返回值类型
Method(参数字段描述符...)返回值字段描述符int add(int a,int b) 如:(II)I

如何通过指令获取当前 class 的签名:

找到此目录: 执行以下命令:

javap -s -p MainActivity.class 

相关描述符信息如下:

Compiled from "MainActivity.kt"
public final class org.lulu.jnilearning.MainActivity extends androidx.appcompat.app.AppCompatActivity {
  //...
  private java.lang.String nonStaticField;
    descriptor: Ljava/lang/String;
  private static java.lang.String staticField;
    descriptor: Ljava/lang/String;
    //...
}

Java和C++交互示例

我们以从 Native 侧修改 Java 侧非静态字段和静态字段为例,编写一段简单 Java 和 C++ 交互的示例:

修改非静态字段:

MainActivity.kt

private const val TAG = "MainActivity"

class MainActivity : AppCompatActivity() {
    /**
     * 待修改的非静态字段
     */
    private var nonStaticField = "Java 非静态字段"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
  //...
        Log.d(TAG, "changeNonStaticField before: $nonStaticField")
        changeNonStaticField()
        Log.d(TAG, "changeNonStaticField after: $nonStaticField")
    }

 /**
     * 修改非静态字段的 Native 方法
     */
    external fun changeNonStaticField()

    companion object {
        // Used to load the 'jnilearning' library on application startup.
        init {
            System.loadLibrary("jnilearning")
        }
    }
    //...
}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_org_lulu_jnilearning_MainActivity_changeNonStaticField(JNIEnv *env, jobject thiz) {
    //1. 获取当前实例的类对象,即 MainActivity.class
    //   有两种方式
    // 1.1 jclass FindClass(const char* name)
    //jclass clazz = env->FindClass("org/lulu/jnilearning/MainActivity");
    // 1.2 【推荐】jclass GetObjectClass(jobject obj)
    jclass clazz = env->GetObjectClass(thiz);
    //2. 获取要修改的非静态字段 Id
    //jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    jfieldID nonStaticFieldId = env->GetFieldID(clazz, "nonStaticField", "Ljava/lang/String;");
    //3. 创建一个新的 jstring,准备赋值
    //jstring NewStringUTF(const char* bytes)
    jstring jstr = env->NewStringUTF("Java 非静态字段,C++ 已修改");
    //4. 这是这个新的 jstring 给 nonStaticField 字段
    //void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
    env->SetObjectField(thiz, nonStaticFieldId, jstr);
}

代码执行结果如下:

changeNonStaticField before: Java 非静态字段
changeNonStaticField after: Java 非静态字段,C++ 已修改

静态变量的修改类似,仅贴出C++代码:

extern "C"
JNIEXPORT void JNICALL
Java_org_lulu_jnilearning_MainActivity_changeStaticField(JNIEnv *env, jobject thiz) {
    //1. 获取当前实例的类对象,即 MainActivity.class
    //   有两种方式
    // 1.1 jclass FindClass(const char* name)
    //jclass clazz = env->FindClass("org/lulu/jnilearning/MainActivity");
    // 1.2 【推荐】jclass GetObjectClass(jobject obj)
    jclass clazz = env->GetObjectClass(thiz);
    //2. 获取要修改的静态字段 Id
    //jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
    jfieldID staticFieldId = env->GetStaticFieldID(clazz, "staticField", "Ljava/lang/String;");
    //3. 创建一个新的 jstring,准备赋值
    //jstring NewStringUTF(const char* bytes)
    jstring jstr = env->NewStringUTF("Java 静态字段,C++ 已修改");
    //4. 这是这个新的 jstring 给 staticField 字段
    //void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
    env->SetStaticObjectField(clazz, staticFieldId, jstr);
}

最后

这篇文章就到这里了,希望大家能够学到一些有用的知识,也欢迎你们在评论区留言交流。如果你觉得这篇文章有趣或者有帮助,不妨给我点个赞或者分享给你的朋友。感谢你们的阅读,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/358849.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全国网络安全行业职业技能大赛WP

word_sercet 文档被加密 查看图片的属性 在备注可以看到解压密码 解密成功 在选项里面把隐藏的文本显示出来 可以看到ffag easy_encode 得到一个bmp二维码 使用qr research 得到的密文直接放瑞士军刀 base32解码base64解码hex解码 dir_pcap 直接搜索flag 发现flag…

C++ 程序使用 OpenCV 可视化和分析两个图像之间特征点的对应关系

文章目录 代码功能源码文件编译文件 代码功能 创建图像和生成随机特征点&#xff1a; 程序首先创建两个灰度图像&#xff08;m_image_Left_BGR 和 m_image_Right_BGR&#xff09;&#xff0c;并将它们转换为彩色图像。然后&#xff0c;生成两组随机特征点&#xff08;mvKeys 和…

线段树分治总结

线段树分治总结 概念例题二分图 /【模板】线段树分治[HAOI2017] 八纵八横[FJOI2015] 火星商店问题EnvyExtending Set of PointsForced Online Queries Problem「雅礼集训 2018 Day10」贪玩蓝月BZOJ4184-shallot[bzoj4644]经典**题 概念 \qquad 线段树分治一般用来解决带有如下两…

强敌环伺:金融业信息安全威胁分析——整体态势

从早期的Zeus和其他以银行为目标的特洛伊木马程序&#xff0c;到现在的大规模分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;再到新颖的钓鱼攻击和勒索软件&#xff0c;金融服务业已成为遭遇网络犯罪威胁最严重的行业之一。金融服务业的重要性不言而喻&#xff0…

浙政钉(专有钉钉)

专有钉钉是浙政钉的测试版本&#xff0c;可在正式发布之前进行业务开发。 专有钉钉 原名政务钉钉 是高安全、强管控、灵活开放的面向大型组织专有独享的协同办公平台。支持专有云、混合云等多种方式灵活部署&#xff0c;以满足客户特定场景所需为目标&#xff0c;最大化以“平…

Docker 搭建MySQL主从复制-读写分离

一. 介绍 MySQL主从复制是一种常用的数据库高可用性解决方案&#xff0c;通过在主数据库上记录的数据变更&#xff0c;同步到一个或多个从数据库&#xff0c;实现数据的冗余备份和读写分离。在Docker环境下搭建MySQL主从复制和读写分离&#xff0c;不仅方便管理&#xff0c;还…

【干货】【常用电子元器件介绍】【电容】(二)--电容器的主要参数、测量、选择与应用

声明&#xff1a;本人水平有限&#xff0c;博客可能存在部分错误的地方&#xff0c;请广大读者谅解并向本人反馈错误。 一、 电容器的主要参数 1.1 耐压 耐压(Voltage Rating)是指电容器在电路中长期有效地工作而不被击穿所能承受的最大直流电压。对于结构、介质、容量相同的…

Linux--redhat9创建软件仓库

1.插入光盘&#xff0c;挂载镜像 模拟插入光盘: 点击:虚拟机-可移动设备-CD/DVD 设备状态全选&#xff0c;使用ISO影响文件选择当前版本镜像&#xff0c;点击确认。 2.输入: df -h 可以显示&#xff0c;默认/dev/sr0文件为光盘文件&#xff0c;挂载点为/run/media/root/镜像…

Linux(CentOS7)常见指令的常见用法(上)

指令功能hostname查看当前的主机名hostnamectl set-hostname修改主机名adduser添加用户passwd给用户设置密码userdel -r 删除用户ls显示某路径下的文件名ls -l ll 显示某路径下每个文件及其属性ls -la ls -al 显示某路径下所有文件包括隐藏文件及属性ls -d只看指定文件夹&…

ElementUI安装与使用指南

Element官网-安装指南 提醒一下&#xff1a;下面实例讲解是在Mac系统演示的&#xff1b; 一、开发环境配置 电脑需要先安装好node.js和vue2或者vue3 安装Node.js Node.js 中文网 安装node.js命令&#xff1a;brew install node node.js安装完后&#xff0c;输入&#xff1…

第九节HarmonyOS 常用基础组件18-checkBox

1、描述 提供多选框组件&#xff0c;通常用于某选项的打开或关闭。 2、接口 Checkbox(options:{name?: string, group?: string}) 3、参数 参数名 参数类型 必填 描述 name string 否 多选框名称 group string 否 多选框群组名称。&#xff08;未配合使用Chec…

【芯片设计- RTL 数字逻辑设计入门 番外篇 8 -- MBIST 详细介绍】

请阅读【嵌入式开发学习必备专栏 】 文章目录 MBISTMBIST 背景MBIST的主要特点和优势MBIST的工作原理举例 MBIST MBIST&#xff08;Memory Built-In Self-Test&#xff09;是一种在系统级芯片&#xff08;SoC&#xff09;中内置的内建自测试&#xff0c;用于检测和验证片上存储…

centos下静态链接:/usr/bin/ld: cannot find -l某某某

问题&#xff1a;/usr/bin/ld: cannot find -l某某某 前言解法相关文章 前言 我是在静态链接的时候碰到了/usr/bin/ld: cannot find -lstdc的问题&#xff0c;这里来记录一下我是如何解决的。 如果你是动态链接的时候出了问题&#xff0c;可以直接看我给出的倒数第二篇文章&a…

C#,贝尔数(Bell Number)的计算方法与源程序

1 埃里克坦普尔贝尔 贝尔数是组合数学中的一组整数数列&#xff0c;以埃里克坦普尔贝尔&#xff08;Eric Temple Bell&#xff09;命名&#xff0c; 埃里克坦普尔贝尔&#xff08;生于1883年2月7日&#xff0c;苏格兰阿伯丁郡阿伯丁&#xff0c;于1960年12月21日在美国加利福尼…

Abp 创建一个WPF的项目

开发环境&#xff1a;VS2022、.NET6 1、创建项目&#xff1a;MyWpfApp&#xff0c;这里不再废话了。 2、NuGet添加&#xff1a; 2.1、Volo.Abp.Autofac 2.2、Serilog.Sinks.File 2.3、Serilog.Sinks.Async 2.4、Serilog.Extensions.Logging 2.5、Serilog.Extensions.Hos…

算法沉淀——滑动窗口(leetcode真题剖析)

算法沉淀——滑动窗口 01.长度最小的子数组02.无重复字符的最长子串03.最大连续1的个数 III04.将 x 减到 0 的最小操作数05.水果成篮06.找到字符串中所有字母异位词07.串联所有单词的子串08.最小覆盖子串 滑动窗口算法是一种用于解决数组或列表中子数组或子序列问题的有效技巧。…

【C++版】排序算法详解

目录 直接插入排序 希尔排序 选择排序 冒泡排序 堆排序 快速排序 hoare法 挖坑法 前后指针法 非递归版本 快速排序中的优化 归并排序 递归版本 非递归版本 计数排序 总结 直接插入排序 直接插入排序的思想是&#xff1a;把待排序的记录按其关键码值的大小逐个插入…

【Java程序设计】【C00174】基于SSM在线医院管理系统(论文+PPT)

基于SSM在线医院管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的在线医院管理系统 本系统分为前台系统、后台管理员、后台医生以及后台用户4个功能模块。 前台系统&#xff1a;当游客打开系统的网址后&#xf…

flask基于python的个人理财备忘录记账提醒系统vue

在当今高度发达的信息中&#xff0c;信息管理改革已成为一种更加广泛和全面的趋势。 “备忘记账系统”是基于Mysql数据库&#xff0c;在python程序设计的基础上实现的。为确保中国经济的持续发展&#xff0c;信息时代日益更新&#xff0c;蓬勃发展。同时&#xff0c;随着信息社…

在 Android 中使用 C/C++:初学者综合指南

在 Android 中使用 C/C&#xff1a;初学者综合指南 一、为什么有人在他们的 Android 项目中需要 C/C 支持&#xff1f;二、了解 C 如何集成到 Android 应用程序中三、C和Java程序的编译3.1 Java3.2 Android ART 和 DEX 字节码 四、使用 JNI 包装 C 源代码五、CMake和Android ND…