Android:JNI实战,加载三方库、编译C/C++

一.概述

Android Jni机制让开发者可以在Java端调用到C/C++,也是Android应用开发需要掌握的一项重要的基础技能。

计划分两篇博文讲述Jni实战开发。

本篇主要从项目架构上剖析一个Android App如何通过Jni机制加载三方库C/C++文件。 

二.Native C++

Android Studio可以直接创建一个可运行的、最简单的Jni Demo App。

字符串"Hello from C++"Jni传到Java再在TextView上显示。

运行:

文件目录结构:

这个默认创建的Jni Demo App的代码就不一一展示了。

接下来会详细讲解自定义Jni App架构和代码改造过程

三.自定义JNI App

3.1 目录架构

默认创建的Jni Demo App只是简单实现了一个字符串在JniJava之间的传递,并没有涉及到加载三方库.c/.cpp,所以接下来要做的就是,在默认Jni Demo App基础上进行升级改造,实现一个便于扩展、能够加载三方库和.c/.cppDemo。

先看看改造后的目录结构:

相对于AndroidStudio默认创建的Jni Demo App,主要的修改点有如下:

  1. MainActivity.java中的Load Jni so以及native函数声明部分单独抽离出来,写成一个专门的JNIDEMO.java文件,便于对Jni的调用
  2. 与 java 目录平级新建 jnicpp 目录放置C/C++源码文件
  3. 与 java 目录平级新建 jnilibs 目录放置需要加载的三方库
  4. 变更CMakeLists.txt位置,放置在jnilibs根目录

3.2 源码解析

3.2.1 JniActivity.java
package com.android.demo.activity;import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.android.demo.databinding.ActivityJniBinding;import com.android.demo.jni.JNIDEMO;public class JniActivity extends AppCompatActivity {private final String TAG = "JniActivity";private ActivityJniBinding binding;//实现一个JNIDEMO实例对象private JNIDEMO mJniDemo = new JNIDEMO();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityJniBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;//通过JNIDEMO实例调用java native方法,从而调用到Jni方法,实现对Jni字符串的获取tv.setText(mJniDemo.JavaGetStringFromJNI());}
}
3.2.2 JNIDEMO.java
package com.android.demo.jni;public class JNIDEMO {private static final String TAG = "JNIDEMO";// 应用启动时,load编译Jni生成的sostatic {System.loadLibrary("jnidemo");}//Java从Jni获取Stringpublic native String JavaGetStringFromJNI();
}
3.2.3 jnidemo.cpp
#include <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h> //引用android log#include <stdlib.h>
#include <stdio.h>#include "jnidemo.h"//定义日志打印的方法
#define TAG "JNITEST" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型using namespace std;#ifdef __cplusplus   //兼容C++代码
extern "C" {         //兼容C代码JNIEXPORT jstring JNICALL
Java_com_android_demo_jni_JNIDEMO_JavaGetStringFromJNI(JNIEnv *env,      //Java虚拟机内存地址指针jobject instance  //Java对象实例) {string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}}
#endif
3.2.4 jnidemo.h

此次Demo实现重点在工程架构,功能并不复杂,与默认创建的Jni Demo一样,只是传递一个string,所以.h文件中暂未有变量和函数声明。

/*
* Created by Shawn.xiao on 2023/12/31.
*/
#ifndef MYDEMO_JNIDEMO_H
#define MYDEMO_JNIDEMO_H
#endif#ifdef __cplusplus
extern "C" {}
#endif
3.2.5 build.gradle

build.gradleandroid{ }中需要指定 jnilibs CMakeList.txt 两个路径

android {...sourceSets {main{jniLibs.srcDirs = ['src/main/jnilibs']}}externalNativeBuild {cmake {path file('src/main/jnicpp/CMakeLists.txt')version '3.18.1'}}...}

如果要导入第三方库,CMake在编译时,会默认在jniLibs.srcDirs目录下查找和编译如下4个主流平台的库,如果这4个平台的库没有配置全,编译就会报错。

但是通常我们只会配置移动端64位的库,也就是arm64-v8a

所以需要在build.gradle :: android{} :: defaultConfig{}里加上如下代码进行过滤

android {...defaultConfig {...externalNativeBuild {cmake {abiFilters "arm64-v8a"arguments "-DANDROID_STL=c++_shared", "-DANDROID_ARM_NEON=TRUE"//arguments "-DANDROID_STL=c++_static", "-DANDROID_ARM_NEON=TRUE"}}}...}
3.2.6 CMakeLists.txt
(1).最基础的CMakeList.txt
# 设置CMake版本
cmake_minimum_required(VERSION 3.18.1)# 设置变量
set(jnicpp_src "${CMAKE_SOURCE_DIR}/src")    #src源码路径
set(jnicpp_inc "${CMAKE_SOURCE_DIR}/inc")    #inc头文件路径
set(jnilibs_dir "${CMAKE_SOURCE_DIR}/src/main/jnilibs")  #so/.a# 1.创建和命名库,本demo里是要生成的库jnidemo.so
# 2.设置要生成的库的属性:STATIC(.a) 或 SHARED(.so)
# 3.设置生成库的源码路径
# 4.可以定义多个库,CMake都会进行编译,Gradle再自动将库打包到Apk
add_library(jnidemo    #设置so文件名称SHARED     #设置这个so文件为共享${jnicpp_src}/jnidemo.cpp)   #源码路径#指定需要使用的公共NDK库
find_library(log-lib  # 设置路径变量名称log)     # 指定需要CMake去搜寻定位的公共NDK库#链接头文件
target_include_directories(jnidemo    #Jni库PRIVATE    #对外引用属性${jnicpp_inc})  #头文件路径#包含头文件
#这个方法与target_include_directories()不同
#设置后,当前目录的所有子目录中的CMakeLists.txt头文件包含都会引用该方法中的变量定义
#include_directories(${jnicpp_inc})# 指定需要用CMake链接到目标库的库。
# 可以链接多个库,例如在本脚本中定义的库、预构建的第三方库或系统库。
target_link_libraries(jnidemo     #指定目标库${log-lib}  # 链接NDK中的log-lib库到目标库
)

一个最基本的CMakeList.txt就写成了

编译工程生成apk,将后缀.apk改为.zip后解压,就会发现lib文件夹

看看对应64位移动端的arm64-v8a目录:

到这里,一个能够加载第三方库C/C++文件Jni App基本成型了

但是,对于要导入的第三方库,这个最基础的CMakeLists.txt能做到的仅仅只是把它们加载到了Apk中,如果Jni代码中需要引用到这些三方库里的方法,那么在CMakeLists.txt里还需要对三方库进行设置和链接,将它们链接到最终会编译生成的Jni库上。

(2).链接三方库的CMakeLists.txt
# 设置CMake版本
cmake_minimum_required(VERSION 3.18.1)# 设置变量
set(jnicpp_inc "${CMAKE_SOURCE_DIR}/inc")    # jnicpp/inc头文件目录路径
set(jnicpp_src "${CMAKE_SOURCE_DIR}/src")    # jnicpp/src源文件目录路径# CMake所有内置变量都只能到CMakeLists.txt所在目录路径,下面方式可以获取CMakeLists.txt所在目录的上一级目录路径
string(REGEX REPLACE "(.*)/(.*)" "\\1" CMAKE_UP_PATH  ${PROJECT_SOURCE_DIR})
set(jnilibs_dir "${CMAKE_UP_PATH}/jnilibs")  ##jnilibs目录路径 so/.a# 1.使用add_library命令创建和命名要生成的jni库,本demo里要生成的是jnidemo.so
# 2.设置要生成的库的属性:STATIC(.a) 或 SHARED(.so)
# 3.设置生成库的源码路径
add_library(jnidemo    #设置so文件名称SHARED     #设置这个so文件为共享${jnicpp_src}/jnidemo.cpp)   #源码路径#查找指定需要使用的公共NDK库
find_library(log-lib  # 设置路径变量名称log)     # 指定需要CMake去搜寻定位的公共NDK库# 使用add_library命令,通过指定IMPORTED选项表明这是一个要导入的库文件
# 相当于告知CMake,我要链接三个SHARED动态库:aaa、bbb、ccc
# 这三句必须在前面,要不然后面的语句不知道aaa、bbb、ccc是什么
add_library(aaa SHARED IMPORTED)
add_library(bbb SHARED IMPORTED)
add_library(ccc SHARED IMPORTED)# CMake属性设置函数,IMPORTED_LOCATION 表示设置目标aaa、bbb、ccc的文件路径属性
# ${CMAKE_SOURCE_DIR}:表示CMakeLists.txt的当前文件夹路径
# ${ANDROID_ABI}:编译时会自动根据CPU架构去选择相应的库
set_target_properties(aaaPROPERTIESIMPORTED_LOCATION"${jnilibs_dir}/${ANDROID_ABI}/libqxrcamclient.so")set_target_properties(bbbPROPERTIESIMPORTED_LOCATION"${jnilibs_dir}/${ANDROID_ABI}/libqxrcoreclient.so")set_target_properties(cccPROPERTIESIMPORTED_LOCATION"${jnilibs_dir}/${ANDROID_ABI}/libqxrsplitclient.so")#链接头文件目录路径
target_include_directories(jnidemo    #Jni库PRIVATE    #对外引用属性${jnicpp_inc})  #头文件路径#包含头文件
#这个方法与target_include_directories()不同
#设置后,当前目录的所有子目录中的CMakeLists.txt头文件包含都会引用该方法中的变量定义
#include_directories(${jnicpp_inc})# 指定需要用CMake链接到目标库的库。
# 可以链接多个库,例如在本脚本中定义的库、导入的第三方库或系统库。
target_link_libraries(jnidemo     #指定目标库${log-lib}  # 链接NDK中的log-lib库到目标库aaabbbccc
)

四.结束语

到此,一个可以加载三方库、编译C/C++的Jni App就搭建完成了

这一篇博文主要介绍Jni App的项目架构,构建文件编写等, 并没有涉及Jni代码语法

下一篇会在此篇基础上讲解Jni开发详细语法。

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

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

相关文章

精准核酸检测 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 为了达到新冠疫情精准防控的需要&#xff0c;为了避免全员核酸检测带来的浪费&#xff0c;需要精准圈定可能被感染的人群。 现在根据传染病流调以及大数据分析&a…

【代码实战】从0到1实现transformer

获取数据 import pathlibimport tensorflow as tf# download dataset provided by Anki: https://www.manythings.org/anki/ text_file tf.keras.utils.get_file(fname"fra-eng.zip",origin"http://storage.googleapis.com/download.tensorflow.org/data/fra-…

transdata笔记:手机数据处理

1 mobile_stay_duration 每个停留点白天和夜间的持续时间 transbigdata.mobile_stay_duration(staydata, col[stime, etime], start_hour8, end_hour20) 1.1 主要参数 staydata停留数据&#xff08;每一行是一条数据&#xff09;col 列名&#xff0c;顺序为[‘starttime’,…

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07-2 动态规划 Dynamic Programming

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-2 动态规划 Dynamic Programming 1. 基本概念2. 代码详解3. 简单一维案例 1. 基本概念 Richoard Bell man 最优化理论&#xff1a; An optimal policy has the …

纯C无操作系统轻量协程库Protothread使用记录

文章目录 目的源码说明使用演示总结 目的 在单片机开发中很多时候都是无操作系统环境&#xff0c;这时候如果要实现异步操作&#xff0c;并且流程逻辑比较复杂时处理起来会稍稍麻烦。这时候可以试试 Protothread 这个协程库。 官网&#xff1a; https://dunkels.com/adam/pt/…

深入剖析:Kafka流数据处理引擎的核心面试问题解析75问(5.7万字参考答案)

Kafka 是一款开源的分布式流处理平台&#xff0c;被广泛应用于构建实时数据管道、日志聚合、事件驱动的架构等场景。本文将深入探究 Kafka 的基本原理、特点以及其在实际应用中的价值和作用。 Kafka 的基本原理是建立在发布-订阅模式之上的。生产者将消息发布到主题&#xff08…

37-WEB漏洞-反序列化之PHPJAVA全解(上)

WEB漏洞-反序列化之PHP&JAVA全解&#xff08;上&#xff09; 一、PHP 反序列化原理二、案例演示2.1、无类测试2.1.1、本地2.1.2、CTF 反序列化小真题2.1.3、CTF 反序列化类似题 2.2、有类魔术方法触发2.2.1、本地2.2.2、网鼎杯 2020 青龙大真题 三、参考资料 一、PHP 反序列…

SpringMVC(八)处理AJAX请求

一、处理AJAX之准备工作: 首先我们创建一个新的工程: 我们将pom.xml复制过来: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-in…

【项目日记(三)】内存池的整体框架设计

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:项目日记-高并发内存池⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你做项目   &#x1f51d;&#x1f51d; 开发环境: Visual Studio 2022 项目日…

MES管理系统为何成为汽配企业的刚需

随着经济全球化、产品定制化及安全法规的严格化&#xff0c;汽配企业的经营环境变得越来越复杂。中国劳动力资源和原辅料成本的持续上升&#xff0c;导致行业利润率不断下滑。为了应对这些挑战&#xff0c;汽配企业需要引入一种精益制造和管理的工具&#xff0c;而MES管理系统正…

四款通用组织架构图模板-一键高清导出

组织架构图作为一种直观的图形化工具&#xff0c;能够帮助我们更好地理解和规划组织结构&#xff0c;提高工作效率。今天&#xff0c;我们就为大家带来四款通用组织架构图模板&#xff0c;让你一键高清导出&#xff0c;轻松搞定组织架构设计&#xff01; 第一款&#xff1a;某基…

Excel乱码?教你3个简单解决方法!

“我在编辑一个文件时&#xff0c;Excel突然就乱码了&#xff0c;怎么会这样呢&#xff1f;这个文件对我来说是比较重要的&#xff01;有什么方法可以快速解决吗&#xff1f;” 在处理Excel文件时&#xff0c;我们有时会遇到乱码问题。乱码不仅影响数据的可读性&#xff0c;还可…

PySide6/PyQt6中Qt窗口标志/窗口属性汇总,如何正确的设置窗口标志/窗口属性

文章目录 📖 介绍 📖🏡 环境 🏡📒 使用方法 📒📚 窗口标志汇总📚 窗口属性汇总📝 使用方法📝 注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在Qt框架中,窗口标志(window flags)是用于控制窗口的各种属性和行为的强大工具。它们通过设置窗口的属性,如边框…

vue3中Fragment特性的一个bug,需要留意的注意事项

vue3中的Fragment 模版碎片特性是什么&#xff0c;简单的理解就是template模板代码不在像vue2中那样必须在根节点在包裹一层节点了。 vue2写法 <template><div><h1>标题</h1><p>正文内容</p></div> </template>vue3写法 &l…

【OCR项目】之用HALCON的深度学习工具进行文字识别,并导出到C++调用

前言 HALCON是一个强大的机器视觉工具&#xff0c;包含了2D&#xff0c;3D图像各种算子&#xff0c;以及各种任务的深度学习工具&#xff0c;包括目标检测&#xff0c;实例分割&#xff0c;文字识别等。 这次从实际生产的角度&#xff0c;来分享一下如何用HALCON进行文字识别…

Redis(01)——常用指令

基础指令 select 数字&#xff1a;切换到其他数据库flushdb&#xff1a;清空当前数据库flushall&#xff1a;清空所有数据库dbsize&#xff1a;查看数据库大小exists key1[key2 …]&#xff1a;判断当前的key是否存在keys *&#xff1a;查看所有的keyexpire key 时间&#xff…

【数据结构与算法】1.数据结构绪论

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&…

EOCR电机保护器带煤电厂的具体应用

EOCR系列电动机智能保护器在煤电厂中有广泛的应用。这些保护器具有齐全的保护功能、直观的测量参数、快速的反应灵敏度、可靠的行动以及与上位机通讯构成远程监控的能力&#xff0c;使其成为理想的低压电动机保护及远程监控产品。 在煤电厂中&#xff0c;电动机保护器需要具备过…

【Linux多线程】生产者消费者模型

目录 生产者消费者模型 1. 生产者消费者模式的概念 2. 生产者消费者模型优点 ​编辑3. 生产者消费者模型的特点 基于BlockingQueue&#xff08;阻塞队列&#xff09;的生产者消费者模型 1.BlockingQueue 2. 使用CSTL中的queue来模拟实现阻塞队列 3. 基于任务的生产者消费…