一看就会的jni,不会你来打我!

news2025/1/23 2:13:55

环境配置

Android Studio,这个不多说了。

简单说一下NDK的下载和环境变量,方便在Terminal里使用命令(mac版)。

下载

1.可以通过Android Studio内置的Settings-Android SDK-SDK Tools安装NDK,下载目录为

/Users/mac-xxx(Username)/Library/Android/sdk/ndk/(NDK Version Number)

2.也可通过官网下载NDK 下载  |  Android NDK  |  Android Developers,选择一个安装目录,方便后续配置环境变量。

环境变量

1.如果是mac,我们要配置到具体的NDK版本目录,方便使用ndk-build打包so库

打开终端,输入open -e .bash_profile打开配置文件

export ANDROID_NDK=/Users/mac-xxx(Username)/Library/Android/sdk/ndk/(NDK Version Number)
export PATH=$PATH:$ANDROID_NDK

执行source .bash_profile使环境变量生效

2.如果是win,我们右键

"我的电脑"—>"属性"—>"高级系统设置"—>"环境变量"—>"系统变量"—>"新建"

%ANDROID_HOME%是我们安卓SDK的默认安装目录

将我们的NDK目录放进去,保存。

打开终端,输入ndk-build,如果有以下输出,则OK。

Android NDK: Could not find application project directory !    Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.    /Users/mac-xxx/Library/Android/sdk/ndk/26.1.10909125/build/core/build-local.mk:151: *** Android NDK: Aborting  . Stop.

JNI编写

1.java端本地代码接口,这里以初始化方法,int,String类型参数为例,列举了三个native接口方法

package com.monke.simplejnidemo;

public class SimpleJniUtils {
    //初始化操作
public static native void init();

    //翻倍一个数字,并且返回
public static native  int doubleData(int data);

    //输入一个字符串,并且返回一个字符串
public static native String testStr(String str);

}

2.生成.h头文件

使用Terminal 将目录先定位到java目录,执行

javah -jni com.monke.simplejnidemo.SimpleJniUtils

确保jdk环境变量已经配置OK。然后在java目录下会生成以包名路径开始的C语言头文件com_monke_simplejnidemo_SimpleJniUtils.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_monke_simplejnidemo_SimpleJniUtils */

#ifndef _Included_com_monke_simplejnidemo_SimpleJniUtils
#define _Included_com_monke_simplejnidemo_SimpleJniUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_monke_simplejnidemo_SimpleJniUtils
 * Method:    init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_monke_simplejnidemo_SimpleJniUtils_init(JNIEnv *, jclass);

/*
 * Class:     com_monke_simplejnidemo_SimpleJniUtils
 * Method:    doubleData
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_monke_simplejnidemo_SimpleJniUtils_doubleData(JNIEnv *, jclass, jint);

/*
 * Class:     com_monke_simplejnidemo_SimpleJniUtils
 * Method:    testStr
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_monke_simplejnidemo_SimpleJniUtils_testStr(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

3.在main目录下,新建jni目录,将第二步生成的.h文件复制进来,并且创建simplejniutils.c文件,编码如下 

#include <com_monke_simplejnidemo_SimpleJniUtils.h> //引入.h头文件

#include <android/log.h>


JNIEXPORT void JNICALL Java_com_monke_simplejnidemo_SimpleJniUtils_init  //从.h文件里复制方法名过来,补起参数和方法体
(JNIEnv *env, jclass j){
    //方便定位函数调用,这里引用Android的log作为输出
     __android_log_print(ANDROID_LOG_INFO, "MainActivity", "Java_com_monke_simplejnidemo_SimpleJniUtils_init");
}


JNIEXPORT jint Java_com_monke_simplejnidemo_SimpleJniUtils_doubleData(JNIEnv *env, jclass j, jint data){
    __android_log_print(ANDROID_LOG_INFO,"MainActivity","Java_com_monke_simplejnidemo_SimpleJniUtils_doubleData value is %d",data);
    return data*2;
}


JNIEXPORT jstring Java_com_monke_simplejnidemo_SimpleJniUtils_testStr(JNIEnv *env, jclass j, jstring jstr){
    //方便打印结果,转char类型const char *str = (*env)->GetStringUTFChars(env, jstr, 0);
    __android_log_print(ANDROID_LOG_INFO,"MainActivity","Java_com_monke_simplejnidemo_SimpleJniUtils_testStr value is %s",str);
    return jstr;
}

4.新建Android.mk文件,主要作用是编译的c文件和生成的so库配置

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# 打印安卓log日志库
LOCAL_LDLIBS := -llog
# 生成的so库名
LOCAL_MODULE := SimpleJni
# 要编译的源文件,如果有多个,以空格隔开
LOCAL_SRC_FILES =: simplejniutils.c
include $(BUILD_SHARED_LIBRARY)

5.新建Application.mk文件,主要作用是配置生成so库的cpu目录

APP_ABI := all

# or 具体某些cpu架构

# 常用的安卓cpu架构目录
# armeabiv-v7a: 第7代及以上的 32位ARM 处理器
# arm64-v8a: 第8代、64位ARM处理器
# armeabi: 第5代、第6代的32位ARM处理器,早期的手机在使用,现在基本很少了。
# x86: Intel 32位处理器,在平板、模拟器用得比较多。
# x86_64: Intel 64位处理器,在平板、模拟器用得比较多

# APP_ABI := armeabi-v7a arm64-v8a x86 x86_64

6.进入项目main目录,执行ndk-build,生成so库

自测用例

在上一步,我们已经可以成功生成so库了,下面在java项目中,如何使用呢?

第一步:回到SimpleJniUtils.java类中,补充so库的调用

public class SimpleJniUtils {
    ……
    static {
        System.loadLibrary("SimpleJni");
    }
    ……
}

第二步:在app/build.gradle下,加入so库路径,否则运行会找不到so库

android {
……
sourceSets{
    main{
            jni.srcDirs=[] //不使用gradle编译本地c/c++代码
            jniLibs.srcDirs = ['libs','src/main/libs']//加载so库 lib是第三方so src/main/libs是准备生成的so库位置
    }
}
……
}

java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader…… couldn't find "libSimpleJni.so"

第三步:调用native方法,验证结果

可以看到,jni的执行和java层的结果打印都是ok的。

第三方so库融合与调用

基于以上步骤,我们已经生成了一个so库,并且调用验证是没问题的。那么如果是一个第三方的so库,我们应该如何调用呢?或者基于c上述流程是闭环的,如果是c++的库,又该如何处理?从这个思考出发,下面进行so库的融合。

新建一个Android项目,在main目录下,新建jni目录,再新建simplejinlib目录,把上面我们生成的so库以及so库的.h头文件放进来,目录结构如下:

在simplejinlib目录下,新建Android.mk文件,内容如下:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# so库的名字
LOCAL_MODULE := SimpleJni
# so库的路径
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSimpleJni.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
include $(PREBUILT_SHARED_LIBRARY)

在app的build.gradle目录下,加入以下代码

android {
    
     # ……
    sourceSets{
        main{
            jni.srcDirs=[]     //不使用gradle编译本地c/c++代码
            jniLibs.srcDirs = ['libs','src/main/libs']    //加载so库 lib是第三方so   src/main/libs 是准备生成的so库位置
        }
    }
    # ……
}

基于以上,我们已经把第三方so库导入成功了,接下来需要完善java层native接口,调用so库内容即可。

第一步:编写java(这里以调用so库的翻倍函数为例)

package com.monke.sotest;

public class Utils {
    public static native  int useSoDoubleData(int data)
}

第二步:导出jni所需的.h头文件

使用Terminal 将目录定位到java目录,执行

javah -jni com.monke.sotest.Utils

 将生成的com_monke_sotest_utils.h文件,放入jni目录下,并#include第三方so库的.h头文件,内容如下: 

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include "simplejinlib/com_monke_simplejnidemo_SimpleJniUtils.h"
/* Header for class com_monke_sotest_Utils */

#ifndef _Included_com_monke_sotest_Utils
#define _Included_com_monke_sotest_Utils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_monke_sotest_Utils
 * Method:    useSoDoubleData
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_monke_sotest_Utils_useSoDoubleData(JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif

新建externdemojin.cpp(这是一个c++文件),在这里调用第三方so库的函数,内容如下:

#include <com_monke_sotest_Utils.h>

JNIEXPORT jint Java_com_monke_sotest_Utils_useSoDoubleData(JNIEnv *env, jclass j, jint data){
    //调用第三方so库的翻倍函数,注意类名路径和.h保持一致
return Java_com_monke_simplejnidemo_SimpleJniUtils_doubleData(env,j,data);
}

新建Android.mk,声明ndk要编译的c/c++文件,引用的第三方so库名称,导出的so库名称,日志等

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# LOCAL_SHARED_LIBRARIES 引用的第三方so库的名字 如果有多个用 \ 分割
LOCAL_SHARED_LIBRARIES :=SimpleJni
LOCAL_MODULE    := ExternJni
# LOCAL_SRC_FILES .c源文件
LOCAL_SRC_FILES := externdemojin.cpp

LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/simplejinlib/Android.mk

新建Application.mk文件,作用是so库的架构支持

#APP_ABI := all
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64

这样,我们融合so库的准备工作就做好了,再看一下现在的项目目录

 接着,定位main目录,执行ndk-build,结果如下:

 在这里,也生成了两个so库,一个是原第三方的libSimpleJni.so,一个是新生成的libExternJni.so

最后,我们在java层,静态导入so库,测试验证,发现,融合三方so库,c++调用c层也是没问题的。

package com.monke.sotest;

public class Utils {

    static {
        System.loadLibrary("ExternJni");
    }
    public static native  int useSoDoubleData(int data);
}

总结

基于以上步骤,我们实现了java到jni-到c层的调用,再扩展到java到jni到c++再到c的so库的调用。其中,在jni层,我们引用了安卓的log库,输出日志,方便定位问题。如果是c++层,在函数传参时,需要类型转换,也很简单。

jni中的jstring转char

const char* str = env->GetStringUTFChars(jstr, NULL);

其中,jstr为需要转换的jstring类型变量,env为JNIEnv指针。需要注意的是,GetStringUTFChars()函数返回的char*类型指针需要在使用完后调用ReleaseStringUTFChars()函数释放,以避免内存泄漏。具体使用方法如下:

env->ReleaseStringUTFChars(jstr, str);

常见问题

1.Android Studio NDK配置目录为灰色, 且无法选择目录

在local.properties文件里配置,重启IDE即可。

ndk.dir=/Users/mac-xxx(Username)/Library/Android/sdk/ndk/ndk/(NDK Version Number)

2.执行ndk-build报

zsh: command not found: ndk-build

在终端,执行open -e .zshrc 配置ndk环境变量,再运行source ~/.zshrc 使其生效

3.error: expected identifier or '('extern "C" ……之类的错误

因为 extern “C” 是声明给C++用的,如果在安卓里使用C文件,不需要这个声明,去掉即可

4.jni/simplejniutils.c:5:11: error: redefinition of 'jint' as different kind of symbol

JNIEXPORT jint JNICAll Java_com_monke_simplejnidemo_SimpleJniUtils_doubleData

这个错误去掉 JNICAll

工具类

一、在jni中c++层进行log的打印

1、在需要使用log的cpp文件中加入

#include <android/log.h>


2、在需要打印的地方直接调用

%d是数字,%s是string或者char

__android_log_print(ANDROID_LOG_INFO,"test","value is %d\n",a);

二、jni中的jstring转char

在JNI开发中,可以使用GetStringUTFChars()函数将jstring类型转换为char*类型。具体使用方法如下:

const char* str = env->GetStringUTFChars(jstr, NULL);

其中,jstr为需要转换的jstring类型变量,env为JNIEnv指针。需要注意的是,GetStringUTFChars()函数返回的char*类型指针需要在使用完后调用ReleaseStringUTFChars()函数释放,以避免内存泄漏。具体使用方法如下:

env->ReleaseStringUTFChars(jstr, str);

参考

JNI开发(一) 简单的C代码打包成SO库以及项目如何调用SO库_c打包成so-CSDN博客

JNI开发(二) 在JNI开发中调用第三方so库_jni调用第三方so库-CSDN博客 

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

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

相关文章

服务号可以升级为订阅号吗

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;在推送频率上来看&#xff0c;服务号每月能推送四条消息&#xff0c;而订阅号可以每天&#xff08;24小时&#xff09;推送一条消息。如果企业开通公众号的目的是提供服务&#xff0c;例如售前资讯…

WPS文件丢失如何恢复?4个方法教你快速恢复!

“在WPS里保存了一些比较重要的文档&#xff0c;由于某些原因导致文件丢失了&#xff0c;这种情况下&#xff0c;还有办法成功恢复WPS文件吗&#xff1f;” WPS Office是许多用户在电脑上创建和编辑文档的首选工具。然而&#xff0c;有时文件会意外丢失&#xff0c;可能是由于误…

Linux_在命令行中以树状结构显示目录_tree

1、安装tree命令 使用tree命令&#xff0c;可以在命令行中以树状结构显示目录&#xff0c;当你想知道一个路径下文件的结构时十分方便&#xff0c;还有别的选项功能&#xff0c;下面会介绍其中的一些&#xff0c;完整的介绍Linux命令 - tree—LZL在线工具。 sudo apt updates…

头部厂商Q3交付量环比下滑!激光雷达,现实很骨感

由于中国自主品牌车企在高阶智驾赛道上的激进策略&#xff0c;全球激光雷达行业的走势&#xff0c;也无疑受到中国市场的影响。 本周&#xff0c;禾赛科技发布2023年度三季报&#xff0c;季度激光雷达交付量为47,440台&#xff0c;同比增长125.5%&#xff0c;其中ADAS激光雷达交…

mysql---主从复制和读写分离

主从复制 主从复制&#xff0c;修改&#xff0c;表里的数据&#xff1a;主mysql上的数据&#xff0c;新增都会同步到从mysql上面试题&#xff1a;mysql的主从复制的模式&#xff1a; 1、异步复制&#xff1a;mysql的默认复制就是异步复制。只要执行完之后&#xff0c;客户端提…

使用 Redis 构建轻量的向量数据库应用:图片搜索引擎(二)

本篇文章我们来继续聊聊轻量的向量数据库方案&#xff1a;Redis&#xff0c;如何完成整个图片搜索引擎功能。 写在前面 在上一篇文章《使用 Redis 构建轻量的向量数据库应用&#xff1a;图片搜索引擎&#xff08;一&#xff09;》中&#xff0c;我们聊过了构建图片搜索引擎的…

如何在10亿级别用户中检查用户名是否存在?

题目 不知道大家有没有留意过&#xff0c;在使用一些app注册的时候&#xff0c;提示你用户名已经被占用了&#xff0c;需要更换一个&#xff0c;这是如何实现的呢&#xff1f;你可能想这不是很简单吗&#xff0c;去数据库里查一下有没有不就行了吗&#xff0c;那么假如用户数量…

windows usbip(瑞芯微windows开发)

Rockchip RK3588 windows开发 安装usbipd 除usbipd之外&#xff0c;还有一个usbip仓库可以参考usbip-win&#xff0c;但是相对麻烦一些 windows install winget install usbipdShare Devices usbipd --help usbipd list usbipd bind --busid<BUSID>Remote Connectin…

mysql统计整个数据库记录条数

SELECTSUM(TABLE_ROWS) FROM(SELECTTABLE_NAME,TABLE_ROWSFROMINFORMATION_SCHEMA.TABLESWHERETABLE_SCHEMA 数据名&#xff0c;其他不变) t;效果如下&#xff1a;

算法-贪心算法-简单-买卖股票的最佳时机

记录一下算法题的学习4 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这…

【设计一个缓存--针对各种类型的缓存】

设计一个缓存--针对各种类型的缓存 1. 设计顶层接口2. 设计抽象类 -- AbstractCacheManager3. 具体子类3.1 -- AlertRuleItemExpCacheManager3.2 -- AlertRuleItemSrcCacheManager 4. 类图关系 1. 设计顶层接口 // 定义为一个泛型接口,提供给抽象类使用 public interface Cach…

Linux命令(123)之mail

linux命令之mail 1.mail介绍 linux命令mail是用来发送邮件 2.mail用法 mail -s "Subject" EmailAddress < EmailMessage 参数说明-s指定邮件主题SubjectEmailAddress指定邮件地址EmailMessage指定邮件内容3.实例 3.1.配置QQ邮件发送 1.安装sendmail/mailx …

ThreadLocal这点牛角尖总算是给我钻明白了

前言 这个问题算是我的一个羞耻点&#xff0c;起源于一次面试中&#xff0c;面试官问ThreadLocal的底层实现是啥&#xff0c;我那时候一直以为ThreadLocal是一个类似于Redis一样的独立于线程外的第三方存储容器&#xff0c;如何底层维护了一个Map结构&#xff0c;以线程ID为Key…

iOS性能优化

了解屏幕成像的原理。 有一个电子枪然后在很多横轴方向上 发射电子&#xff0c;不同横轴的电子枪根据显示器中的硬件时钟产生一系列的定时信号&#xff0c;以此来让电子以不同的时间发射出去 这些电子一瞬间的运动形成了一帧动画。 CPU优化&#xff1a; 1.文本计算优化 如果一…

DSVPN简介

定义 动态智能VPN&#xff08;Dynamic Smart Virtual Private Network&#xff09;&#xff0c;简称DSVPN&#xff0c;是一种在Hub-Spoke组网方式下为公网地址动态变化的分支之间建立VPN隧道的解决方案。 目的 越来越多的企业希望建立Hub-Spoke方式的IPSec VPN网络将企业总部…

2760. 最长奇偶子数组 : 抽丝剥茧,图解双指针做法正确性

题目描述 这是 LeetCode 上的 「2698. 求一个整数的惩罚数」 &#xff0c;难度为 「简单」。 Tag : 「双指针」、「滑动窗口」 给你一个下标从 开始的整数数组 nums 和一个整数 threshold。 请你从 nums 的子数组中找出以下标 l 开头、下标 r 结尾 ( ) 且满足以下条件的 最长子…

控制器宕机之SBC相关

简介: 本文主要以FS6500系列展开介绍&#xff0c;介绍深度安全故障(深度睡眠)状态的转换&#xff0c;以及相关寄存器值的变化过程 1.控制器什么时候会因为SBC停止工作? 窗口看门狗未及时喂狗达到故障计数最大值芯片进入某种(deep fail state)模式或LP-DFS模式或LP-Sleep模式…

阿里云的99元服务器和腾讯云的88元云服务器选择哪个?怎么选?

近日&#xff0c;阿里云宣布在2023年双十一优惠活动中推出了一系列降价措施&#xff0c;使得同配置的云服务器比腾讯云更具竞争力。这一消息不仅在云计算领域引起了轰动&#xff0c;更为广大互联网用户提供了更为实惠的选择。 阿里云推出99元一年的服务器&#xff0c;续费价格…

FreeSWITCH案例跟踪之一,sip bye发不出去

报故障的说&#xff0c;网关呼叫fs&#xff0c;网关收不到fs的sip bye Wireshark看call-flow, 是这样的&#xff1a; INVITE里面的contact是<sip:172.23.4.109:5060;transporttcp> 于是Wireshark设置过滤条件为ip.addr 172.23.4.109 and tcp.port 5060 fs tcp连网关被…

基于 HTTP Digest 与 CURL 以及 Requests 的兼容性问题:解决方案与推测原因

在使用Python库requests进行HTTP Digest认证时&#xff0c;我遇到了一个问题。当我使用requests.get()函数时&#xff0c;返回了401 Unauthorized错误&#xff0c;但是当我使用cURL命令时&#xff0c;认证成功。 解决方案&#xff1a; 确认使用的requests版本&#xff1a;我首…