SAKA'S BLOG

shell脚本生成安卓全abi动态库与静态库

以jrtplib的依赖库jthread的编译为例,介绍cmake的交叉编译,因为这个库相对简单,构建完成后就可以直接使用,将动态库或者静态库放置到安卓指定的文件夹下,通过引用头文件来编写jni即可。

这篇文章用到了一点cmake交叉编译的东西,在我的掘金主页有关于如何使用cmake的教程。
https://juejin.im/post/5a8ebe006fb9a0635a6574de

文章废话少,干货多,请静下心来看:

关于ndk的东西网上已经烂大街了,我这里就不多介绍了。这篇文章主要是关于如何使用bash控制cmake来cross-compile。

  1. 创建build文件夹用于外部构建
  2. 创建target及架构平台文件夹用于存放构建好的动态库
  3. cmake外部编译、交叉编译
  4. 将构建好的项目导出至target文件夹下

ndk-build生成多abi动态库相对简单,安卓环境的cmake环境生成动态库也很简单,不多说,但是在非安卓cmake环境中的cmake中我目前还没找到如何一次生成多个平台架构,所以写了一个脚本来实现。

由于脚本相对来说比较简单,这文章篇幅不会很长:

android架构

armeabi
armeabi­v7a
arm64­v8a
x86
x86_64
mips
mips64
这七种是我们今天将要编译的abi,这七个文件夹会在target文件夹中生成,并在对应的文件夹下生成动态库

一些变量

使用cmake交叉编译时必须找到ndk路径,否则不能编译,定义如下,将NDK_PATH替换为自己的路径即可。

1
2
3
4
5
6
7
#!/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文件夹是构建的文件夹,所有的生成文件都会在这边,所以先检测是否有这个文件夹,没有就创建,有就清除里边的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#清除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对应的文件夹下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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架构的文件夹,第三个用于复制库到创建好的文件夹下。

构建函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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即可自动补全你想要的架构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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命令。

1
source build.sh

这个命令是用来输出执行脚本,然后我们就可以用构建命令了

1
sbuild all

构建全平台

1
sbuild armeabi

构建armeabi平台。七种架构都能单独构建。
单独构建后只有一种平台架构:

1
2
3
4
5
6
.
└── armeabi
├── libjthread.a
└── libjthread.so

1 directory, 2 files

全部构建后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.
├── 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编程来实现的,不能直接来调用。