以jrtplib的依赖库jthread的编译为例,介绍cmake的交叉编译,因为这个库相对简单,构建完成后就可以直接使用,将动态库或者静态库放置到安卓指定的文件夹下,通过引用头文件来编写jni即可。
这篇文章用到了一点cmake交叉编译的东西,在我的掘金主页有关于如何使用cmake的教程。
juejin.cn/post/684490…
文章废话少,干货多,请静下心来看:
关于ndk的东西网上已经烂大街了,我这里就不多介绍了。这篇文章主要是关于如何使用bash控制cmake来cross-compile。
- 创建build文件夹用于外部构建
- 创建target及架构平台文件夹用于存放构建好的动态库
- cmake外部编译、交叉编译
- 将构建好的项目导出至target文件夹下
ndk-build生成多abi动态库相对简单,安卓环境的cmake环境生成动态库也很简单,不多说,但是在非安卓cmake环境中的cmake中我目前还没找到如何一次生成多个平台架构,所以写了一个脚本来实现。
由于脚本相对来说比较简单,这文章篇幅不会很长:
android架构
armeabi
armeabiv7a
arm64v8a
x86
x86_64
mips
mips64
这七种是我们今天将要编译的abi,这七个文件夹会在target文件夹中生成,并在对应的文件夹下生成动态库
一些变量
使用cmake交叉编译时必须找到ndk路径,否则不能编译,定义如下,将NDK_PATH替换为自己的路径即可。
#!/bin/bash
#ndk的路径,替换为自己的路径
export NDK_PATH=/Users/rangaofei/Library/Android/sdk/ndk-bundle
#将要构建的架构
TARGETS=(armeabi arm64-v8a armeabi-v7a mips mips64 x86 x86_64)
准备build文件夹
采用外部构建的方式,build文件夹是构建的文件夹,所有的生成文件都会在这边,所以先检测是否有这个文件夹,没有就创建,有就清除里边的内容。
#清除build文件夹下的内容
function clean_build() {
if ([ -d build ]); then
echo "prepare to clean cache"
(rm -rf ./build/*)
echo "complete"
else
echo "build is not a directory"
exit 0
fi
}
function prepare_build() {
# 检测是否有Build文件夹,有的话删除文件夹,没有的话创建文件夹
if ([ -e build ]); then
echo "you already have build dir"
clean_build
else
echo "prepare to create dir build"
mkdir build
fi
}
准备target文件夹
target文件夹是我们用来存放各种abi动态库的文件夹,我们会将build文件夹中生成的动态库和静态库复制到target对应的文件夹下
function prepare_target() {
#检测是否有所有的target文件夹,有则删除,没有则创建
if ([ -e target ] && [ -d target ]); then
echo "prepare to clean target"
rm -rf ./target/*
echo "clean target complete"
else
echo "you not have target_dir,we will create it"
mkdir target
fi
}
function create_child_dir() {
if ([ -e target ]); then
(
cd target
mkdir $1
)
else
echo "target is not a dir"
fi
}
function move_to_target() {
if ([ -e ./build/src/libjthread.a ] && [ -e ./build/src/libjthread.so ]); then
echo "prepare move target to ./target/$1"
mv ./build/src/libjthread.a ./target/$1
mv ./build/src/libjthread.so ./target/$1
echo "move to ./target/$1 finished"
else
echo "move error"$()
fi
}
这里边有三个函数,第一个用来创建target文件夹,第二个用来创建abi架构的文件夹,第三个用于复制库到创建好的文件夹下。
构建函数
function build_lib() {
clean_build
cd build
cmake .. \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_SYSTEM_VERSION=21 \
-DCMAKE_ANDROID_ARCH_ABI=$1 \
-DCMAKE_ANDROID_NDK=$NDK_PATH \
-DCMAKE_ANDROID_STL_TYPE=gnustl_static
}
function create_all_child_dir() {
for dir in ${TARGETS[@]}; do
create_child_dir $dir
echo "$dir created"
done
}
function build_armeabi() {
prepare_build
prepare_target
create_child_dir armeabi
(
build_lib armeabi
make
)
move_to_target armeabi
}
function build_armeabi-v7a() {
prepare_build
prepare_target
create_child_dir armeabi-v7a
(
build_lib armeabi_v7a
make
)
move_to_target armeabi-v7a
}
function build_arm64-v8a() {
prepare_build
prepare_target
create_child_dir arm64-8a
(
build_lib arm64-v8a
make
)
move_to_target arm64-8a
}
function build_mips() {
prepare_build
prepare_target
create_child_dir mips
(
build_lib mips
make
)
move_to_target mips
}
function build_mips64() {
prepare_build
prepare_target
create_child_dir mips64
(
build_lib mips64
make
)
move_to_target mips64
}
function build_x86() {
prepare_build
prepare_target
create_child_dir x86
(
build_lib x86
make
)
move_to_target x86
}
function build_x86_64() {
prepare_build
prepare_target
create_child_dir x86_64
(
build_lib x86_64
make
)
move_to_target x86_64
}
function create_all_target() {
prepare_build
prepare_target
create_all_child_dir
for target in ${TARGETS[@]}; do
(
build_lib $target
make
)
move_to_target $target
done
看起来很长的一段函数,其实没毛的内容,第一个函数是用来交叉编译的主要函数,他接受一个平台参数,也就是我们定义好的TARGETS数组中的元素,后边的七个函数是分别构建单一abi架构。最后一个就牛逼了,他会遍历数组,构建所有的abi平台。
到这里主要功能完成了。
自动补全
为了让程序更完美,我做了一个自动补全的命令,在bash中安tab即可自动补全你想要的架构。
function sbuild() {
echo "-------$1"
case $1 in
"all")
create_all_target
;;
"armeabi")
build_armeabi
;;
"v7a")
build_armeabi-v7a
;;
"v8a")
build_arm64-v8a
;;
"mips")
build_mips
;;
"mips64")
build_mips64
;;
"x86")
build_x86
;;
"x86_64")
build_x86_64
;;
"*") ;;
esac
}
sbuild_list=(${TARGETS[@]} "all")
function _sbuild() {
local cur
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=($(compgen -W "${sbuild_list[*]}" -- ${cur}))
return 0
}
complete -o filenames -F _sbuild sbuild
在这里我们扩展了一下TARGETS数组,添加了一个all,这个参数就是用来构建全平台abi。
执行命令
最后执行一下,最好在bash中执行,zsh好像没有自动补全的complete命令。
source build.sh
这个命令是用来输出执行脚本,然后我们就可以用构建命令了
sbuild all
构建全平台
sbuild armeabi
构建armeabi平台。七种架构都能单独构建。
单独构建后只有一种平台架构:
.
└── armeabi
├── libjthread.a
└── libjthread.so
1 directory, 2 files
全部构建后:
.
├── arm64-v8a
│ ├── libjthread.a
│ └── libjthread.so
├── armeabi
│ ├── libjthread.a
│ └── libjthread.so
├── armeabi-v7a
│ ├── libjthread.a
│ └── libjthread.so
├── mips
│ ├── libjthread.a
│ └── libjthread.so
├── mips64
│ ├── libjthread.a
│ └── libjthread.so
├── x86
│ ├── libjthread.a
│ └── libjthread.so
└── x86_64
├── libjthread.a
└── libjthread.so
7 directories, 14 files
最后说明一下,这个方式是用cmake来构建安卓平台的动态库和静态库的方式,生成好的文件最后还是要放进安卓指定的文件夹中,这些库的调用必须是用jni编程来实现的,不能直接来调用。