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

环境配置

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博客 

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

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

相关文章

VF01 bapi BAPI_BILLINGDOC_CREATEMULTIPLE修改付款方

系统标准通过函数SD_PARTNER_READ&#xff0c;读取VBPA表销售订单对应的伙伴。 调整通过源代码增强LV60AA01最后位置。

《QT从基础到进阶·二十九》QT,opencv源码调试

有时候我们在使用VS调试程序的bug&#xff0c;但发现程序崩溃的地方并不在我们写的程序中&#xff0c;我们通过调用堆栈发现程序崩溃的地方出现在QT或者opencv等源码中&#xff0c;那么我们怎么能把断点打到这些开源库中&#xff0c;下面提供一种办法&#xff1a; 解决方案–右…

单日充值破6000万、8天收入破亿,小程序短剧的商业真相

进入2023年以来&#xff0c;短剧发展的速度相当惊人。无论是从短视频平台的用户规模来说&#xff0c;还是从短剧内容的商业效益来看&#xff0c;都进入了双增长的狂飙模式。 小程序指的是在一些APP的小程序平台上&#xff08;多为微信端&#xff0c;抖音、快手等平台也有&…

使用requests库解决Session对象设置超时的问题

在requests库的IRC频道中&#xff0c;提出了一个问题&#xff0c;即Session对象在requests库中没有一个可以全局设置的timeout属性&#xff0c;而是需要为每个请求传递timeout值&#xff0c;或者创建一个自定义子类来实现。 为了解决这个问题&#xff0c;可以向Session对象添加…

Apache阿帕奇安装配置

目录 一、下载程序 1. 点击Download 2. 点击Files for Microsoft Windows 3. 点击Apache Lounge 4. 点击httpd-2.4.54-win64-VSI6.zip ​编辑​ 5. 下载压缩包 6.解压到文件夹里 二、配置环境变量 1. 右键我的电脑 - 属性 2. 高级系统设置 3. 点击环境变量 4. 点击系统…

中国芯片金字塔成形,商业化拐点将至

其作始也简&#xff0c;其将毕也钜。 传说埃及用时30年建成左赛尔金字塔&#xff0c;成为亘古不灭的世界奇迹。在今天&#xff0c;中国芯片产业走过8年“国产替代”历程&#xff0c;国产芯片的“金字塔”体系业已初具雏形&#xff0c;展现出蓬勃的发展潜力。 2023年是补全自主…

Linux系统进程与进程间通信

Linux是一个多用户、多任务的操作系统&#xff0c;支持多个进程同时运行。进程是Linux系统中的基本单元&#xff0c;它们负责执行各种任务&#xff0c;如网页浏览、文件下载、程序运行等。在Linux中&#xff0c;进程是由一个或多个线程组成的&#xff0c;线程是进程的基本执行单…

浅谈安科瑞无线测温产品在巴西某工厂的应用

摘 要&#xff1a;高压开关设备是变电站和配电站中保证电力系统安全运行的重要设备之一,因此,开关柜的稳定运行对于整个电力系统有非常重要的意义。设备老化、长期高负荷运行都可能使设备局部温度过高而发生火灾&#xff0c;因此,对变电站内的敏感设备进行温度检测变得尤为重要…

Java实现简单的俄罗斯方块游戏

一、创建新项目 1.首先新建一个项目&#xff0c;并命名为俄罗斯方块。 2.其次新建一个类&#xff0c;命名为Main&#xff0c;或其他的。 二、运行代码 代码如下&#xff1a; package 俄罗斯方块;import java.awt.BorderLayout; import java.awt.Color; import java.awt.Gr…

2024有哪些免费的mac苹果电脑内存清理工具?

在我们日常使用苹果电脑的过程中&#xff0c;随着时间的推移&#xff0c;可能会发现设备的速度变慢了&#xff0c;甚至出现卡顿的现象。其中一个常见的原因就是程序占用内存过多&#xff0c;导致系统无法高效地运行。那么&#xff0c;苹果电脑内存怎么清理呢&#xff1f;本文将…

【机器学习8】采样

1 均匀分布随机数 均匀分布是指整个样本空间中的每一个样本点对应的概率&#xff08;密度&#xff09; 都是相等的。 根据样本空间是否连续&#xff0c; 又分为离散均匀分布和连续均匀分布。编程实现均匀分布随机数生成器一般可采用线性同余法&#xff08;Linear Congruential…

大数据-之LibrA数据库系统告警处理(ALM-12046 网络写包丢包率超过阈值)

告警解释 系统每30秒周期性检测网络写包丢包率&#xff0c;并把实际丢包率和阈值&#xff08;系统默认阈值0.5%&#xff09;进行比较&#xff0c;当检测到网络写包丢包率连续多次&#xff08;默认值为5&#xff09;超过阈值时产生该告警。 用户可通过“系统设置 > 阈值配置…

全功能知识付费变现小程序系统源码 自带流量主 轻松帮你赚钱 带完整搭建教程

大家好啊&#xff0c;今天罗峰要来给大家分享一款全功能知识付费变现小程序源码系统 。近年来互联网技术的快速发展&#xff0c;以及人们对知识付费的需求不断增长。全功能知识付费变现小程序系统源码的出现为大家提供一个全面、高效、安全的解决方案&#xff0c;帮助用户实现知…

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

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

Linux学习教程(第三章 Linux文件和目录管理)1

第三章 Linux文件和目录管理&#xff08;初识Linux命令&#xff09; 对初学者来说&#xff0c;管理 Linux 系统中的文件和目录&#xff0c;是学习 Linux 至关重要的一步。 为了方便管理文件和目录&#xff0c;Linux 系统将它们组织成一个以根目录 / 开始的倒置的树状结构。Li…

VueEcharts的使用简解以及常用网站

目录 一&#xff1a;前言 二&#xff1a;实现 1、安装echarts依赖 2、创建图表 1&#xff09;全局引入 2&#xff09;按需引入 三&#xff1a;结尾 一&#xff1a;前言 VueEcharts 是项目开发中可视化的一个重要知识部分。其涵盖了柱状图&#xff0c;饼状图&#xff0c;…

机器学习深度学习服务器推荐

大学生、研究生未免找不到还有的GPU服务器&#xff0c;这边博主推荐 https://featurize.cn?s3d13789cb8184f16bb6133b20c353207 方便&#xff0c;便宜&#xff0c;不会自动删除上传项目文件&#xff0c;支持VScode、pycharm、SSH链接&#xff0c;上传文件速度快。强烈推荐&am…

B031-网络编程 Socket Http TomCat

目录 计算机网络网络编程相关术语IP地址ip的概念InerAdress的了解与测试 端口URLTCP、UDP和7层架构TCPUDPTCP与UDP的区别和联系TCP的3次握手七层架构 Socket编程服务端代码客户端代码 http协议概念Http报文 Tomcat模拟 计算机网络 见文档 网络编程相关术语 见文档 IP地址 …

【Proteus仿真】【STM32单片机】锂电池管理系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1602显示模块、DS18B20温度传感器、PCF8691 ADC模块、按键、LED蜂鸣器模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示温…

2核2G3M带宽云服务器99元(续费同价),阿里云老用户可买!

在阿里云的双11云服务器活动中&#xff0c;用户对轻量服务器2核2G3M带宽和经济型e实例2核2G配置3M带宽特别关注。除了这两款产品&#xff0c;阿里云还提供了其他性价比很高的云服务器配置&#xff0c;让用户有更多的选择。 经济型e实例2核2G3M配置99元一年是适用于个人和普通企…