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/.cpp** 的Demo。

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

相对于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
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
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
java 复制代码
#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**文件中暂未有变量和函数声明。

javascript 复制代码
/*
* 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{ } 中需要指定 jnilibsCMakeList.txt两个路径

java 复制代码
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{}**里加上如下代码进行过滤

bash 复制代码
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
bash 复制代码
# 设置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
bash 复制代码
# 设置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开发详细语法。

相关推荐
源代码•宸5 分钟前
C++高频知识点(十五)
c++·经验分享
2501_9160074713 分钟前
Charles中文版抓包工具使用指南 提高API调试和网络优化效率
android·ios·小程序·https·uni-app·iphone·webview
野犬寒鸦13 分钟前
Pipeline功能实现Redis批处理(项目批量查询点赞情况的应用)
java·服务器·数据库·redis·后端·缓存
叽哥15 分钟前
flutter学习第 6 节:按钮与交互组件
android·flutter·ios
꧁༺摩༒西༻꧂20 分钟前
Spring Boot Actuator 监控功能的简介及禁用
java·数据库·spring boot
xzkyd outpaper33 分钟前
Android视图状态以及重绘
android
Java中文社群44 分钟前
快看!百度提前批的面试难度,你能拿下吗?
java·后端·面试
用户2018792831671 小时前
为什么 Tab 文字默认会全大
android
丨千纸鹤丨1 小时前
Tomcat
java·tomcat
用户2018792831671 小时前
Tablayout默认情况下,标签为什么会比文字宽?
android