Fork me on GitHub

CMake 中的静态连接和动态链接

作者: 朕小猫与GPT4

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
cmake_minimum_required(VERSION 3.4.1)
project(DualVectorFoil)

# 先设置 Assimp 配置项,禁用测试、样例和文档生成
set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ASSIMP_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
set(ASSIMP_BUILD_DOCS OFF CACHE BOOL "" FORCE)

# 添加 Assimp 目录和包含目录
add_subdirectory(${CMAKE_SOURCE_DIR}/Assimp)
include_directories(${CMAKE_SOURCE_DIR}/Assimp/include)

# 定义库文件 DualVectorFoilLib
add_library(
DualVectorFoilLib # 库名称
SHARED # 库类型
# 你的源文件列表,示例:src/main/cpp/你的文件.cpp
)

# 链接必要的库
target_link_libraries(
DualVectorFoilLib
assimp # Assimp 库
android # Android 系统库
EGL # OpenGL 库
GLESv2 # OpenGL ES 2.0
m # 数学库
z # 压缩库
atomic # 原子操作库
)

Q: 这样写完之后,apk 安装包中有两个so文件 libDualVectorFoilLib 和 libassimp,都占用 14m,但其实,libDualVectorFoilLib 中包含的就是libassimp,不应该再占用这么大体积

A: 为了减少 APK 中的体积,你可以将 libassimp 静态链接到 DualVectorFoilLib 中,这样只会生成一个包含 assimplibDualVectorFoilLib.so 文件,而不会包含独立的 libassimp.so 文件。这种方式可以避免重复打包和减小 APK 的体积。具体修改如下:

修改 CMakeLists.txt 配置,将 assimp 静态链接

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
cmake_minimum_required(VERSION 3.4.1)
project(DualVectorFoil)

# 先设置 Assimp 配置项,禁用测试、样例和文档生成
set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ASSIMP_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
set(ASSIMP_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF) # 禁用 Assimp 的动态库生成,改为静态库

# 添加 Assimp 目录和包含目录
add_subdirectory(${CMAKE_SOURCE_DIR}/Assimp)
include_directories(${CMAKE_SOURCE_DIR}/Assimp/include)

# 定义库文件 DualVectorFoilLib
add_library(
DualVectorFoilLib # 库名称
SHARED # 库类型
# 你的源文件列表,示例:src/main/cpp/你的文件.cpp
)

# 链接必要的库
target_link_libraries(
DualVectorFoilLib
assimp # 静态链接 Assimp 库
android # Android 系统库
EGL # OpenGL 库
GLESv2 # OpenGL ES 2.0
m # 数学库
z # 压缩库
atomic # 原子操作库
)

解释

  1. **禁用 BUILD_SHARED_LIBS**:将 BUILD_SHARED_LIBS 设置为 OFF,使 Assimp 以静态库(.a 文件)形式生成,这样 Assimp 将不会生成独立的 .so 文件,而是将代码直接打包到 libDualVectorFoilLib.so 中。

  2. **静态链接 assimp**:在 target_link_libraries 中链接 assimp,这会将 Assimp 的代码直接合并进 libDualVectorFoilLib.so,避免生成和打包独立的 libassimp.so

  3. 结果:这样可以减小 APK 体积,因为不再包含重复的库文件。

之后重新构建项目,确保 APK 中只包含 libDualVectorFoilLib.so 文件,不再包含 libassimp.so 文件。

Q: 为什么改成静态连接之后小了好多

A: 改成静态链接之后 APK 大小减少,主要原因在于静态链接和动态链接的工作原理不同,以及 Android 应用打包时如何处理库文件:

  1. 避免重复加载的冗余文件:动态链接时,libassimp.so 作为独立的动态库被单独打包,并在运行时加载。而静态链接则将 libassimp 的代码直接整合到 libDualVectorFoilLib.so 中,避免了多余的 .so 文件。因此,只生成一个完整的 .so 文件,而不是两个重复的文件,节省了空间。

  2. 优化与裁剪:静态链接时,链接器会自动去除 Assimp 库中未使用的代码,只保留 DualVectorFoilLib 需要的部分。这意味着生成的 libDualVectorFoilLib.so 体积更小,因为一些多余的功能不会被包含进去。而动态链接时,通常整个库都被打包,即使项目不使用 Assimp 的所有功能,APK 也会包含整个库的内容。

  3. 减少符号表与动态加载信息:动态库需要额外的符号表和动态加载信息,以便在运行时进行地址解析和符号加载。静态链接的 .so 文件则不需要这些额外的信息,因此最终生成的文件会更小。

  4. 加载性能提升:除了体积减小,静态链接也会带来性能上的提升,因为它省去了动态加载库时的解析步骤,在某些场景下可以略微缩短应用启动时间。

通过静态链接,代码体积优化和去重工作会更加有效,因此可以显著减小 APK 大小。

Q: 如果我只想引入 assimp 不想引入 libDualVectorFoilLib.so 怎么处理

A: 如果只想引入 assimp 库,不需要 libDualVectorFoilLib.so,可以直接在 CMake 中只构建 assimp,而不创建其他自定义的 .so 库文件。这样可以实现只生成 libassimp.so,并确保它在你的 APK 中被正确引用。以下是可以实现的步骤:

  1. 直接构建 Assimp 并禁用其他库的生成
    只需要 assimp,无需自定义的库文件,可以移除自定义库的定义,只保留对 assimp 的配置与编译。

  2. 修改 CMakeLists.txt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    cmake_minimum_required(VERSION 3.4.1)
    project(DualVectorFoil)

    # 配置 Assimp:禁用测试、样例和文档生成
    set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
    set(ASSIMP_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
    set(ASSIMP_BUILD_DOCS OFF CACHE BOOL "" FORCE)

    # 添加 Assimp 目录并包含头文件
    add_subdirectory(${CMAKE_SOURCE_DIR}/Assimp)
    include_directories(${CMAKE_SOURCE_DIR}/Assimp/include)

    # Assimp 将自动生成 libassimp.so,无需自定义其他库
  3. **在 build.gradle 中指定加载 libassimp.so**:
    在你的 Android 项目中确保在 Java 或 Kotlin 代码中正确加载 libassimp.so,例如:

    1
    System.loadLibrary("assimp")
  4. **检查并确保只有 libassimp.so**:
    构建 APK 后,检查生成的 APK 文件,确认只有 libassimp.so 被打包,而没有 libDualVectorFoilLib.so

这样配置后,APK 中只会包含 libassimp.so,并且该库可以直接使用,不再生成不必要的库文件。

Q: So 文件的 Raw File Size 和 Download Size 分别是什么?

A: 在 APK 文件的打包和发布过程中,文件会经历压缩处理,这使得 APK 下载包的体积通常小于安装后在设备上实际占用的空间。在 Android Studio 中,APK Analyzer 工具展示了两个关键指标来帮助我们理解这些差异:

  1. Raw File Size(原始文件大小):这是文件在 APK 解压后,在设备上占用的实际存储空间大小。在您提供的截图中,libassimpd.so 的原始文件大小为 14.4 MB,表示安装到设备后它会占用 14.4 MB 的空间。

  2. Download Size(下载大小):这是 APK 包含的文件在压缩后的大小。由于 APK 打包时会对文件进行压缩,libassimpd.so 的压缩后下载大小仅为 3.4 MB。这个值表示用户在下载 APK 包时实际需要传输的数据量。

总结来说:

  • Raw File Size 是文件在设备上解压后的实际存储空间需求。
  • Download Size 是文件在 APK 压缩包中的大小,直接影响 APK 的下载体积。

因此,虽然下载 APK 时 libassimpd.so 只占 3.4 MB,但安装后在设备上会占用 14.4 MB 的存储空间。

Q: So 文件是怎么实现从14M压缩到3.4M的

A: so 文件(共享库文件)的大小可以大幅度缩小,主要是因为 Android APK 打包过程中应用了压缩算法。以下是一些关键原因和过程:

1. ZIP 压缩

APK 文件本质上是一个 ZIP 文件,打包时会对资源文件和库文件应用 ZIP 压缩。共享库文件(so 文件)通常包含大量重复数据空白区域,这些内容在 ZIP 压缩时能够被有效地缩减。因此,像 libassimpd.so 这种大型的二进制文件,通过 ZIP 压缩可以大幅度减小体积。

2. 二进制格式的高压缩率

so 文件是二进制格式,包含大量的结构化数据代码段。这些二进制数据在结构上非常紧密,包含许多重复的模式和数据块,非常适合被压缩算法处理。常用的压缩算法(例如 DEFLATE)能够识别这些模式,减少冗余,从而将文件压缩到较小的尺寸。

3. 去除无用数据

一些调试信息和符号信息在生成 so 文件时可能会被包含在内,但是在发布 APK 时通常会将这些调试符号信息剥离。这会进一步减少 so 文件的体积,尤其在压缩之后,对这些无用数据的移除效果显著。

4. 压缩前未使用的代码和数据区域

so 文件可能包含一些预留的数据段对齐填充,这些部分在压缩后也会被进一步缩小。比如对齐填充区域在压缩时几乎不会占用空间,从而进一步减小了整体体积。

示例总结

在您的示例中,libassimpd.so 原始文件大小为 14.4 MB,而在 APK 中压缩后的下载大小是 3.4 MB。这种大幅缩小主要得益于 ZIP 压缩的高效性,以及 so 文件的二进制结构带来的高压缩比。

总结

APK 中的 so 文件能够实现从 14 MB 压缩到 3.4 MB,主要是由于以下原因:

  • ZIP 压缩算法的高效性
  • 二进制文件的结构适合压缩
  • 发布时剥离调试符号信息
  • 未使用的数据和对齐填充在压缩后基本不占空间

这些因素共同作用,使得 so 文件在安装包中的大小远小于其解压后的原始大小。

,