深入理解JNI

news2025/1/10 15:59:07

文章目录

  • 1.JNI
    • 1.1 JNI实现步骤
    • 1.2 NDK
    • 1.3 CMakeList.txt
    • 1.4 HelloWorld
  • 2. native-lib.cpp
    • 2.1 调用java静态方法
    • 2.2 调用java实例方法
    • 2.3 创建引用
  • 3.CMake
  • 4.JNI使用全解
    • 4.1 JNI 类型签名介绍
    • 4.2 abi
    • 4.3 so文件

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

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

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

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.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'
            }
        }
    }
    
  4. 在应用中初始化库文件,并创建 native 方法,选择合适的地方调用该方法运行即可

    // src/main/java/com/monk/cpp/MainActivity.kt
    compaion object{
        init{
            // CMakeLists.txt中声明的库名称
            Syste.loadLibrary(libname:"cpp")
        }
    }
    
    // 创建完成后,编译器会自动提示需要在创建一个与之对应的 c++ 方法
    external fun stringFromJni():String
    
  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());
    }
    

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());
}

其中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"
        }
    }

对应的 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;
}

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)"
    }
}

对应的 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, "<init>", "(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;
}

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;
}

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


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);
}

4.1 JNI 类型签名介绍

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

(参数1类型标示;参数2类型标示;...参数n类型标示;)返回值类型标示
例子:(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V

当参数的类型是引用类型时,其格式是 “L包名;”,其中包名中的 "." 换成 "/",比如 Ljava/lang/String;表示一个 Java String 类型。

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'
            }
        }
}

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

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

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

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

相关文章

软件测试(四)黑盒,白盒,灰盒测试

黑盒测试 测试人员不关注代码内部实现&#xff0c;通过一些科学的手段&#xff0c;向测试系统发起测试数据&#xff0c;关注执行结果是否和预期结果一直 白盒测试 测试人员关注代码内部实现&#xff0c;它一般用来分析程序的内部结构&#xff0c;针对程序的逻辑结构来设计测…

Zookeeper 和 Kafka 工作原理及如何搭建 Zookeeper集群 + Kafka集群

目录 1 Zookeeper 1.1 Zookeeper 定义 1.2 Zookeeper 工作机制 1.3 Zookeeper 特点 1.4 Zookeeper 数据结构 1.5 Zookeeper 应用场景 1.6 Zookeeper 选举机制 2 部署 Zookeeper 集群 2.1 安装前准备 2.2 安装 Zookeeper 3 Kafka 3.1 为什么需要消息队列&#xff08;…

Linux-git

文章目录 git简介git常用命令配置初始化仓库将文件添加到暂存区将暂存区文件加入版本库对比工作区某文件和暂存区中的区别将暂存区的文件移除但git仍管理将文件移除暂存区并且git不再管理查看版本库切换到之前的版本恢复文件持久化 云端将本地的项目推送到远程仓库将远程仓库的…

使用RestSharp和C#编写程序

以下是一个使用RestSharp和C#编写的爬虫程序&#xff0c;用于爬取www.zhihu.com上的视频。此程序使用了https://www.duoip.cn/get_proxy来获取代理IP。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks…

关于利用webase-front节点控制台一键导出的java项目解析

搭建区块链系统和管理平台分别用的的fisco、webase。 关于我们在利用java开发DApp(去中心化引用)&#xff0c;与区块链系统交互&#xff0c;可以用: 1.webase前置服务给开发者提供的api&#xff1a;我们在搭建好fisco链之后&#xff0c;在搭一个webase-front服务&#xff0c;我…

Ceph存储

数据存储类型 块存储 存储设备与客户端主机是 一对一 的关系&#xff0c;块存储设备只能被一个主机挂载使用&#xff0c;数据以块为单位进行存储的&#xff0c;典型代表&#xff1a;硬盘 文件存储 一对多&#xff0c;能被多个主机同时挂载/传输使用&#xff0c;数据以文件的…

新年学新语言Go之四

一、前言 任何编程语言都有类型系统&#xff0c;类型系统解决了数据的存取问题&#xff0c;它决定了使用这个类型需要开辟内存空间大小以及数据是如何存放的&#xff0c;也解决如何读出数据&#xff0c;因为在内存中相同二进制值不同类型的含义是不一样的&#xff0c;关于Go基…

单链表的相关操作(初阶)

链表的概念 链表是线性表的一种&#xff0c;它是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻 辑顺序是通过链表中的指针链接次序实现的 。其实链表就相当于一列火车&#xff1a; 链表的结构跟⽕⻋⻋厢相似&#xff0c;淡季⻋厢会相应减少&#xff0c…

再添合作 | 大势智慧与长沙市规划信息服务中心签订战略合作协议

10月18日&#xff0c;武汉大势智慧科技有限公司&#xff08;以下简称&#xff1a;大势智慧&#xff09;与长沙市规划信息服务中心&#xff08;以下简称&#xff09;战略合作签约仪式在长沙举行。大势智慧CTO张帆与长沙市规划信息服务中心生产经营总监杨凤京代表双方签署战略合作…

如何处理前端无障碍(Accessibility)?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

酷开会员值得回味的经典老剧还记得吗?酷开系统家庭影院带你重温

那些年&#xff0c;大家的娱乐生活主要集中在那一台9寸的黑白电视机中&#xff1b;那些年&#xff0c;家家户户的孩子们晚上都会聚到电视机前欢声笑语&#xff1b;那些年&#xff0c;是诸多经典的电视剧陪伴了很多人的闲暇时光……那些年陪伴我们成长&#xff0c;在记忆中熠熠生…

向量数据库Transwarp Hippo1.1多个新特性升级,帮助用户实现降本增效

例如,当查询“A公司业务发展情况”时,通过向量检索可以检索出A公司“主要业务”、“经营模式”、“财务情况”、“市场地位”等信息,通过全文检索可以检索出知识库中和关键字“业务”、“发展”相关的结果作为补充,通过将两者检索的结果进行结合,可以使得大模型回答的结果…

nexus私服安装

1.将文件上传到linux服务器中 2.解压、重命名 tar -zxvf nexus-3.7.1-02-unix.tar.gz //解压 mv nexus-3.7.1-02 nexus //重命名 3.自定义配置虚拟机可打开 nexus.vmoptions 文件进行配置 如果Linux硬件配置比较低的话&#xff0c;建议修改为合适的大小&…

前端(十九)——vue/react脚手架的搭建方式

&#x1f604;博主&#xff1a;小猫娃来啦 &#x1f604;文章核心&#xff1a;前端&#xff08;十九&#xff09;——vue/react脚手架的搭建方式 文章目录 前言Vue脚手架搭建方法Vue CLI脚手架Vite脚手架其他方式 React脚手架搭建方法Create React App脚手架Vite脚手架其他方式…

element 日期选择器禁止选择指定日期前后时间

画圈重点&#xff1a;disabledDate的写法要用箭头函数&#xff0c;不能用普通函数写法&#xff0c;否则this指向就错了&#xff0c;会报 undefined <el-date-picker v-model"time" type"date" value-format"yyyy-MM-dd" :…

使用CPR库和Python编写程序

以下是一个使用CPR库和Python编写的爬虫程序&#xff0c;用于爬取。此程序使用了proxy的代码。 import requests from cpr import CPR ​ def get_proxy():url "https://www.duoip.cn/get_proxy"headers {"User-Agent": "Mozilla/5.0 (Windows NT …

C++标准模板(STL)- 类型支持 (数值极限,min,lowest,max)

数值极限 提供查询所有基础数值类型的性质的接口 定义于头文件 <limits> template< class T > class numeric_limits; numeric_limits 类模板提供查询各种算术类型属性的标准化方式&#xff08;例如 int 类型的最大可能值是 std::numeric_limits<int>::ma…

01、MySQL-------性能优化

目录 一、影响性能的相关因素存储过程&#xff1a; 二、sql优化1>、Mysql系统架构2>、引擎区别&#xff1a; 3>、索引1、什么是索引&#xff1f;联合主键索引理解&#xff1a;索引长度理解&#xff1a;什么是慢查询&#xff1f; 1&#xff09;、索引理解2&#xff09;…

Win系统VMware虚拟机安装配置(一)(附激活码安装包)

VMware软件包&#xff08;Mac和Win&#xff09;提取码:hzxyhttps://www.123pan.com/s/JRpSVv-vKnjv.html 一、VMware 安装 一台电脑本身是可以装多个操作系统的&#xff0c;但是做不到多个操作系统切换自如&#xff0c;所以我们 需要一款软件帮助我们达到这个目的&#xff0c…

MIKE水动力笔记16_MIKE中的u、v、Speed、Direction之间的关系

本文目录 前言Step 1 MIKE中u、v、Speed、Direction的界定Step 2 从MIKE中导出u、v、Speed、Direction数据Step 3 数据导入Excel验证 前言 这两天饶有兴趣的做了一下关于MIKE中u、v、Speed、Direction之间关系的小测试&#xff0c;其实主要是为了探究利用u、v得到的角度和Dire…