cmake+JNI调用C/C++开发Base64编码解码APP

本文介绍在Android studio中通过cmake分别实现JNI调用C和JNI调用C++开发Base64编码解码app的方法。阐述了JNI的概念、作用,说明了开发环境,详细介绍了两种调用方式的开发步骤,还针对中文乱码问题给出了C端和Java端的编码转换解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

cmake+JNI调用C/C++开发Base64编码解码APP

声明:本人也是初学者,对于java语言也是刚开始学,下内容可能存在错误希望阅读者指正,部分内容也是参考其他网络文章,但是我找不到文件链接,表示很抱歉,也很感谢指导。

摘要:在Androidstudio中通过cmake分别实现了JNI调用C和JNI调用C++的两种方式实现Base64编码解码app开发

1 JNI
1.1 JNI简介
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

1.2 JNI的作用
JNI可以这样与本地程序进行交互:
  1. 你可以使用JNI来实现“本地方法”(native methods),并在JAVA程序中调用它们。
  2. JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。
JNI是JAVA平台的一个重要特征,使用它我们可以重用以前用C/C++写的大量代码。JNI的部分设计思想来源于Netscape的Java Runtime Interface(JRI)。使用JNI可以结合C++和Java两种语言的不同优势,使得我们的开发产品具有更强大的功能和更好的体验。
一方面,C++具有很好的执行效率和广泛的算法开源库,但是C++的开发成本和维护成本都过高,而另一方面,Java有更广的应用场景(Web开发、服务器开发、移动开发等),Java提供JNI接口,能够使C++代码运行在Java的环境中,使C++具有更好的应用场景,一方面能够将海量的C++开源库进行代码复用,减少了开发成本。

2 Base64开发
2.1 开发环境
Android studio 3.0
JDK:Jdk是Java Development Kit的缩写,顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
NDK:NDK是一系列工具的集合,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
Cmake:是一个跨平台的编译(Build)工具,可以用简单的语句来描述所有平台的编译过程。

2.2 JNI调用C++开发Base64步骤
2.2.1 打开Android studio 新建一个工程base64,注意勾选Include C++ support 选项,勾选后会自动生成makeLists.txt文件
在这里插入图片描述
2.2.2 选择相应的安卓版本
2.2.3 添加一个空活动,择你所需要的C++标准,工程创建完成
可见自动生成了一个用于在应用程序启动时加载“native-lib”(本地)库,一个本地native调用的函数stringFromJNI(),并且在main文件夹下有一个cpp文件夹,在文件夹内的native-lib.cpp就有stringFromJNI()的接口
在这里插入图片描述
在这里插入图片描述
makeLists.txt文件
在这里插入图片描述
add_library( # Sets the name of the library.
native-lib – 设置库的名称

         # Sets the library as a shared library.
         SHARED   --将库设置为共享库。

         # Provides a relative path to your source file(s).
         src/main/cpp/native-lib.cpp  ---提供源文件的相对路径。此处可以根据自己的C/C++文件的具体位置更改设定)

build.gradle出现以下内容
在这里插入图片描述
2.2.4 设计app的应用界面
安卓app应用界面的设计是在app\res\layout\activity_main.xml下完成

2.2.6 编写接口
2.2.6.1 在MainActivity中编写本地调用接口,此时就是调用src/main/cpp/native-lib.cpp中的接口
在这里插入图片描述
2.2.6.2 在src/main/cpp/native-lib.cpp(此时native-lib.cpp是系统默认自动生成我们直接写入C++,不用其他操作)中编写C++源
写好接口函数如下:在这里插入图片描述
同样要加入你需要的.h 头文件,并且在cpp中声明:在这里插入图片描述
2.2.7 So文件生成
Make project一下就可以在cmake文件目录下看到生存了so文件
在这里插入图片描述
2.2.8 获取EditText的输入字符并且输出到TextView在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.2.9 在布局按钮中设置响应,触发解码编码在这里插入图片描述
2.2.10 打包软件生成app
存在中文解码乱码错误!!!

2.3 JNI调用C开发Base64步骤
前期步骤同C++开发,界面完全相同,只是使用C语言进行JNI调用,以下是不同之处
2.3.1 创建c文件
在src/main/cpp目录下创建base64.c、base64.h文件
在这里插入图片描述
2.3.2 更改makeLists.txt使用C源码在这里插入图片描述
在这里插入图片描述
步骤可省略!

2.3.3 编写接口
在MainActivity中编写本地调用接口,此时就是需要调用src/main/cpp/base64.c中的接口
在这里插入图片描述
2.3.4 得到对应.class文件
Build project可在
app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/dqp/base64
下得到MainActivity.class

2.3.5 生成对应的.h文件
使用命令javac -h + .class路径
就可以生成对应的.h文件,可在app\src\main\jni下
找到com_dqp_base64_MainActivity.h,其中就是C中对应的接口

在这里插入图片描述
将内容拷贝到之前创建的base64.h中

2.3.6 编写C代码,完成接口
同样也要先引入.h文件
在这里插入图片描述
2.3.7 So文件生成
在这里插入图片描述
2.3.8 完成MainActivity中数据的获取和输出并且调用base.c得到结果在这里插入图片描述
2.3.9 在布局按钮中设置响应,触发解码编码在这里插入图片描述
2.3.10 打包生成app

3 关于中文乱码问题
java 内部是使用的 16 bit 的 unicode 编码(utf-16)来表示字符串的,无论英文还是中文都是 2 字节。
JNI 内部是使用 utf-8 编码来表示字符串的,utf-8 是变长编码的 unicode,一般 ascii 字符是 1 字节,中文是 3 字节。
C/C++ 使用的是原始数据,ascii 就是一个字节,中文一般是 GB2312 编码,用 2 个字节表示一个汉字。
java 调用的时候使用的是 utf-16 编码的字符串,jvm 把这个参数传递给 JNI,C 得到的输入是 jstring,此时,可以利用 JNI 提供的两种函数进行解析:一个是 GetStringUTFChars,这个函数将得到一个 UTF-8 编码的字符串; 另一个是 GetStringChars 这个将得到 UTF-16 编码的字符串。无论那个函数,得到的字符串如果含有中文,都需要进一步转化成GB2312的编码。

解决方法:
1.C 端进行编码转换
C 端进行中文字符转换传递个 java 的时候,也是调用了 java 中 String 类的编码转换方式进行转换。
2.java 端进行编码转换
在 C 中,将 char* 转换为 jbyteArray,然后返回。在 Java端,获取数组,然后从”GB2312”格式生成 utf16 格式的java字符串。

if (frame.empty()) { __android_log_print(ANDROID_LOG_ERROR, "rtsp", "没图·咯~。~"); std::cerr << "Error: Empty frame!" << std::endl; break; } cv::cvtColor(frame, yuvImg, cv::COLOR_BGR2RGB);我验证了再转,还是出现,OpenCV(4.6.0) Error: Requested object was not found (could not open directory: /data/app/~~1jiOqiJfco6kfhiL3XSmKQ==/com.sisheng.aiaio-34cZ66il_WvVgAjfsh-uoA==/base.apk!/lib/arm64-v8a) in glob_rec, file /home/dreamgyf/compile/opencv/modules/core/src/glob.cpp, line 279 2025-03-14 17:40:59.800 7472-7517 libc com.sisheng.aiaio A Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x68 in tid 7517 (Thread-4), pid 7472 (m.sisheng.aiaio) 2025-03-14 17:41:00.330 7544-7544 DEBUG pid-7544 A Cmdline: com.sisheng.aiaio 2025-03-14 17:41:00.330 7544-7544 DEBUG pid-7544 A pid: 7472, tid: 7517, name: Thread-4 >>> com.sisheng.aiaio <<< 2025-03-14 17:41:00.331 7544-7544 DEBUG pid-7544 A #01 pc 000000000071ee28 /data/app/~~1jiOqiJfco6kfhiL3XSmKQ==/com.sisheng.aiaio-34cZ66il_WvVgAjfsh-uoA==/base.apk!librknnrt.so (offset 0x70ea000) (BuildId: 37b1e9d4e376e328c6ea301178644cfd452cdfa7) 2025-03-14 17:41:00.331 7544-7544 DEBUG pid-7544 A #02 pc 000000000071ea5c /data/app/~~1jiOqiJfco6kfhiL3XSmKQ==/com.sisheng.aiaio-34cZ66il_WvVgAjfsh-uoA==/base.apk!librknnrt.so (offset 0x70ea000) (BuildId: 37b1e9d4e376e328c6ea301178644cfd452cdfa7) 2025-03-14 17:41:00.331 7544-7544 DEBUG pid-7544 A #03 pc 000000000071b164 /data/app/~~1jiOqiJfco6kfhiL3XSmKQ==/com.sisheng.aiaio-34cZ66il_WvVgAjfsh-uoA==/base.apk!librknnrt.so (offset 0x70ea000) (BuildId: 37b1e9d4e376e328c6ea301178644cfd452cdfa7) 2025-03-14 17:41:00.331 7544-7544 DEBUG
最新发布
03-17
<think>好的,用户遇到了在Android平台上使用OpenCV时出现的崩溃问题,具体是空帧处理导致SIGSEGV崩溃,可能与librknnrt.so的目录访问错误有关。我需要逐步分析可能的原因和解决方法。 首先,用户提到崩溃发生在处理空帧时。空帧可能是指摄像头未正确初始化或未获取到有效图像数据,导致OpenCV处理时出现空指针访问。这时候需要检查摄像头初始化流程,确保在获取帧数据前摄像头已经正确打开,并且有权限访问摄像头设备。此外,处理每一帧前应检查帧是否为空,避免直接处理空帧[^2]。 接着,SIGSEGV错误通常与内存访问有关,比如空指针解引用或越界访问。在OpenCV中,处理图像数据时如果Mat对象为空或未正确分配内存,就会导致此类崩溃。因此,在调用任何图像处理函数前,应添加空帧判断,例如使用if (!frame.empty())来确保帧有效。 用户还提到librknnrt.so的目录访问错误。这个库可能与Rockchip的NPU加速库有关,可能与OpenCV的集成或路径配置不正确有关。需要检查APK中是否包含正确的库文件,并确保在运行时能够正确加载。可能需要检查Android项目的jniLibs目录结构,确保不同ABI(如arm64-v8a、armeabi-v7a)的库文件放置在正确的位置。例如,OpenCV SDK的libs目录结构需要与项目配置一致,避免加载时找不到库文件导致崩溃[^1]。 另外,权限问题也可能导致目录访问错误。Android应用需要适当的存储权限才能访问某些目录,特别是如果OpenCV尝试读写外部存储时。需要检查AndroidManifest.xml中是否声明了必要的权限,如READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE,并且在运行时动态请求这些权限(针对Android 6.0及以上版本)。 还有可能是OpenCV的初始化问题。在使用OpenCV的Java API时,需要正确加载OpenCV库,例如在应用启动时调用OpenCVLoader.initDebug(),并确保成功加载后再执行相关操作。加载失败可能导致后续调用本地方法时崩溃。 编译配置也可能影响库的正确性。用户提到他们自己编译了libopencv_java4.so,可能存在编译选项不正确或依赖缺失的问题。例如,编译时是否启用了正确的模块(如contrib模块),或者是否链接了必要的依赖库。需要确保编译脚本正确配置,特别是与Android NDK的兼容性和ABI支持[^2]。 可能还需要检查日志信息,通过adb logcat查看崩溃时的堆栈跟踪,确定崩溃发生的具体位置。例如,如果崩溃发生在native层的某个函数,可能需要检查对应的C++代码是否存在空指针或资源未正确释放的问题。 最后,建议用户分步骤排查:先确保摄像头初始化正确,添加空帧检查;验证库文件的路径和权限;检查运行时权限和OpenCV初始化流程;查看编译脚本和库文件是否正确。同时,参考已有的成功编译和配置案例,调整项目配置,逐步排除问题。</think>针对OpenCV在Android平台上因空帧处理和目录访问导致的崩溃问题,以下是分步骤解决方案: ### 一、空帧处理导致SIGSEGV崩溃 1. **添加空帧检测** 在摄像头帧处理前必须添加有效性验证: ```java if (!frame.empty()) { // 执行图像处理操作 } else { Log.e(TAG, "Received empty frame"); } ``` 否则直接操作空Mat对象会导致空指针解引用,触发SIGSEGV错误[^1]。 2. **摄像头初始化检查** - 确认摄像头权限`android.permission.CAMERA`已动态申请 - 检查`Camera.open()`返回值是否为有效设备实例 - 使用`Camera.setPreviewCallbackWithBuffer`避免帧数据竞争 ### 二、librknnrt.so目录访问错误 1. **验证库文件路径** OpenCV Android SDK要求将ABI-specific库文件(如`arm64-v8a`)放置在: ``` app/src/main/jniLibs/arm64-v8a/ ``` 需确认以下文件存在: - `libopencv_java4.so` - `librknnrt.so`(第三方NPU库) 文件缺失会导致`java.lang.UnsatisfiedLinkError`[^1]。 2. **NDK兼容性配置** 在`build.gradle`中指定支持的ABI: ```groovy android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'armeabi-v7a' } } } ``` 若APK包含不兼容ABI库,可能触发`dlopen failed`错误。 3. **库加载顺序优化** 在`static { System.loadLibrary("opencv_java4"); }`之后加载第三方库: ```java static { OpenCVLoader.initDebug(); System.loadLibrary("rknnrt"); } ``` ### 三、编译配置验证 1. **检查自定义编译脚本** 若使用自定义编译的`libopencv_java4.so`(如用户提供的[CSDN脚本]): ```bash # build-android-opencv.sh关键参数 -DANDROID_ABI=arm64-v8a \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_opencv_java=ON \ -DOPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules ``` 缺少`-DBUILD_opencv_java=ON`会导致Java绑定缺失。 2. **符号表匹配验证** 使用`nm -gDC libopencv_java4.so`检查是否包含关键符号: ``` Java_org_opencv_core_Mat_n_1delete Java_org_opencv_videoio_VideoCapture_n_1grab ``` 符号缺失表明编译配置错误。 ### 四、运行时权限处理 1. **添加存储权限** 在`AndroidManifest.xml`中声明: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ``` Android 11+需添加`android:requestLegacyExternalStorage="true"`。 2. **动态权限申请** 在Activity中请求权限: ```java if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } ``` ### 五、调试建议 1. **捕获Native崩溃日志** 通过`adb logcat`过滤信号: ``` adb logcat | grep -E 'SIGSEGV|libopencv|rknnrt' ``` 观察崩溃堆栈中涉及的具体OpenCV函数。 2. **使用AddressSanitizer** 在`build.gradle`中启用ASAN检测内存错误: ```groovy android { defaultConfig { externalNativeBuild { cmake { arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared" } } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值