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

news2024/10/1 17:37:14

一.概述

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();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityJniBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView 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生成的so
    static {
        System.loadLibrary("jnidemo");
    }

    //Java从Jni获取String
    public 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(aaa
        PROPERTIES
        IMPORTED_LOCATION
        "${jnilibs_dir}/${ANDROID_ABI}/libqxrcamclient.so")

set_target_properties(bbb
        PROPERTIES
        IMPORTED_LOCATION
        "${jnilibs_dir}/${ANDROID_ABI}/libqxrcoreclient.so")

set_target_properties(ccc
        PROPERTIES
        IMPORTED_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库到目标库
        aaa
        bbb
        ccc
)

四.结束语

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

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

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

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

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

相关文章

python一元线性回归sklearn

# -*- coding: utf-8 -*-from sklearn.linear_model import LinearRegression import numpy as np import matplotlib.pyplot as plt# 载入数据 data np.genfromtxt(一元线性回归.csv, delimiter,) x_data data[:,0] y_data data[:,1] plt.scatter(x_data,y_data) plt.show(…

如何本地安装Python Flask并结合内网穿透实现远程开发

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

03--数据库连接池

1、数据库连接池 1.1 JDBC数据库连接池的必要性 在使用开发基于数据库的web程序时&#xff0c;传统的模式基本是按以下步骤&#xff1a; 在主程序&#xff08;如servlet、beans&#xff09;中建立数据库连接进行sql操作断开数据库连接 这种模式开发&#xff0c;存在的问题:…

外贸自建站如何建立?海洋建站的操作指南?

外贸自建站的建站流程什么&#xff1f;做跨境怎么搭建外贸网站&#xff1f; 外贸自建站成为企业开拓国际市场、提升品牌形象的重要途径。然而&#xff0c;对于许多企业而言&#xff0c;如何高效地进行外贸自建站仍然是一个挑战。海洋建站将带您一步步探讨外贸自建站的关键步骤…

Ubuntu重设root的密码

重设root的密码 未重设密码之前&#xff0c;Ubuntu 中默认的 root 密码是随机的&#xff0c;即每次开机都会有一个新的root 密码&#xff0c;所以此时的 root 用户密码并不确定&#xff1b; 重设root 密码&#xff0c;使用安装时创建的用户登录后sudo su切换至root用户&#…

JAVASE进阶(设计模式、设计原则)(更新中...)

目录 一、注解 内置注解&#xff1a;JAVA中已经定义好的注解。 元注解&#xff1a;修饰注解的注解。 自定义注解。 二、克隆 JAVA中对clone的实现&#xff1f; 浅克隆 深克隆 那么该如何做到深克隆呢&#xff1f; 三、常用设计模式 1、创建型模式 单例模式 工厂模式 工…

【数据结构与算法】之字符串系列-20240122

这里写目录标题 一、383. 赎金信二、387. 字符串中的第一个唯一字符三、389. 找不同四、392. 判断子序列五、409. 最长回文串 一、383. 赎金信 简单 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果…

【EI会议征稿通知】第五届电气、电子信息与通信工程国际学术会议 (EEICE 2024)

第五届电气、电子信息与通信工程国际学术会议 (EEICE 2024&#xff09; 2024 5th International Conference on Electrical, Electronic Information and Communication Engineering (EEICE 2024) 第五届电气、电子信息与通信工程国际学术会议 (EEICE 2024&#xff09;将于20…

消息中间件之RabbitMQ

1.RabbitMQ简介 1.基于AMQP协议Erlang语言开发的一款消息中间件&#xff0c;客户端语言支持比较多&#xff0c; 比如Python,Java,Ruby,PHP,JS,Swift.运维简单&#xff0c;灵活路由&#xff0c;但是性能不高&#xff0c; 可以满足一般场景下的业务需要,三高场景下吞吐量不高&am…

【SpringCloud Alibaba】 介绍及微服务模块搭建

文章目录 SpringCloud Alibaba 介绍主要功能组件 微服务环境搭建案例准备技术选型模块设计微服务调用 创建父工程创建基础模块1、创建 shop-common 模块2、创建实体类 创建用户微服务1、创建pom.xml2、编写主类3、创建配置文件 创建商品微服务1、创建一个名为 shop-product 的模…

机器学习实验报告——EM算法

目录 一、算法介绍 1.1算法背景 1.2算法引入 1.3算法假设 1.4算法原理 1.5算法步骤 二、算法公式推导 2.1数学基础 2.2EM算法推导 三、算法实现 3.1关于EM聚类 3.2EM工具包的使用 3.3 实例测试 四、算法讨论 4.1EM算法的优缺点 4.2EM算法的应用 4.3对于EM算法…

79、avx2 向量指令集优化卷积运算

上一节 介绍了 avx2 向量指令集中的 load/store 操作,本节介绍如何使用 avx2 的向量指令集来实现乘累加运算。 因为我们实战中用到的 resnet50 神经网络中,卷积运算在整个模型中的比例占据是相当高,而卷积运算的核心计算就是乘累加计算。因此,只要将最核心的乘累加计算效率…

【Linux】Shell 命令以及运行原理

Shell 命令以及运行原理 当用户登录 Linux 系统的时候&#xff0c;系统会给用户创建一个新的进程&#xff0c;一般叫做 bash&#xff08;命令行解释器&#xff09;。 Linux 严格意义上说的是一个操作系统&#xff0c;我们称之为 “核心&#xff08; kernel &#xff09;” &…

水利信息化监测平台 助力现代水利

​随着物联网、大数据、云计算等新技术在水利行业的广泛应用,水利信息化监测平台应运而生,在提升水利工程监测预警、增强水资源管理调度能力等方面发挥着重要作用。 一、水利信息化监测平台概述 水利信息化监测平台通过部署在水工程设施上的各类智能终端和传感器,形成信息采集…

MBR扇区修复和GRUB引导修复实验

修复MBR扇区 步骤一&#xff1a;在进行实验之前我们需要新加一块磁盘&#xff0c;并对新加磁盘进行分区处理&#xff0c;用来备份sda磁盘的MBR及分区表信息。&#xff08;注&#xff1a;在实验中可以不像我如此这么繁琐&#xff0c;一个主分区&#xff0c;并格式化挂载即可&am…

亚像素边缘检测——基于模糊边缘模型的亚像素圆检测方法

论文&#xff1a;A Novel Subpixel Circle Detection Method Based on the Blurred Edge Model 期刊&#xff1a;IEEE Transactions on Instrumentation and Measurement, 71:1-11, 2021. 作者&#xff1a;Weihua Liu, Xianqiang Yang, Xuebo Yang, Hao Sun, Xinghu Yu, Huij…

无限商机、拓全国、赢未来!2024上海国际轴承展重磅来袭!

中国设备管理协会主办的“2024上海国际轴承及其专用装备展览会”将于2024年7月24日至26日在“国家会展中心&#xff08;虹桥&#xff09;”举办。展会预计展出面积55000平方米&#xff0c;汇聚来自世界各地的近1000家企业与60000多人次的国内外观众齐聚一堂。为期三天的展览会是…

计算机网络——运输层(2)暨小程送书

计算机网络——运输层&#xff08;2&#xff09;暨小程送书 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 运输层&#xff08;2&#xff09;TCP/IP对比TCP&#xff08;传输控制协议&#xff09;&#xff1a;IP&#xff08;互联网协议&#xff09;&#xff1a;总结 拥塞…

只会 Python 不行,不会 Python 万万不行 。。。

当下的环境大家有目共睹&#xff0c;未来一段时间情况如何&#xff0c;想必不少人心里也清楚&#xff0c;技术人走到中年&#xff0c;难免会焦虑&#xff0c;职场上干得不爽&#xff0c;但是跳槽也不容易&#xff0c;加上不少企业裁员&#xff0c;换个满意的工作更是难上加难。…

Python中r‘ ‘, b‘ ‘, u‘ ‘, f‘ ‘的含义及用法详解

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;字符串是一种常见的数据类型&#xff0c;用于表示文本信息。除了普通的字符串&#xff0c;Python还提供了一些特殊的字符串前缀和格式化字符串&#xff0c;包括r ,b ,u ,f 。本文将详细解…