写这篇文章,来记录下如何在Linux平台下使用NDK编译出能够在AndroidStudio中使用的动态库。
在进入正题之前,最好先熟悉下这几个知识点。
- 查看gcc相关的知识:交叉编译必知--gcc/g++详细讲解,这篇文章介绍了gcc/g++,如何链接头文件以及库文件,gcc/g++参数的传递。
- 查看动态库的相关知识:生成以及链接动态库
- 查看如何编译Android平台的可执行程序,这篇文章讲解了如何在Linux平台下编译出Android平台可用的可执行程序。
这篇文章可以学到什么?
- 交叉编译生成.so文件,拿到AndroidStudio中使用。
- 静态库和动态库在CMakeLists.txt文件中的配置。
- 如果打包生成支持多种cpu的so库。
环境配置:
- 交叉编译使用的是Ubantu,我这里使用xShell连接的阿里云的服务器,可以查看xShell连接阿里云服务器
- 交叉编译使用的NDK17(Linux版本的NDK),
- AndroidStudio版本是3.4。
下面直接进入正题
设置NDK中的变量
export CC=/root/softffmpeg/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc
export AAA="--sysroot=/root/softffmpeg/android-ndk-r17c/platforms/android-21/arch-arm -isystem /root/softffmpeg/android-ndk-r17c/sysroot/usr/include -isystem /root/softffmpeg/android-ndk-r17c/sysroot/usr/include/arm-linux-androideabi"
使用echo $CC
可以查看变量的路径。
生成动态库
$CC $AAA -fPIC -shared test.c -o libTest.so
$CC $AAA
意为将AAA当做参数传递给CC。
test.c文件
int test(){
return 1;
}
将生成的libTest.so拿到AndroidStudio中使用
将listTest.so放在jniLibs目录下
接下来配置下CMakeLists.txt文件
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")
target_link_libraries(
native-lib
Test
log )
注意:CMake通过生成makefile文件,makefile文件生成.a和.so文件。
add_library( native-lib SHARED src/main/cpp/native-lib.cpp )
native-lib
:变量名字,这个名字随便起
SHARED
:动态库 ;静态STATIC为
src/main/cpp/native-lib.cpp
:源文件
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")
上面这句话是设置一个变量。
CMAKE_CXX_FLAGS
是c++的参数 会传给编译器,相当于--sysroot=XX。
CMAKE_C_FLAGS
是c的参数 会传给编译器。
CMAKE_SOURCE_DIR
代表的是这个文件(CMakeLists.txt)的所在的目录。
上面的set语句和下面的是一样的,效果也是一样的:
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp )
#这两句和上面的set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")是一样的
add_library(Test SHARED IMPORTED)
set_target_properties(Test PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libTest.so)
#-lTest
target_link_libraries( # Specifies the target library.
native-lib
Test
#引入NDK中的库
log
)
上面使用的都是我们自己的库,那NDK的库在什么位置呢?
比如上面我使用了NDK中的log这个库,其实它在系统中的位置如下:
E:\androidstudio3.4\SDK\ndk-bundle\platforms\android-21\arch-arm\usr\lib
如果需要编译多个平台的so文件,可以使用
ANDROID_ABI
设置:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI} ")
引入静态库和引入动态库的区别?
引入静态库
add_library(Test2 STATIC IMPORTED)
set_target_properties(Test2 PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libTest.a)
target_link_libraries(
native-lib
Test2
)
IMPORTED
:表示我们这一个静态库是以导入的形式添加进来的(预编译静态库)
MainActivity.java中配置
static {
System.loadLibrary("Test");
System.loadLibrary("native-lib");
}
native-lib.cpp中使用
#include
#include
#include
#define LOG_TAG "atguigu"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ERROR,LOG_TAG ,__VA_ARGS__)
extern "C"{//这个文件是C++写的,libTest.so中的test方法是C写的
extern int test();//注意:这里使用extern来调用.so中的方法
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
LOGI("调用so的test()方法%d\n",test());
return env->NewStringUTF(hello.c_str());
}
extern
的作用
1)C++文件中引用C的代码
2)C++代码中引用其它库中的方法。
app下的build.gradle配置
android {
defaultConfig {
//指导我们的源文件编译
externalNativeBuild {
cmake {
cppFlags ""
//你希望编译你的c/c++源文件,编译几种cpu(arm,x86等)
abiFilters "armeabi-v7a"
//abiFilters "arm64-v8a","armeabi-v7a"
}
}
//这里表示打包集中cpu,比如集成了第三方库,第三方库提供了arm的,提供了x86的,可以在此处指导打包arm的,生成出来的apk就包含arm的。
ndk{
abiFilters "armeabi-v7a"
//abiFilters "arm64-v8a","armeabi-v7a"
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"//这里指定CMakeLists.txt文件的路径
version "3.10.2"
}
}
}
上面最外层的externalNativeBuild中指定了CMakeLists.txt文件的位置,如果我们的CMakeLists.txt文件在别的位置,比如放在了cpp目录下了,那这里的路径应该这样处理?
将
path "CMakeLists.txt
改为
path "src/main/cpp/CMakeLists.txt
并且CMakeLists.txt中的相关目录也要修改,我这里需要将
set_target_properties(Test PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libTest.so)
改为
//..代表CMakeLists.txt文件所在目录的上一级目录
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}")
如何打包成多cpu的so库?
比如我现在想打包armeabi-v7a和arm64-v8a的两个库:
1)在jniLibs目录下再创建一个文件夹,把armeabi-v7a中的so库拷贝到arm64-v8a中。
2)修改下CMakeLists.txt中文件
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}")
我们将之前的set注释掉,使用ANDROID_ABI
来动态的加载libTest.so库。
3)修改app下的build.gradle文件
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
abiFilters "arm64-v8a","armeabi-v7a"
}
ndk {
ldLibs "log"//实现__android_log_print
}
}
ndk{
//这里我们的库支持两种cpu
abiFilters "arm64-v8a","armeabi-v7a"
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.10.2"
}
}
}
运行之前先clear
下,运行成功后,我们查看下面这个路径的apk
app\build\outputs\apk\debug\app-debug.apk
点击进行查看
可以看到成功打包了支持两个cpu的so库。