用于 Java 程序与 C、C++ 库间的互相调用。
它使用 JNI 实现 Java 程序调用 C/C++ 本地代码,允许 C/C++ 本地代码访问 Android API,不只是用来开发或移植 C/C++ 库,也可以是 C/C++ 程序。
引用自 Android NDK | Android NDK | Android Developers
Android NDK
The Android NDK is a toolset that lets you implement parts of your app in native code, using languages such as C and C++. For certain types of apps, this can help you reuse code libraries written in those languages.
NDK 提供一系列稳定的 C/C++ API,头文件在 sysroot/usr/include
下,主要包括 C 标准库、C++ 标准库、jni、math、pthread、zlib、OpenGL、Android 相关的库, NDK 支持的 API 也会随着需求的增加而日趋完善。
NDK 提供了创建独立工具链的工具,对于移植使用 GNU Autotools 的项目到 Android 平台很有帮助,省去写 Android.mk
。
创建独立工具链
/opt/android-ndk/build/tools/make_standalone_toolchain.py \
--arch arm64 --api 28 --stl=libc++ --install-dir /opt/android-toolchain
环境变量配置
export PATH=$PATH:/opt/android-toolchain/bin
export CC=aarch64-linux-android-clang
export CXX=aarch64-linux-android-clang++
export LD=aarch64-linux-android-ld
export AR=aarch64-linux-android-ar
export STRIP=aarch64-linux-android-strip
export RANLIB=aarch64-linux-android-ranlib
export AS=aarch64-linux-android-clang
export CFLAGS="-fPIE -fPIC"
export CXXFLAGS="-fPIE -fPIC"
export LDFLAGS="-pie"
export SYSROOT=/opt/android-toolchain/sysroot
export CROSS_COMPILE_HOST=aarch64-linux-android
交叉编译
./configure --host=${CROSS_COMPILE_HOST} --prefix=/opt/local
make
make install
编译出错 cstddef:43:25: fatal error: stddef.h: No such file or directory
看起来是 C++ 编译器找不到 C 头文件,是已知问题,在 Standalone 工具链中使用 gcc
就会出现,见
stddef.h: No such file or directory · Issue #215 · android-ndk/ndk
改为使用 clang
就好了,以后的 NDK 将彻底移除对 gcc
的支持。
交叉编译 OpenSSL
网上有大量的移植文档,基本上是基于 OpenSSL Android - OpenSSLWiki ,最大的问题是使用的 NDK 和 OpenSSL 版本都比较旧,最后主要参考
couchbaselabs/couchbase-lite-libcrypto: Pre-built OpenSSL libcrypto static libraries
移植成功。OpenSSL 的交叉编译需要完整的 NDK 包,最好新开一个 Shell 来编译 OpenSSL,避免为独立工具链设置的环境变量影响到 OpenSSL 的编译。
编译过程中会报错 crtbegin_so.o: No such file: No such file or directory
,将它从工具链中拷到当前目录即可, crtend_so.o
、 crtbegin_dynamic.o
、 crtend_android.o
也进行相同处理,参考 gcc - crtbegin_so.o missing for android toolchain (custom build) - Stack Overflow
# Download
wget https://codeload.github.com/openssl/openssl/zip/OpenSSL_1_1_0-stable -O openssl-OpenSSL_1_1_0-stable.zip
unzip openssl-OpenSSL_1_1_0-stable.zip
wget https://raw.githubusercontent.com/couchbaselabs/couchbase-lite-libcrypto/master/build-android-setenv.sh -O openssl.setenv
sed -i -e 's/^_ANDROID_NDK=/#_ANDROID_NDK=/g' openssl.setenv
# Config
export ANDROID_NDK_ROOT=/opt/android-ndk
export _ANDROID_TARGET_SELECT=arch-arm64-v8a
export _ANDROID_NDK="android-ndk"
export ANDROID_EABI_PREFIX=aarch64-linux-android
export _ANDROID_EABI="${ANDROID_EABI_PREFIX}-4.9"
export _ANDROID_ARCH=arch-arm64
export _ANDROID_API="android-28"
source openssl.setenv
# Build
cd openssl-OpenSSL_1_1_0-stable
./Configure dist
./Configure no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/opt/local/ --prefix=/opt/local/ linux-generic64 -DB_ENDIAN -B$ANDROID_DEV \
-I${ANDROID_NDK_ROOT}/sysroot/usr/include -I${ANDROID_NDK_ROOT}/sysroot/usr/include/${ANDROID_EABI_PREFIX} \
-fPIE -pie -L${ANDROID_NDK_ROOT}/platforms/${_ANDROID_API}/${_ANDROID_ARCH}/usr/lib
ln -s ${SYSROOT}/usr/lib/crtbegin_so.o
ln -s ${SYSROOT}/usr/lib/crtend_so.o
ln -s ${SYSROOT}/usr/lib/crtbegin_dynamic.o
ln -s ${SYSROOT}/usr/lib/crtend_android.o
make -j6
make install
交叉编译出的配置检测程序无法直接运行
Nginx 没有使用 GNU Autotools,而是有自已的 Configure 脚本,遇到的第一个问题就是 Nginx 是通过使用编译器编译一个测试程序来获取配置信息,交叉编译出来的程序肯定是无法在编译机上运行的,一个可靠的办法就是修改 Configure 脚本,改为上传到目标设备上执行。
下面是我写好的一个远程执行脚本 execute
#!/bin/bash
if [ $# != 2 ] || ([ $1 != "-c" ] && [ $1 != "-f" ]) ; then
echo "usage: $0 -<c|f> <cmd|file>" >> /dev/stderr
exit 22 #Invalid argument
fi
LOG_FILE=/dev/null
if [[ "$CROSS_COMPILE_HOST" = *"android"* ]]; then
adb connect ${CROSS_COMPILE_DEVICE_IP} >>$LOG_FILE 2>&1
adb wait-for-device
adb root >>$LOG_FILE 2>&1
adb connect ${CROSS_COMPILE_DEVICE_IP} >>$LOG_FILE 2>&1
adb wait-for-device
adb remount >>$LOG_FILE 2>&1
adb connect ${CROSS_COMPILE_DEVICE_IP} >>$LOG_FILE 2>&1
adb wait-for-device
if [ $1 == "-f" ]; then
tempfile=`mktemp -u XXXXXXXX`
tempdir=/tmp/cross-compile
adb shell mkdir ${tempdir} >>$LOG_FILE 2>&1
adb push $2 ${tempdir}/${tempfile} >>$LOG_FILE 2>&1
adb shell "find ${tempdir} -ctime +1 -type f -exec rm {} \; >/dev/null 2>&1; cd ${tempdir} && chmod a+x $tempfile && ./$tempfile"
else
adb shell $2
fi
else
if [ $1 == "-f" ]; then
tempfile=`mktemp -u XXXXXXXX`
tempdir=/tmp/cross-compile
ssh -o StrictHostKeyChecking=no root@${CROSS_COMPILE_DEVICE_IP} mkdir ${tempdir} >>$LOG_FILE 2>&1
scp -o StrictHostKeyChecking=no $2 root@${CROSS_COMPILE_DEVICE_IP}:${tempdir}/${tempfile} >>$LOG_FILE 2>&1
ssh -o StrictHostKeyChecking=no root@${CROSS_COMPILE_DEVICE_IP} "find ${tempdir} -mmin +1 -type f -exec rm {} \; >/dev/null 2>&1; cd ${tempdir} && chmod a+x $tempfile && ./$tempfile"
else
ssh -o StrictHostKeyChecking=no root@${CROSS_COMPILE_DEVICE_IP} $2
fi
fi
修改 nginx-1.14.0
的配置脚本,将本地运行测试程序的地方改为远程执行
sed -i -e 's/\/bin\/sh -c $NGX_AUTOTEST/timeout 10 \/build\/execute -f $NGX_AUTOTEST/g' `find auto -type f`
sed -i -e 's/$NGX_AUTOTEST >\/dev\/null/timeout 10 \/build\/execute -f $NGX_AUTOTEST >\/dev\/null/g' `find auto -type f`
sed -i -e 's/`$NGX_AUTOTEST`/`timeout 10 \/build\/execute -f $NGX_AUTOTEST`/g' `find auto -type f`
运行时需要预先设置环境变量 CROSS_COMPILE_DEVICE_IP
为目标设备 IP,对于嵌入式 Linux 设备,需要使用 ssh-copy-id
命令设置为本机免密码 ssh 登录,对于 Android 设备需要预先 adb 授权通过。
编译时找不到 crypt.h
crypt.h
属于 glibc
, glibc
是 Linux 下的 C 标准库实现,除了实现 ANSI C
标准库还有大量方便 Linux 开发的扩展功能。而 NDK 提供的 C 标准库并非 glibc
而是 Bionic libc ,这导致移植 Nginx 时由于缺少 crypt.h
头文件而编译不过。
可以将 crypt
调用替换为 DES_crypt
sed -i -e 's/#include <crypt.h>/#if (NGX_HAVE_CRYPT_H)\n#include <crypt.h>\n#endif\n#include <openssl\/des.h>/g' src/os/unix/ngx_linux_config.h
sed -i -e 's/= crypt(/= DES_crypt(/g' src/os/unix/ngx_user.c
链接时找不到 glob
函数
NDK 工具链是有提供 glob.h
头文件的,但是链接时却找不到 glob
函数。网络上有很多单独提供 glob 下载的,但是都无法直接通过编译,因为里面有很多平台相关的特性。
Undefined reference to glob and globfree in libc.so · Issue #718 · android-ndk/ndk 中说是已经在 r17b
版本解决,需安装 android-ndk-r17-beta2
同时设置 API Level
为 28
即可。
交叉编译 Nginx
可使用独立工具链来交叉编译 Nginx,参考前面环境变量配置部分先设置好工具链的环境变量。
CC_TEST_FLAGS="${CFLAGS} ${LDFLAGS}" ./configure --with-ld-opt="${LDFLAGS}" --prefix=/opt/local --crossbuild=`/build/execute -c 'uname -srm' | tr ' ' ':'` --user=root --group=root --with-select_module --with-poll_module --with-file-aio --with-http_ssl_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module
make
make install
编译出的 Nginx 无法在低版本的 Android 上运行
之前为了使用最新的 NDK 工具链包含的函数 glob
,而将 API Level
设置成 28
,这就意味着编译出来的程序无法在低版本的 Android(<=8.1) 上运行,在 Android 7.1 上运行会报错 /system/bin/nginx: No such file or directory
。
将 NDK 最新工具链带的 so
也拷到设备上,运行程序前设置一下 $LD_LIBRARY_PATH
优先使用最新的 so
,Nginx 运行后直接卡死或者立即崩溃。
添加 -static -Wl,--dynamic-linker=/system/bin/linker
链接选项静态编译,运行时会报错 /system/bin/nginx: Accessing a corrupted shared library
的错误,需要使用 /system/bin/linker64
。
由于 -static
与 -pie
是互相冲突的选项,静态编译的程序运行时会报错 only position independent executables (PIE) are supported.
。
编译在 Android 5 及以上版本运行的 Nginx
参考以下项目,通过使用 docker 方便交叉编译 nginx-1.14.0
tangxinfa/android-nginx: Cross compile nginx with android ndk
一般的 C/C++ 库通常本身就会注重可移植性,不会生硬地依赖系统底层特性,使用 NDK 移植是可行的,即使是 ffmpeg
这种大型的库也可以成功移植到 Android。
而对于一些 Linux 下的程序,使用 NDK 直接移植会有很大的失败机率,因为他们可能使用了 NDK 不支持的特性(如 glibc
)。
NDK 一直在改进,以前阻碍我们移植到 Android 的问题很可能会在新版本中解决,遗憾的是编译出的程序无法运行在旧版本 Android 上。