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

    @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开发详细语法。

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

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

相关文章

高防IP如何保护服务器

首先我们要知道什么是高防IP~ 高防IP是指高防机房所提供的ip段&#xff0c;主要是针对互联网服务器遭受大流量DDoS攻击时进行的保护服务。高防IP是目前最常用的一种防御DDoS攻击的手段&#xff0c;用户可以通过配置DDoS高防IP&#xff0c;将攻击流量引流到高防IP&#xff0c;防…

样品前处理国产微波消解罐的优势

国产微波消解罐参数&#xff1a; 型号 MARS5、MARS6等 内罐材质 TFM 外罐材质 宇航纤维复合材料 耐温 -200&#xff5e;260℃ 规格 25ml、55ml、75ml、100ml、110ml 厂家秉承 “客户、服务、技术”为根本的理念国产替代微波罐受到众多用户的青睐。 特性&#xff1a…

【LeetCode】每日一题 2024_1_21 分割数组的最大值(二分)

文章目录 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01;题目&#xff1a;分割数组的最大值题目描述代码与解题思路 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 今天是 hard&#xff0c;难受&#xff0c;还好有题解大哥的清晰讲解 题目&a…

论文阅读:Vary论文阅读笔记

目录 引言整体结构图数据集构造Vary-tiny部分Document Data数据构造Chart Data构造Negative natural image选取 Vary-base部分 引言 论文&#xff1a;Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models Paper | Github | Demo 许久不精读论文了&#x…

文件操作与IO(3)

文件内容的读写--数据流 这里我们将要讲到文件操作中的重要概念--流. 之前也在C语言讲解中提到了文件流的概念---读写文件内容 分为这几步:(1)打开文件;(2)读/写文件;(3)关闭文件. 数据流主要分为字节流和字符流. 字节流:以字节为单位进行读写(代表:InputStream,OutputStrea…

20240122让WIN10在启动的时候进入安全模式

20240122让WIN10在启动的时候进入安全模式 2024/1/22 18:30 缘起&#xff1a;为了使用openai的whisper识别小语种【非英语】电影的字幕&#xff0c;决定开始折腾CUDA了&#xff01;https://github.com/openai/whisper https://www.bilibili.com/video/BV1d34y1F7qA https://www…

API协议设计的十种技术

文章目录 前言一、REST二、GraphQL三、gRPC&#xff08;google Remote Procedure Calls&#xff09;四、Webhooks五、服务端的事件发送——SSE&#xff08;Server-sent Events&#xff09;六、EDI&#xff08;Electronic Data Interchange&#xff09;七、面向API 的事件驱动设…

HarmonyOS NEXT 开发者必看“清单“就在这里!

随着HarmonyOS NEXT开启开发者预览版Beta招募&#xff0c;开发者可以体验到全面升级的 OS开放新能力、鸿蒙特征新场景、开发工具等。这是一项需要广大开发者一起参与的伟大事业&#xff0c;华为期待携手开发者一路同行&#xff0c;共赴鸿蒙生态的星辰大海。如何借助HarmonyOS N…

数据集笔记:UJIIndoorLoc

1 数据集介绍 UJIIndoorLoc - UCI Machine Learning Repository UJIIndoorLoc是一个多建筑多楼层的室内定位数据库&#xff0c;用于测试依赖于WLAN/WiFi指纹的室内定位系统。 2 数据读取 数据分类训练数据和测试数据 import pandas as pdapd.read_csv(Downloads/ujiindoo…

矩阵和矩阵如何相乘?

矩阵与矩阵相乘遵循特定的数学规则。为了相乘&#xff0c;第一个矩阵的列数必须等于第二个矩阵的行数。矩阵乘法的结果是一个新矩阵&#xff0c;其行数等于第一个矩阵的行数&#xff0c;列数等于第二个矩阵的列数。矩阵乘法不满足交换律&#xff0c;即 AB≠BA。 例子&#xff…

MySQL---多表分组查询综合练习

创建dept表 CREATE TABLE dept ( deptno INT(2) NOT NULL COMMENT 部门编号, dname VARCHAR (15) COMMENT 部门名称, loc VARCHAR (20) COMMENT 地理位置 ); 添加dept表主键 mysql> alter table dept add primary key(deptno); Query OK, 0 rows affected (0.02 s…

Mybatis 动态SQL条件查询(注释和XML方式都有)

需求 : 根据用户的输入情况进行条件查询 新建了一个 userInfo2Mapper 接口,然后写下如下代码,声明 selectByCondition 这个方法 package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.model.UserInfo; import org.apache.ibatis.annotations.*; import j…

接口测试介绍以及用例编写

6.1 接口 6.1.1 接口概述 定义&#xff1a; 接口就是API&#xff08;Application Programming Interface&#xff0c;应用程序接口&#xff09;&#xff0c;是一个软件或服务对外提供的接口&#xff0c;别人只要调用这接口&#xff0c;而内部如何实现&#xff0c;不需要关心。…

Python 算法交易实验67 第一次迭代总结

说明 在这里对第一次迭代&#xff08;2023.7~ 2024.1&#xff09;进行一些回顾和总结&#xff1a; 回顾&#xff1a; 1 实现了0~1的变化2 在信息隔绝的条件下&#xff0c;无控制的操作&#xff0c;导致被套 总结&#xff1a; 思路可行&#xff0c;在春暖花开的时候&#x…

3分钟带你了解,软件测试是做什么的

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

springboot集成COS对象存储

1.申请腾讯云存储桶 新建密钥&#xff08;后面配置要用到&#xff09; 2.编写工具类 此处使用工具类进行基本属性配置&#xff0c;也可选择在yml中配置 package com.sfy.util;import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.a…

【网络安全 -> 防御与保护】专栏文章索引

为了方便 快速定位 和 便于文章间的相互引用等 作为一个快速准确的导航工具 网络安全——防御与保护 &#xff08;一&#xff09;.信息安全概述

地图 - 实现有多条定位,显示多条定位,并且使用一个圆形遮罩层将多条定位进行覆盖

首先&#xff0c;需要在你的index.html模板页面头部加载百度地图JavaScript API代码&#xff0c;密钥可去百度地图开放平台官网申请 <script type"text/javascript" src"//api.map.baidu.com/api?typewebgl&v1.0&ak您的密钥"></script&…

消息队列之王——Kafka

Zookeeper 在学习kafka之前&#xff0c;我们需要先学习Zookeeper&#xff0c;那Zookeeper是什么呢&#xff1f;Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观…

VUE---插槽

一、插槽的作用&场景 1、在封装组件的时候&#xff0c;将可变的结构设计为插槽&#xff08;<slot></slot>&#xff09; 2、使用上述组件的时候&#xff0c;可以按需为插槽提供自定义的结构&#xff0c;以达到复用组件且高度自定的效果 二、基本语法 1、组件内…