当前位置:   article > 正文

Android:JNI实战,加载三方库、编译C/C++_android 引用第三方jni库

android 引用第三方jni库

一.概述

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

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

  1. /*
  2. * Created by Shawn.xiao on 2023/12/31.
  3. */
  4. #ifndef MYDEMO_JNIDEMO_H
  5. #define MYDEMO_JNIDEMO_H
  6. #endif
  7. #ifdef __cplusplus
  8. extern "C" {
  9. }
  10. #endif
3.2.5 build.gradle

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

  1. android {
  2. ...
  3. sourceSets {
  4. main{
  5. jniLibs.srcDirs = ['src/main/jnilibs']
  6. }
  7. }
  8. externalNativeBuild {
  9. cmake {
  10. path file('src/main/jnicpp/CMakeLists.txt')
  11. version '3.18.1'
  12. }
  13. }
  14. ...
  15. }

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

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

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

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

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

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

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

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

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

(2).链接三方库的CMakeLists.txt
  1. # 设置CMake版本
  2. cmake_minimum_required(VERSION 3.18.1)
  3. # 设置变量
  4. set(jnicpp_inc "${CMAKE_SOURCE_DIR}/inc") # jnicpp/inc头文件目录路径
  5. set(jnicpp_src "${CMAKE_SOURCE_DIR}/src") # jnicpp/src源文件目录路径
  6. # CMake所有内置变量都只能到CMakeLists.txt所在目录路径,下面方式可以获取CMakeLists.txt所在目录的上一级目录路径
  7. string(REGEX REPLACE "(.*)/(.*)" "\\1" CMAKE_UP_PATH ${PROJECT_SOURCE_DIR})
  8. set(jnilibs_dir "${CMAKE_UP_PATH}/jnilibs") ##jnilibs目录路径 so/.a
  9. # 1.使用add_library命令创建和命名要生成的jni库,本demo里要生成的是jnidemo.so
  10. # 2.设置要生成的库的属性:STATIC(.a) 或 SHARED(.so)
  11. # 3.设置生成库的源码路径
  12. add_library(
  13. jnidemo #设置so文件名称
  14. SHARED #设置这个so文件为共享
  15. ${jnicpp_src}/jnidemo.cpp) #源码路径
  16. #查找指定需要使用的公共NDK库
  17. find_library(
  18. log-lib # 设置路径变量名称
  19. log) # 指定需要CMake去搜寻定位的公共NDK库
  20. # 使用add_library命令,通过指定IMPORTED选项表明这是一个要导入的库文件
  21. # 相当于告知CMake,我要链接三个SHARED动态库:aaa、bbb、ccc
  22. # 这三句必须在前面,要不然后面的语句不知道aaa、bbb、ccc是什么
  23. add_library(aaa SHARED IMPORTED)
  24. add_library(bbb SHARED IMPORTED)
  25. add_library(ccc SHARED IMPORTED)
  26. # CMake属性设置函数,IMPORTED_LOCATION 表示设置目标aaa、bbb、ccc的文件路径属性
  27. # ${CMAKE_SOURCE_DIR}:表示CMakeLists.txt的当前文件夹路径
  28. # ${ANDROID_ABI}:编译时会自动根据CPU架构去选择相应的库
  29. set_target_properties(aaa
  30. PROPERTIES
  31. IMPORTED_LOCATION
  32. "${jnilibs_dir}/${ANDROID_ABI}/libqxrcamclient.so")
  33. set_target_properties(bbb
  34. PROPERTIES
  35. IMPORTED_LOCATION
  36. "${jnilibs_dir}/${ANDROID_ABI}/libqxrcoreclient.so")
  37. set_target_properties(ccc
  38. PROPERTIES
  39. IMPORTED_LOCATION
  40. "${jnilibs_dir}/${ANDROID_ABI}/libqxrsplitclient.so")
  41. #链接头文件目录路径
  42. target_include_directories(
  43. jnidemo #Jni库
  44. PRIVATE #对外引用属性
  45. ${jnicpp_inc}) #头文件路径
  46. #包含头文件
  47. #这个方法与target_include_directories()不同
  48. #设置后,当前目录的所有子目录中的CMakeLists.txt头文件包含都会引用该方法中的变量定义
  49. #include_directories(${jnicpp_inc})
  50. # 指定需要用CMake链接到目标库的库。
  51. # 可以链接多个库,例如在本脚本中定义的库、导入的第三方库或系统库。
  52. target_link_libraries(
  53. jnidemo #指定目标库
  54. ${log-lib} # 链接NDK中的log-lib库到目标库
  55. aaa
  56. bbb
  57. ccc
  58. )

四.结束语

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/306885
推荐阅读
相关标签
  

闽ICP备14008679号