掌握 Android JNI 基础

news2024/11/23 12:17:19

写在前面

最近在看一些底层源码,发现 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);
}

最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

类和对象 第六部分 继承 第一部分:继承的语法

一.继承的概念 继承是面向对象的三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图&#xff1a; 我们可以发现&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性&#xff0c;这个时候&#xff0c;我们可以讨论利用继承的技术&#xff0c;…

使用antdesign3.0、echarts制作固定资产后台管理系统原型

学了半个月Axure,周末用半天时间&#xff0c;照着网上的模板做了一个固定资产后台管理系统的原型。重点是内联框架的使用&#xff0c;和对echarts表格js代码的调试。原型链接&#xff1a;https://qoz5rv.axshare.com 资产管理系统

qt 动态添加多个button按钮,并添加单击响应

qt动态添加多个button按钮简单&#xff0c;难题是如何对动态的按钮添加响应函数&#xff0c;本文解决方案有两个 方法一&#xff1a;使用信号-槽函数 QString strTemp;int nCol 6;//一行有6个for(int i 0; i < CZList.size(); i){int ii i / nCol;int jj i % nCol;strT…

Qt+css绘制标题

之前学过html和小程序&#xff0c;帮老师做项目的时候也用过vue&#xff0c;在想qt绘制界面是不是也可以使用css,然后查了一些资料&#xff0c;绘制了一个标题&#xff0c;准备用到智能家居的上位机上面。 成果 源码 重写了paintEvent函数和TimeEvent函数&#xff0c;一个用于绘…

Next.js如何正确处理跨域问题?

以前一直使用Vue来写前端。去年下半年接手了一个基于React Next.js的项目&#xff0c;于是顺带学习了一下Next.js。由于Next.js的特点&#xff0c;这个项目的前后端是放在一起的。一开始没什么问题&#xff0c;看了半天文档就上手了。 上周我们需要在另一个网页项目中&#x…

基于DataX完成数据导入-仅新增方案

仅新增方式: 订单退款表为例, 探讨如何完成仅新增方式导入操作 从业务库将数据导入到ODS层, 分为 首次导入和增量导入两部分, 其中首次导入指的第一次建表, 导入数据, 此时一般都是全量导入, 后续每一天都是采用增量导入的方式, 当前项目, 增量模式: T1(当天处理都是上一天的数…

Vue中嵌入原生HTML页面

Vue中嵌入html页面并相互通信 需求&#xff1a;b2b支付需要从后获取到数据放到form表单提交跳转&#xff0c;如下&#xff1a; 但是vue目前暂时没找到有类似功能相关文档&#xff0c;所以我采用iframe嵌套的方式 1. Vue中嵌入Html <iframe src"/static/gateway.htm…

储能柜控制单元|EsccUnit8300储能柜控制单元功能简介及定制开发|储能EMS能量控制单元|储能控制单元|储能柜EMS系统|储能协调控制器

储能柜控制单元&#xff5c;EsccUnit8300储能柜控制单元功能简介及定制开发|储能EMS能量控制单元|储能控制单元|储能柜EMS系统|储能协调控制器 一&#xff1a;什么叫储能柜 Energy storage cabinet 储能柜包含柜体、由池组单元、由池管理单元、储能变流器、控制单元、消防单元…

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费

文章目录 一、121、LeetCode买卖股票的最佳时机1.1 动态规划1.2 动态规划-滚动数组 二、122、买卖股票的最佳时机 II三、123、买卖股票的最佳时机 III七、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、121、LeetCode买…

【科技素养题】少儿编程 蓝桥杯青少组科技素养题真题及解析第23套

少儿编程 蓝桥杯青少组科技素养题真题及解析第23套 1、英国计算机科学家艾伦图灵于 1950 年提出了著名的“图灵测试”,用于判断计算机是否具有智能。“图灵测试”是通过()的方法进行判断的 A、让两台计算机对话 B、让人类与计算机对话 C、给计算机出题 D、让计算机分辨图…

Logstash 7.7.1版本安装系统梳理

前言 上一篇文章介绍了 《ElasticSearch7.7.1集群搭建 & Kibana安装》&#xff0c;今天说一下 Logstash的安卓和配置&#xff1b; Logstash是一个开源的数据收集引擎&#xff0c;具有实时管道功能。它可以动态地将来自不同数据源的数据统一起来&#xff0c;并将数据标准化…

【数据结构】双向带头循环链表实现及总结

简单不先于复杂&#xff0c;而是在复杂之后。 文章目录 1. 双向带头循环链表的实现2. 顺序表和链表的区别 1. 双向带头循环链表的实现 List.h #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h>typede…

Springboot-SpringCloud学习

文章目录 web项目开发历史 SpringBootSpring以及Springboot是什么微服务第一个Springboot项目配置如何编写 yaml自动装配原理集成 web开发&#xff08;业务核心&#xff09;集成 数据库 Druid分布式开发&#xff1a;Dubbo&#xff08;RPC&#xff09; zookeeperswagger&#x…

免费的ppt网站分享

前言 相信大学生们深有体会&#xff0c;对于学校而言&#xff0c;好像是任何活动都需要我们做ppt&#xff0c;当你拿着自己辛苦做的ppt去展示现场的时候&#xff0c;你看到别人的ppt比你的还好&#xff0c;此时心情就是毙&#xff0c;当你知道人家不过是仅仅的1个小时不到就完成…

Springboot集成Javamelody

JavaMelody的目标是监视QA和生产环境中的Java或Java EE应用服务器。它不是模拟用户请求的工具&#xff0c;而是根据用户对应用程序的使用情况来衡量和计算应用程序实际操作的统计信息的工具。JavaMelody主要基于请求统计和演化图。 它允许改进QA和生产中的应用程序&#xff0c…

海外云手机对于亚马逊卖家的作用

近年来&#xff0c;海外云手机作为一种新型模式迅速崭露头角&#xff0c;成为专业的出海SaaS平台软件。海外云手机在云端运行和存储数据&#xff0c;通过网页端操作&#xff0c;将手机芯片放置在机房&#xff0c;通过网络连接到服务器&#xff0c;为用户提供便捷的上网功能。因…

WebService的services.xml问题

WebService有多种实现方式&#xff0c;这里使用的是axis2 问题&#xff1a; 在本地开发&#xff0c;访问本地的http://localhost:8080/services/ims?wsdl&#xff0c;正常访问 但是打成jar包&#xff0c;不管是linux还是window启动&#xff0c;都访问不到&#xff0c;报错…

双创竞赛项目申报:Java + Spring Boot的实战指南

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

2024前端面试总结—JS篇(文档持续更新中。。。)

1、Event Loop&#xff08;事件循环&#xff09;机制 JS是单线程的非阻塞语言 为什么是单线程&#xff08;如果js是多线程&#xff0c;那么两个线程同时对同一个Dom进行操作&#xff0c;一个增一个删&#xff0c;浏览器该如何执行&#xff1f;&#xff09; 非阻塞&#xff08;…

如何线上发布寒假作业,让学生在线确认签收?

老师们可以利用易查分制作一个寒假作业查询系统&#xff0c;在线发布学生的寒假作业&#xff0c;让学生本人在线上获取寒假作业&#xff1b;如果希望由学生本人进行签收&#xff0c;还可通过手写签名功能来进行确认&#xff0c;确保由学生本人收到寒假作业。 &#x1f4cc;使用…