交叉编译-生成动态库在AndroidStudio中使用

写这篇文章,来记录下如何在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库。

你可能感兴趣的:(交叉编译-生成动态库在AndroidStudio中使用)