当前位置: 首页 > news >正文

Android NDK — 在Linux环境下使用NDK实现交叉编译

文章目录

    • 安装与使用lrzsz
      • 介绍
      • 编译安装
      • 使用
    • 下载NDK
    • 配置NDK的Linux环境变量
    • 交叉编译
    • 配置GCC的Linux环境变量
      • 临时环境
      • 永久环境
    • 编译打包静态库和动态库
      • 静态库
      • 动态库
      • 生成动态库
        • Linux服务器的动态库
        • 交叉编译动态库
      • 生成静态库
        • Linux服务器的静态库
        • 交叉编译静态库
    • 将动态库和静态库部署到Android项目中
    • 参考

安装与使用lrzsz

介绍

rz,sz是Linux/Unix同Windows进行ZModem文件传输的命令行工具。

rz(receive Zmodem) 可以很方便的从客户端传文件到服务器。sz(send Zmodem)也可以很方便的从服务器传文件到客户端,就算中间隔着跳板机也不影响。

在这里插入图片描述

只有windows系统才有需要安装

编译安装

依次执行下面命令安装:

cd /tmp
wget http://www.ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz
tar zxvf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20
./configure && make && make install

其中tar zxvf lrzsz-0.12.20.tar.gz 是解压下载tar包,解压后:

在这里插入图片描述

上面安装过程默认把lsz和lrz安装到了/usr/local/bin/目录下,现在我们并不能直接使用,下面创建软链接(即桌面快捷方式),并命名为rz/sz。依次执行命令:

cd /usr/bin
ln -s /usr/local/bin/lrz rz
ln -s /usr/local/bin/lsz sz

cd /usr/bin要返回根目录执行。

使用

sz:将Linux上的文件传给windows系统上;
rz:将windows上的文件传给Linux系统上。

rz

在这里插入图片描述

sz

在这里插入图片描述

当前使用的终端是XShell。

下载NDK

ndk官网下载地址:

https://github.com/android/ndk/wiki/Unsupported-Downloads

以下载版本r17为例:

wget https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip

在这里插入图片描述

下载后需解压android-ndk-r17c-linux-x86_64.zip

unzip android-ndk-r17c-linux-x86_64.zip

配置NDK的Linux环境变量

下面是CentOS环境的配置流程

vim /etc/profile 进入vim编辑模式,添加配置:

##### android ndk 环境变量配置 ################## android ndk linux平台
export NDK="/root/ndk/tools/android-ndk-r17c"
# ndk的命令操作配置到Linux环境变量中
export PATH=$NDK:$PATH

接着让环境变量生效source /etc/profile

最后验证环境变量是否配置成功build-ndk

在这里插入图片描述

Ubuntu环境参考这篇

交叉编译

交叉编译可以理解为,在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序:

比如,我们在 x86 平台上,编写程序并编译成能运行在 ARM 平台的程序,编译得到的程序在 x86 平台上是不能运行的,必须放到 ARM 平台上才能运行。

本地编译

本地编译可以理解为,在当前编译平台下,编译出来的程序只能放到当前平台下运行。平时我们常见的软件开发,都是属于本地编译:

比如,我们在 x86 平台上,编写程序并编译成可执行程序。这种方式下,我们使用 x86 平台上的工具,开发针对 x86 平台本身的可执行程序,这个编译过程称为本地编译。

交叉编译是有一整套编译工具链组成,每个链都有一套工具集来实现,最终将源文件(c文件)生成二进制程序bin执行文件,其中gcc就是整个编译过程(预处理、编译、汇编、链接)实现功能工具。

在这里插入图片描述

配置GCC的Linux环境变量

这里利用Android NDK自带的GCC工具,以armeabi为例,GCC工具位于如下目录:

在这里插入图片描述

临时环境

配置临时环境变量后,执行gcc -o命令后会发现找不到头文件错误fatal error: stdio.h: No such file or directory #include<stdio.h>

关于GCC的一些参数,可参考 https://www.runoob.com/w3cnote/gcc-parameter-detail.html

在这里插入图片描述

export GCC=/root/ndk/tools/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc

需要在执行编译命令的时候添加对应的参数,来指定头文件和库文件的目录,主要参数如下:

/**
*使用xx作为这一次编译的头文件与库文件的查找目录,配置了之后就会在编译的时候自动去查找XX目录下的usr/include的头文件和usr/lib目录下的库文件,如gcc --sysroot=目录1 main.c 编译时就会去 目录1/usr/include查找头文件和目录1/usr/lib找库文件(一般是.a和.so)
*/
--sysroot=XX   /**
*指定头文件查找目录,配置之后会自动覆盖--sysroot中的头文件查找目录,查找XX/usr/include的头文件,如gcc --sysroot=目录1 -isysroot 目录2 main.c 编译时就不会去 目录1/usr/include下找头文件而是去 目录2/user/include下查找头文件和到目录1/usr/lib下查找库文件
*/
-isysroot XX   /**
*指定头文件查找目录,配置之后会直接查找查找XX目录下的头文件,如gcc --sysroot=目录1 -isysroot 目录2  -isystem 目录3 main.c 编译时就不会去 目录1/usr/include下找头文件而是到 目录2/user/include和目录3下查找头文件和到目录1/usr/lib下查找库文件
*/
-isystem XX   /**
*指定头文件查找目录,配置之后会直接查找查找XX目录下的头文件,如gcc --sysroot=目录1 -isysroot 目录2  -isystem 目录3 -I 目录4 main.c 编译时就不会去 目录1/usr/include下找头文件而是到 目录2/user/include和目录3及目录4下查找头文件和到目录1/usr/lib下查找库文件
*/
-I XX         
/*****其中优先级: -I > -isystem > -isysroot所谓优先级指的是同时配置了这些指令的话编译时会先到优先级大的目录下查找,找到了就不会去查找其他目录的头文件了,所以以上的注释都是在优先级高的找不到的时候再继续往低一级的查找,而且不会去递归去查找指定目录下的子目录********//**
*链接指定XX目录下的xx.so,gcc -L目录1 -l库名,比如连接到NDK的日志库和libEGL库可以使用gcc -LC:\AndroidIDE\SDK\ndk-bundle\platforms\android-26\arch-arm\usr\lib -llog.so -lEGL.so(当然也可以使用--sysroot来指定)
*/
-L:XX  -lxx.so  

指定头文件和库文件的路径参数:

export GCC_CONFIG=“–sysroot=/root/ndk/tools/android-ndk-r17c/platforms/android-27/arch-arm -isystem /root/ndk/tools/android-ndk-r17c/sysroot/usr/include”

这里的-isystem参数是寻找头文件,--sysroot参数是库文件。编译执行命令:

在这里插入图片描述

执行命令后会报错fatal error: asm/types.h: No such file or directory。根据报错提示可知在include目录找不到types.h,查看include目录确实是没有的。但在arm-linux-androideabi目录下是存在的,因此我们可以通过配置指定头文件和库文件的查找目录。

在这里插入图片描述

export GCC_CONFIG=“–sysroot=/root/ndk/tools/android-ndk-r17c/platforms/android-27/arch-arm -isystem /root/ndk/tools/android-ndk-r17c/sysroot/usr/include -isystem /root/ndk/tools/android-ndk-r17c/sysroot/usr/include/arm-linux-androideabi”

添加以上的参数后再次执行编译命令:$GCC $GCC_CONFIG -pie main.c -o mainclient是可以编译通过。

永久环境

vim /etc/profile 进入vim编辑模式配置永久环境。


#### ndk gcc 交叉编译环境变量配置 ############### 这里只配置armeabi-v7a、arm64-v8a两个指令集cpu架构
export NDK_GCC_arm="$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc"export NDK_GCC_arm_64="$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc"# 寻找头文件、库文件配置
export NDK_CONFIG_arm="--sysroot=$NDK/platforms/android-27/arch-arm -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/arm-linux-androideabi"export NDK_CONFIG_arm_64="--sysroot=$NDK/platforms/android-27/arch-arm64 -isystem $NDK/sysroot/usr/include -isystem $NDK/sysroot/usr/include/aarch64-linux-android"# 静态库打包配置export NDK_AR_arm="$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar"export NDK_AR_arm_64="$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"

查看Android设备 CPU架构的命令
adb shell cat /proc/cpuinfo
adb shell getprop ro.product.cpu.abi

不要忘记了执行source /etc/profile命令,让环境配置生效。

最后测试gcc的环境变量是否生效,执行gcc命令:

在这里插入图片描述

编译打包静态库和动态库

库本质上是一种可执行的二进制代码,可以被OS载入到内存中去执行,根据载入内存的时间和流程可以分为静态库(Linux中后缀名为”.a”)和动态库(Linux中后缀名为”.so”)两种

静态库

静态库包含了所有执行的代码,库中所有的函数机器码在编译链接时全部被拷贝到可执行的文件中并被添加到和它链接的每一个程序中,简单来说就是把库文件的代码全部加入到可执行文件中,因此生成的文件比较大(打包成APK也较大),但在运行时也就不再需要库文件了,所以静态库节省时间,不需要再进行动态链接,需要调用的代码直接就在代码内部。不过当静态库中某一个函数改变生活,所有使用这个静态库的程序都得重新编译,适用于内部使用。

动态库

动态库在编译链接时并没有把库文件的全部代码加入到可执行文件中,而是在程序运行时由运行时的链接文件加载库,即运行时再动态申请调用进行链接拷贝,gcc在编译时默认使用动态库,动态库能节省空间,如果一个动态库被两个程序调用,那么这个动态库只需要在内存中即可(因为加载器在加载动态库时,OS会先检查对应的动态库是否已经因为其他程序把这个动态库信息加载到了内存中,若没有加载到内存中,OS会将动态库载入内存,并将它的引用计数设置为1;如果已经加载到内存,仅将动态库的引用计数加1,无需重新加载),而且Java在不经过封装的情况下只能直接使用动态库,因此动态库又称为共享库。

生成动态库

在这里插入图片描述

  • libgetndk.so:交叉编译的动态库
  • libget.so: Linux服务的动态库,未交叉编译,在Android中不能运行会报错。
Linux服务器的动态库

创建一个动态库so文件,可以使用GCC的-shared选项,同时还需结合-fPIC选项。

  • -shared:生成动态库
  • -fPIC:-fPIC 选项作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code);这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

在这里插入图片描述

-o 是指定生成的输出文件;其中libget.so就是生成的动态库。

gcc -fPIC -shared get.c -o libget.so

以上方式生成的so文件动态库,是没有交叉编译的,在Android项目中是无法使用。(只有在本机Linux环境下使用)

在这里插入图片描述

file xxx.so查看so库信息,从上图可知,so库仅支持64位,因为我本机windows是64位机子。

交叉编译动态库

生成交叉编译库,必须使用Android自带的GCC服务。

在这里插入图片描述

$NDK_GCC_arm_64 $NDK_CONFIG_arm_64 -fPIC -shared get.c -o libgetndk.so

libgetndk.so是打包出来的64位ARM动态库。其中$NDK_GCC_arm_64$NDK_CONFIG_arm_64是前面配置环境变量,第一个是NDK内置的GCC服务指令,第二个参数是指定C的文件和库文件的寻找路径,解决无法查找头文件等资源问题。

生成静态库

静态库打包相对会复杂些。要分为两步走,第一步输出.o目标文件,第二步根据.o文件来打包生成.a文件,即静态库。

Linux服务器的静态库

在这里插入图片描述

第一步:gcc指令输出.o目标文件

gcc -fPIC -c get.c -o get.o

-c:指定输出目标文件的源文件,-o:输出文件。

第二步:打包生成静态库,通过ar rcs指令编译生成静态库。

ar rcs -o libget.a get.o

-o后面有两个参数,第一个是静态库的名称,第二个生成静态库的源文件(.o文件)。

交叉编译静态库

除了GCC服务指令需要使用NDK内置的,另外ar指令也是一样
要NDK内置的。前面在环境变量要配置的,NDK_AR_arm_64便是内置ar指令;否则,如果其中的某个步骤不是使用NDK内置指令,生成的静态库在Android项目运行是报错。

在这里插入图片描述

第一步:

$NDK_GCC_arm_64 $NDK_CONFIG_arm_64  -fPIC -c get.c -o  getndk.o

第二步:

$NDK_AR_arm_64 rcs -o libgetndk.a getndk.o

最终libgetndk.a就是交叉编译的静态库。

将动态库和静态库部署到Android项目中

通过lrsz工具将库从Linux导出到Windows中,使用sz

在这里插入图片描述

注意都是arm64的交叉编译库

使用AS创建C/C++工程,通过CMake导入静态库、动态库。

CMake 是跨平台的构建工具 , 其可以根据不同类型的平台 , 不同类型的编译器 , 生成对应的 Makefile ,开发者通过使用CMakeLists.txt 进行配置构建编译项目。

在这里插入图片描述

main/cpp目录下存放静态库;main/jinLibs目录下放动态库。

CMakeLists.txt文件配置导入静态库或动态库的链接。

静态库:


# 批量导入cpp目录的c++源文件, 在add_library中会用上
file(GLOB allCpp *.cpp)# 关于 https://cmake.org/cmake/help/latest/command/add_library.html#command:add_library
# https://blog.csdn.net/LaineGates/article/details/108242803
# 导入动态库 cpp目录的C++源文件为例,jniLibs下的动态库也可以用这种方式导入
add_library(${CMAKE_PROJECT_NAME}# 动态库SHARED# List C/C++ source files with relative paths to this CMakeLists.txt.# C++源文件${allCpp})# 导入静态库
# getndk 是别名
add_library(getndk STATIC IMPORTED)
# 设置静态库的位置, CMAKE_SOURCE_DIR: CmakeLists.text的目录 ,CMAKE_ANDROID_ARCH_ABI:根据设备的cpu架构去对应目录查找
set_target_properties(getndk PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/${CMAKE_ANDROID_ARCH_ABI}/libgetndk.a)        # 最后将静态库和动态库链接到总库中一起打包
target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroidlog # 日志库# 将库打包到CMAKE_PROJECT_NAME(libjni_demo.so)里面getndk)

重点在add_libraryset_target_properties这两个API上。

  • add_library:导入静态库或动态库,第一个参数是库的别名,在链接到目标总库(target_link_libraries)会使用上;第二次参数是库的类型,动态库SHARED、静态库STATIC ;第三个参数通常是变量IMPORTED,具体可以参考这篇
  • set_target_properties:是指定库的路径。

动态库类似的,区别是在于库的路径不同,其他配置都是一样的。

# 导入动态库 路径是jniLibs目录
add_library(getndk SHARED IMPORTED)
set_target_properties(getndk PROPERTIES IMPORTED_LOCATION${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libgetndk.so)

除了add_library方式导入库,还有set(CMAKE_CXX_FLAGS xxxx) API也是可以导入动态库或静态库。

# CMAKE_CXX_FLAGS  "xxxx" key value格式 jniLibs的路径
# CMAKE_SOURCE_DIR: CmakeLists目录的路径
# CMAKE_ANDROID_ARCH_ABI:当前设备支持的cpu架构模式,比如 armeabi-v7a
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libgetndk.so")

这里推荐使用add_library方式,可读性更强。

动态库还需要在Java代码中加载,如下:

在这里插入图片描述

最后就是在项目build.gradle配置ndk支持的CPU架构模式

  defaultConfig {.....ndk {// 配置哪些CPU架构参与APK打包abiFilters.addAll(arrayOf("arm64-v8a"))}externalNativeBuild {cmake {// 生成的目标链接总库支持哪些CPU架构abiFilters.addAll(arrayOf("arm64-v8a"))}}}

参考

  • https://www.cnblogs.com/clicli/p/5941828.html
  • https://blog.csdn.net/michaelwoshi/article/details/111305037
  • https://blog.csdn.net/qq_38410730/article/details/94151172
  • https://blog.csdn.net/pengfei240/article/details/52912833
  • https://www.runoob.com/w3cnote/gcc-parameter-detail.html
  • https://blog.csdn.net/CrazyMo_/article/details/83379994
  • https://www.cnblogs.com/liuzhenbo/p/11030946.html
  • GCC编译选项参数 https://zhuanlan.zhihu.com/p/513148985
  • https://blog.csdn.net/chenhao0568/article/details/120239398
  • CMake API https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html
  • add_library https://blog.csdn.net/LaineGates/article/details/108242803
  • CMake应用:CMakeLists.txt完全指南 https://zhuanlan.zhihu.com/p/371257515
http://www.lryc.cn/news/581302.html

相关文章:

  • React Native 亲切的组件们(函数式组件/class组件)和陌生的样式
  • RabbitMQ 4.1.1初体验-队列和交换机
  • 快速掌握Python编程基础
  • 结构型智能科技的关键可行性——信息型智能向结构型智能的转变(修改提纲)
  • 小架构step系列05:Springboot三种运行模式
  • 黑马点评系列问题之基础篇p7 06初识redis无法在虚拟机查到图形化界面存进去的键
  • 运算方法和运算器补充
  • TCP协议概念和特性
  • AI Agent与Agentic AI原理与应用(下) - 主流Agent平台、框架与项目技术拆解
  • 编程中的英语
  • cocos 打包安卓
  • Rust与PyTorch实战:精选示例
  • 机器学习--实践与分析
  • python优先队列使用
  • NAT、代理服务、内网穿透
  • Ubuntu 22.04 修改默认 Python 版本为 Python3 笔记
  • C#使用开源框架NetronLight绘制流程图
  • C++------模板初阶
  • JS 网页全自动翻译v3.17发布,全面接入 GiteeAI 大模型翻译及自动部署
  • 2025年的前后端一体化CMS框架优选方案
  • 【大模型入门】访问GPT的API
  • 【Halcon】WPF 自定义Halcon显示控件完整流程与 `OnApplyTemplate` 未触发的根本原因解析!
  • day 60 python打卡
  • ffplay6 播放器关键技术点分析 1/2
  • Windows内核并发优化
  • rk3128 emmc显示剩余容量为0
  • 深度学习5(深层神经网络 + 参数和超参数)
  • 力扣网编程55题:跳跃游戏之逆向思维
  • 前端相关性能优化笔记
  • Python数据容器-list和tuple