在Android Studio中通过CMake实现交叉编译生成动态so文件

文章目录

    • NDK工具链
    • 基本步骤
      • CMake配置文件
        • aux_source_directory
        • include_directories
        • link_directories
        • add_library
        • target_link_libraries
      • 将CMake配置文件依赖到Gradle中
      • 交叉编译生成动态库
    • 参考

NDK工具链

最近项目上需要将微信语音silk文件格式进行编解码,在silk、PCM、AMR、MP3间进行格式转换,将silk-wasm项目的C/C++部分进行迁移,通过CMake和JNI集成到Android项目中,之前有在Linux环境通过NDK进行交叉编译生成so文件,试想有没更简单的方式也能交叉编译生成动态库文件,发现在AS IDE也是可以完成这个工作的。

项目源码:https://github.com/751496032/silk-codec

在Linux环境下通过NDK实现交叉编译库

在Android Studio中准备安装NDK工具链:

  • CMake
  • NDK

在Android Studio中通过CMake实现交叉编译生成动态so文件_第1张图片

可以展开下载对应的版本

基本步骤

步骤:

  • 在创建src/main/cpp目录,将C/C++源代码迁移放在该目录下
  • 创建CMakeLists.txt文件并配置构建项目的信息
  • 将 CMake项目添加为Gradle依赖项中,在build.gradle脚本中设置参数,会传递到CmakeLists.txt中。

第一个步骤,这里就不详细展开讲了,是JNI的基础内容,具体可以参考文章

Android NDK—JNI基础

CMake配置文件

第二步骤,在cpp目录下创建CMakeLists.txt文件,并在文件内配置当前项目的CMake构建信息,内容如下:

cmake_minimum_required(VERSION 3.18.1)

# 项目名称
project(silk)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Os -flto")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Os -flto")

# 添加大小优化选项 对优化大小很重要
# -Os: 优化代码大小的编译选项  -O0 -O1-3
if(CMAKE_BUILD_TYPE STREQUAL "Release")
    # 大小优化
    set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Os")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os")

    # 链接优化
    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -Wl,--gc-sections")
endif()

message(${CMAKE_CXX_FLAGS_RELEASE} )
message(${CMAKE_BUILD_TYPE})



# 添加源文件目录 cpp文件,值为存储在变量中
# key value = 目录 变量名称
aux_source_directory(src SRC_LIST)
aux_source_directory(. NATIVE_LIB)
message(${SRC_LIST}) # src/index.cpp
message(${NATIVE_LIB}) # ./native-lib.cpp
# 添加所有的SILK源文件 c文件
file(GLOB SILK_SRC
        libSilkCodec/silk/src/SILK_SDK_SRC_ARM_v1.0.9/src/*.c
)
file(GLOB SILK_CODEC libSilkCodec/src/*.c) 
message(${SILK_CODEC}) # decoder.c 和 encoder.c 文件



# 添加所有的头文件目录 h文件
include_directories(
        ${CMAKE_CURRENT_SOURCE_DIR}/src
#        ${CMAKE_CURRENT_SOURCE_DIR}/include
        libSilkCodec/silk/src/SILK_SDK_SRC_ARM_v1.0.9/interface/
        libSilkCodec/silk/src/SILK_SDK_SRC_ARM_v1.0.9/src/
        libSilkCodec/src/include
)

# 添加库文件目录
link_directories(
        ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}
        ${CMAKE_CURRENT_BINARY_DIR}
)
# CMAKE_CURRENT_SOURCE_DIR: 代码内置的库文件目录,当前项目暂时没有
# CMAKE_CURRENT_BINARY_DIR:当前项目交叉编译生成的库文件目录
message(${CMAKE_CURRENT_SOURCE_DIR})
message(${CMAKE_CURRENT_BINARY_DIR})



# 创建silk-codec共享库 添加所有的C和C++源文件
add_library(silk-codec SHARED
        ${SRC_LIST}
        ${NATIVE_LIB}
        ${SILK_SRC}
        ${SILK_CODEC}
#        libSilkCodec/src/encoder.c
#        libSilkCodec/src/decoder.c
)

# 查找 Android 日志库
find_library(log-lib log)


# 链接所有的库
target_link_libraries(silk-codec
        ${log-lib}
        android
)

# 移除调试符号
# 在Release模式下移除调试信息 ,优化大小  一定要放在末尾
if(CMAKE_BUILD_TYPE STREQUAL "Release")

    # 方法1:使用strip命令
#    add_custom_command(TARGET silk-codec POST_BUILD
#            COMMAND ${CMAKE_STRIP} --strip-all $
#    )

    # 方法2:使用编译标志
#    target_compile_options(silk-codec PRIVATE
#            -fvisibility=hidden
#            -fdata-sections
#            -ffunction-sections
#    )

    # 方法3:使用链接标志
#    set_target_properties(silk-codec PROPERTIES
#            LINK_FLAGS "-Wl,--strip-all -Wl,--gc-sections"
#    )

endif()

这里重点说下aux_source_directoryinclude_directorieslink_directories三者的区别和作用,首先都是搜集文件,但搜集的文件类型有所不同,在此我们先了解下什么是源文件、头文件、库文件。

  • 源文件:代码的细节实现,需要编译 ,常见格式 .c, .cpp, .cc, .cxx
  • 头文件:函数等声明和定义,会源文件引用实现,常见格式.h, .hpp, .hxx
  • 库文件:静态库和动态库

静态库:

// Windows: .lib
// Linux/Unix: .a
// 示例:libexample.a, example.lib

// 使用示例
target_link_libraries(your_target
    ${CMAKE_SOURCE_DIR}/lib/libexample.a
)

动态库:

// Windows: .dll
// Linux: .so
// macOS: .dylib
// Android: .so
// 示例:libexample.so, example.dll

// 使用示例
target_link_libraries(your_target
    ${CMAKE_SOURCE_DIR}/lib/libexample.so
)
aux_source_directory

aux_source_directory收集所有的源文件并保存变量中,比如上例中的变量SRC_LIST、NATIVE_LIB,变量的值分别是

src/index.cpp
./native-lib.cpp

语法:

// https://cmake.org/cmake/help/latest/command/aux_source_directory.html#command:aux_source_directory
// dir: 文件相对目录 variable:变量名称
aux_source_directory( )

另外也可以file变量指定目录下文件,然后保存到变量中。

include_directories

include_directories添加头文件的搜索路径,在现代CMake可以用 target_include_directories替代

语法:

// https://cmake.org/cmake/help/latest/command/include_directories.html#command:include_directories
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
link_directories

link_directories添加库文件的搜索路径 ,在现代CMake可以用 target_link_directories替代

语法:

// https://cmake.org/cmake/help/latest/command/link_directories.html#link-directories
link_directories([AFTER|BEFORE] directory1 [directory2 ...])
add_library

add_library 将所有的源文件创建动态库或静态库。

语法:

// https://cmake.org/cmake/help/latest/command/add_library.html#command:add_library
add_library( [] [EXCLUDE_FROM_ALL] ...)
  • name: 库的名称
  • type:库的类型,STATIC是静态库,SHARED是动态库
  • sources:所有的源文件目录
target_link_libraries

target_link_libraries将所有的库链接到目标库上。

语法:

// https://cmake.org/cmake/help/latest/command/target_link_libraries.html#target-link-libraries
target_link_libraries( ... ... ...)

CMake语法中其中带<>是必填的,带[]是可选参数,... 是变长参数

最后小结CMake配置文件的基本步骤:

  • 收集所有的c和c++源文件,保存变量中
  • 收集所有头文件的目录
  • 设置库文件的目录,包括内置的和项目本身的cpp目录
  • 设置当前项目的库名称并创建,动态库或静态库
  • 将所有的源文件和库文件链接到当前项目库中

将CMake配置文件依赖到Gradle中

在build.gradle文件中,进行将将CMake配置文件依赖关联到Gradle中,如下:

android {

	defaultConfig {
		
		// 注释2 
		ndk {
            //noinspection ChromeOsAbiSupport
            abiFilters "arm64-v8a" // 生成 arm64-v8a / armeabi-v7a so文件
        }
		
		// 注释3
        externalNativeBuild {
            cmake {
                cppFlags ""
                // arguments 参数会传递到CMakeLists文件中
                // -DCMAKE_BUILD_TYPE 配置生成so包的环境,不配默认是Debug
                arguments "-DANDROID_STL=c++_shared"
            }
        }

	}
	
	// 注释1 	
	externalNativeBuild {
        cmake {
			// CMakeLists.txt路径
            path "src/main/cpp/CMakeLists.txt"
        }
    }


	buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
			// 注释4
            // 配置Release生成so包
            // https://developer.android.google.cn/ndk/guides/symbol-visibility?hl=zh-cn
            externalNativeBuild {
                cmake {
                    arguments "-DCMAKE_BUILD_TYPE=Release"
                            "-DANDROID_OPTIMIZATION_LEVEL=z" // 使用-Oz优化
                    cFlags "-Os", "-fvisibility=hidden"
                    cppFlags "-Os", "-fvisibility=hidden"
                }
            }
        }
    }

}
  • 注释1:指定CMake配置文件CMakeLists.txt的来源路径
  • 注释2:设置在交叉编程生成so文件支持的CPU架构
  • 注释3:设置CMake项目构建的参数,其参数会被传递给CMakeLists.txt中,比如arguments,如果没有在变体或构建release等其他环境配置externalNativeBuild,注释3就是统一默认的配置;
  • 注释4:设置release环境的构建参数,比如arguments "-DCMAKE_BUILD_TYPE=Release" 的DCMAKE_BUILD_TYPE值可以CMakeLists.txt文件中拿到,然后做一些特殊处理,比如构建优化等等。

详细可以参考Android NDK官方文档

交叉编译生成动态库

到这里基本完成了所有的配置项,通过Make 项目就可以生成动态库,或者执行Gradle任务命令生成。

在Android Studio中通过CMake实现交叉编译生成动态so文件_第2张图片

动态so库文件会存放在build/intermediates/cxx目录下。

在Android Studio中通过CMake实现交叉编译生成动态so文件_第3张图片
另外aar包也会生成存放在build/outputs/aar目录下,aar包内置了动态so文件,在项目中可以直接使用aar包,如下:

在Android Studio中通过CMake实现交叉编译生成动态so文件_第4张图片

剩下的就是集成到项目中使用了 ,这里就不展开细讲了。

参考

  • CMake | Android NDK | Android Developers
  • https://blog.csdn.net/LLJJYY001/article/details/77866049
  • https://blog.csdn.net/weixin_41733225/article/details/131521373

你可能感兴趣的:(Android,NDK,android,studio,android,Android,NDK,CMake,动态so文件,交叉编译)