Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

news2024/11/24 8:48:48

文章目录

  • 背景
  • 新建工程
  • 拷贝/编写C/C++代码
  • 编写CMake配置文件
  • 写Java代码加载动态/静态库
  • java转换c++,c++转java
  • native层打印日志
  • Android去调用Java层的native方法
  • 对外提供.so/.a库 + jar包
  • 检查APK里面是否已经被正常包含.so/.a
  • 完成

背景

突然想起做了这么久的JNI开发,却没有分享过相关内容。
今天就公司刚做完不久的一个项目来剖析,作为JNI的一个入门小实践。内容会做一些抽象,隐藏掉细节的业务部分。希望对刚学习JNI和要边学边开发的同学有所帮助。

新建工程

第一步就是新建一个C/C++工程:
在这里插入图片描述
一开始会有一个cpp目录,里面默认有CMakeLists.txt和一个.cpp文件:
在这里插入图片描述

拷贝/编写C/C++代码

通常来说C代码是由别的开发小组提供,我们负责写JNI代码,让Java层去调用C/C++的代码。
我们要做的是把C代码copy到cpp这个目录里面。

编写CMake配置文件

然后根据源代码去做一些CMake的配置,我这个项目是使用一个CMake文件去配置所有代码。想要更加灵活布局的话,也可以每个目录写一个Cmake文件,然后合并到根目录的Cmake中。
在这里插入图片描述
C代码的目录:
在这里插入图片描述
这个时候去编译,看能不能通过。
不能通过的话,说明CMake写的有问题,可以参考:Cmake专栏

写Java代码加载动态/静态库

上面搞定之后,可以正式开始写代码了。
写一个Java类用作加载原生库,可以是.so 或者.a。不了解的动态库和静态库的同学可以参考:Linux专栏
在这里插入图片描述
当没有实现C++方法的时候,方法名是报红的。点击:ALT+Enter可以自动生成对应的JNI方法,点击坐标的C++标记即可跳过去:
在这里插入图片描述
它会根据java的包名自动生成JNI方法名,方法名是由Java+包名+类名+方法名组合而成的。

需要注意的是:假如java改了包名或者类名,这个JNI方法也需要修改,不然无法映射链接过来。

java转换c++,c++转java

下面是示例代码,供参考:
C代码:

int ProcessInject(const char *ipeks,  unsigned char *ipek_mkey, char *fkey_fpath, unsigned char *fk_mkey, char *datetime);

JNI代码:

extern "C"
JNIEXPORT jint JNICALL
Java_com_xxx_nativelib_NativeLib_processInject(JNIEnv *env, jobject thiz, jbyteArray ipeks,
                                               jbyteArray ipek_mkey, jbyteArray fkey_fpath,
                                               jbyteArray fk_mkey, jbyteArray date_time) {
    jbyte *ipeks_c = env->GetByteArrayElements(ipeks, nullptr);
    jbyte *ipek_mkey_c = env->GetByteArrayElements(ipek_mkey, nullptr);
    jbyte *fkey_fpath_c = env->GetByteArrayElements(fkey_fpath, nullptr);
    jbyte *fk_mkey_c = env->GetByteArrayElements(fk_mkey, nullptr);
    jbyte *datetime_c = env->GetByteArrayElements(date_time, nullptr);

    int result = ProcessInject((const char *) ipeks_c, (unsigned char *) ipek_mkey_c,
                           (char *) fkey_fpath_c, (unsigned char *) fk_mkey_c, (char *) datetime_c);
    // Release resources
    env->ReleaseByteArrayElements(ipeks, ipeks_c, 0);
    env->ReleaseByteArrayElements(ipek_mkey, ipek_mkey_c, 0);
    env->ReleaseByteArrayElements(fkey_fpath, fkey_fpath_c, 0);
    env->ReleaseByteArrayElements(fk_mkey, fk_mkey_c, 0);
    env->ReleaseByteArrayElements(date_time, datetime_c, 0);

// 返回结果给 Java
    return result;
}

int类型的无需转换,直接使用即可。
Java的byte类型需要先转为jbyte* 类型,传入C/C++代码的时候根据实际情况强制。其它类型的话,
可以点jni.h头文件进去查看:
在这里插入图片描述
或者直接查看JNI关机文档,需要一定英文水平。

 jbyte *ipeks_c = env->GetByteArrayElements(ipeks, nullptr);
 //使用ipeks_c ,dosomething 
 ...
  // Release resources
    env->ReleaseByteArrayElements(ipeks, ipeks_c, 0);

传入并使用对象类型的时候,记得用完立刻释放,不然有时候需要循环的业务逻辑会消耗掉JNI可以开辟的空间,听说最多是五百个对象。没有实际考察和测试,供参考。

至于JNI使用的栈内存的原理我在公司部分已经做过一期技术分享。后续有时间会做相应的分享到网上,可以先关注一波这个账号。

关于ReleaseByteArrayElements的最后一个参数,绝大部分场景使用0就OK,意思是他会把C/C++源码对这个变量所做的修改刷新回去给Java层。

还有一个点要注意,就是Java层的byte是有符号的,可以是负数,C/C++层的unsigned char是无符号的。写业务的时候需要注意这个点,要了解清楚。

native层打印日志

打印日志可以直接copy这个,Google那边薅过来的:

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#ifndef NATIVE_AUDIO_ANDROID_DEBUG_H_H
#define NATIVE_AUDIO_ANDROID_DEBUG_H_H
#include <android/log.h>

#if 1

#define MODULE_NAME "xxxxx"
#define LOGV(...) \
  __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
#define LOGD(...) \
  __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
#define LOGI(...) \
  __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
#define LOGW(...) \
  __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
#define LOGE(...) \
  __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
#define LOGF(...) \
  __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)

#else

#define LOGV(...)
#define LOGD(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#define LOGF(...)
#endif

#endif  // NATIVE_AUDIO_ANDROID_DEBUG_H_H

CMake文件里面记得确认已经引入log库。Google爸爸已经在NDK包里面提供,直接引入即可。

在这里插入图片描述
日志的使用示例:在这里插入图片描述

Android去调用Java层的native方法

上面这些搞定之后,直接在MainActivity里面new出前面创建的对象:在这里插入图片描述
调用native函数即可。
在这里插入图片描述

对外提供.so/.a库 + jar包

假如我们在CMake里面写了SHARED的话,编译之后,生成的是动态库,.so文件。
在这里插入图片描述
我们通过JNI代码调用的C/C++源码会被打包成一个.so库。也就是说,我们JNI写完之后提供给别人的就是这个.so库,再加上Java文件,就是这个:
在这里插入图片描述
因为JNI需要类名、包名完全一致,为了避免用户改了它们,导致调用不到JNI代码。我们把所有相关的Java代码打包为一个jar文件包。
不叫了解怎么通过Android studio打包jar文件包的可以参考:jar生成

那么.so在哪里呢?

在这里插入图片描述

检查APK里面是否已经被正常包含.so/.a

再看看APK里面是否已经把动态库文件打包进去了
在这里插入图片描述

完成

好了,这就是写一个JNI工程需要掌握的基本技能。
希望能帮助到大家。

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

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

相关文章

(新手必看)自定义数据传输通信协议+STM32代码详解

前言 本篇博客主要学习和了解一些单片机协议的格式&#xff0c;在对传输大数据或者要求准确性的时候&#xff0c;都需要通过协议来发送接收&#xff0c;下面通过了解协议的基本构成和代码来分析和实现协议的发送和接收。本篇博客大部分是自己收集和整理&#xff0c;如有侵权请联…

图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法

图的搜索&#xff08;二&#xff09;&#xff1a;贝尔曼-福特算法、狄克斯特拉算法和A*算法 贝尔曼-福特算法 贝尔曼-福特&#xff08;Bellman-Ford&#xff09;算法是一种在图中求解最短路径问题的算法。最短路径问题就是在加权图指定了起点和终点的前提下&#xff0c;寻找从…

【教3妹学编程-算法题】下一个更大元素 IV

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开发。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&#x…

计算机网络:应用层(一)

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

STM32单片机项目实例:基于TouchGFX的智能手表设计(4)LPBAM的应用

STM32单片机项目实例&#xff1a;基于TouchGFX的智能手表设计&#xff08;4&#xff09;LPBAM的应用 目录 一、概述 二、LPBAM简介 2.1 支持 LPBAM 的外设 三、LPBAM硬件机制 3.1 电源和时钟架构 3.2 速度限制 3.3 LPGPIO_IOToggle实验 一、概述 STM32U5 系列微控制器基…

星际飞船大战

欢迎来到程序小院 星际飞船大战 玩法&#xff1a;滑动鼠标控制方向&#xff0c;点击鼠标左键射击&#xff0c;生命值100分&#xff0c;被敌船击中减去20&#xff0c; 5次生命复活机会&#xff0c;统计分数&#xff0c;快去星际飞船大战吧^^。开始游戏https://www.ormcc.com/pl…

深度解读 Cascades 查询优化器

数据库中查询优化器是数据库的核心组件&#xff0c;其决定着 SQL 查询的性能。Cascades 优化器是 Goetz 在 volcano optimizer generator 的基础上优化之后诞生的一个搜索框架。 本期技术贴将带大家了解 Cascades 查询优化器。首先介绍 SQL 查询优化器&#xff0c;接着分析查询…

css 表示具有特定类或者其他属性的某种标签类型的元素

需求 通过 css 选择器获取某种标签&#xff08;如&#xff1a;div、input 等&#xff09;具有某个属性&#xff08;如&#xff1a;class、id 等&#xff09;的元素&#xff0c;从而修改其样式。 代码 通过 [标签].[属性] 的方式来获取 <div class"test">&l…

HyperGCN笔记

1 Title HyperGCN: A New Method of Training Graph Convolutional Networks on Hypergraphs&#xff08;Naganand Yadati、Prateek Yadav、Anand Louis、Madhav Nimishakavi、Vikram Nitin、Partha Talukdar&#xff09;【NeurIPS 2019】 2 Conclision This paper proposes H…

gprMax安装步骤

本来是想直接在base环境下直接弄的&#xff0c;但是报错了&#xff0c;因为base环境里的conda版本不匹配&#xff0c;于是重新建立虚拟环境gprMax&#xff0c;如下所示。 然后激活建立的gprMax环境&#xff0c;在gprMax环境中安装git 参考文章&#xff1a; https://zhuanlan.…

mysqldump --set-gtid-purged参数详解

在开启了GTID模式的数据库&#xff0c;使用mysqldump进行部分数据备份的时候&#xff0c;经常会遇到如下警告 Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of t…

『 Linux 』进程地址空间概念

文章目录 &#x1fad9; 前言&#x1fad9; 进程地址空间是什么&#x1fad9; 写时拷贝&#x1fad9; 可执行程序中的虚拟地址&#x1fad9; 物理地址分布方式 &#x1fad9; 前言 在c/C中存在一种内存的概念; 一般来说一个内存的空间分布包括栈区,堆区,代码段等等; 且内存是…

智慧机房与3D机房动环监控系统的应用

智慧机房是什么&#xff1f; 智慧机房是集采集信息、实时监控、数据分析、统一管理、故障告警等功能于一体的全方位、立体化的智能环境监控系统&#xff0c;构建物联网、大数据和云计算背景下现代企业的“数据心脏”。它能为机房管理者呈现细致入微的关键性数据&#xff0c;优…

Error: Failed to resolve vue/compiler-sfc——vite项目启动报错——npm run serve

运行项目时&#xff0c;报错如下&#xff1a; Error: Failed to resolve vue/compiler-sfc 根据报错信息的提示&#xff1a;vue的版本必须大于3.2.25&#xff0c;经过查看package.json文件&#xff0c;可以看到vue的版本为3.2.36&#xff0c;是满足条件的。 因此考虑缓存问题&…

Git 硬重置之后恢复历史提交版本

****硬重置之前一定要备份分支呀&#xff0c;谨慎使用硬重置&#xff0c;特别是很多人一起使用的分支**** 如果你在reset的时候选择了Hard选项&#xff0c;也就是硬重置 重置完且push过&#xff0c;那么被你本地和远端后面的提交记录肯定就会被抹去。 解决办法&#xff1a; …

BearPi Std 板从入门到放弃 - 先天神魂篇(1)(RT-Thread 指令点亮LED)

简介 使用 BearPi IOT Std板&#xff0c; 开发板简单信息 主芯片: STM32L431RCT6 串口: Usart1 USER LED : PC13 E53_SC1 扩展板与主板连接: I2C : I2C1 (光照强度传感器&#xff1a;BH1750) LED: PB9RT-Thread 创建线程 线程的管理方式 添加用户代码 main.c #include <…

从零构建属于自己的GPT系列6:模型本地化部署2(文本生成函数解读、模型本地化部署、文本生成文本网页展示、代码逐行解读)

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在PyCharm中进行 本篇文章配套的代码资源已经上传 从零构建属于自己的GPT系列1&#xff1a;数据预处理 从零构建属于自己的GPT系列2&#xff1a;模型训…

adb命令学习记录

1、 adb ( android debug bridge)安卓调试桥&#xff0c;用于完成电脑和手机之间的通信控制。 xcode来完成对于ios设备的操控&#xff0c;前提是有个mac电脑。 安卓系统是基于linux内核来进行开发的。 2、adb的安装: 本身 adb是 android SDK 其中自带的工具&#xff0c;用于完…

山西电力市场日前价格预测【2023-12-09】

1.日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-09&#xff09;山西电力市场全天平均日前电价为366.40元/MWh。其中&#xff0c;最高日前电价为629.26元/MWh&#xff0c;预计出现在08:00。最低日前电价为216.58元/MWh&#xff0c;预…

PySpark大数据处理详细教程

欢迎各位数据爱好者&#xff01;今天&#xff0c;我很高兴与您分享我的最新博客&#xff0c;专注于探索 PySpark DataFrame 的强大功能。无论您是刚入门的数据分析师&#xff0c;还是寻求深入了解大数据技术的专业人士&#xff0c;这里都有丰富的知识和实用的技巧等着您。让我们…