作者: 朕小猫与GPT4
1 | task downloadApk { |
作者: 朕小猫与GPT4
1 | task downloadApk { |
作者: 朕小猫与GPT4
1 | cmake_minimum_required(VERSION 3.4.1) |
Q: 这样写完之后,apk 安装包中有两个so文件 libDualVectorFoilLib 和 libassimp,都占用 14m,但其实,libDualVectorFoilLib 中包含的就是libassimp,不应该再占用这么大体积
A: 为了减少 APK 中的体积,你可以将 libassimp
静态链接到 DualVectorFoilLib
中,这样只会生成一个包含 assimp
的 libDualVectorFoilLib.so
文件,而不会包含独立的 libassimp.so
文件。这种方式可以避免重复打包和减小 APK 的体积。具体修改如下:
assimp
静态链接1 | cmake_minimum_required(VERSION 3.4.1) |
**禁用 BUILD_SHARED_LIBS
**:将 BUILD_SHARED_LIBS
设置为 OFF
,使 Assimp 以静态库(.a
文件)形式生成,这样 Assimp 将不会生成独立的 .so
文件,而是将代码直接打包到 libDualVectorFoilLib.so
中。
**静态链接 assimp
**:在 target_link_libraries
中链接 assimp
,这会将 Assimp 的代码直接合并进 libDualVectorFoilLib.so
,避免生成和打包独立的 libassimp.so
。
结果:这样可以减小 APK 体积,因为不再包含重复的库文件。
之后重新构建项目,确保 APK 中只包含 libDualVectorFoilLib.so
文件,不再包含 libassimp.so
文件。
Q: 为什么改成静态连接之后小了好多
A: 改成静态链接之后 APK 大小减少,主要原因在于静态链接和动态链接的工作原理不同,以及 Android 应用打包时如何处理库文件:
避免重复加载的冗余文件:动态链接时,libassimp.so
作为独立的动态库被单独打包,并在运行时加载。而静态链接则将 libassimp
的代码直接整合到 libDualVectorFoilLib.so
中,避免了多余的 .so
文件。因此,只生成一个完整的 .so
文件,而不是两个重复的文件,节省了空间。
优化与裁剪:静态链接时,链接器会自动去除 Assimp 库中未使用的代码,只保留 DualVectorFoilLib
需要的部分。这意味着生成的 libDualVectorFoilLib.so
体积更小,因为一些多余的功能不会被包含进去。而动态链接时,通常整个库都被打包,即使项目不使用 Assimp 的所有功能,APK 也会包含整个库的内容。
减少符号表与动态加载信息:动态库需要额外的符号表和动态加载信息,以便在运行时进行地址解析和符号加载。静态链接的 .so
文件则不需要这些额外的信息,因此最终生成的文件会更小。
加载性能提升:除了体积减小,静态链接也会带来性能上的提升,因为它省去了动态加载库时的解析步骤,在某些场景下可以略微缩短应用启动时间。
通过静态链接,代码体积优化和去重工作会更加有效,因此可以显著减小 APK 大小。
Q: 如果我只想引入 assimp 不想引入 libDualVectorFoilLib.so 怎么处理
A: 如果只想引入 assimp
库,不需要 libDualVectorFoilLib.so
,可以直接在 CMake 中只构建 assimp
,而不创建其他自定义的 .so
库文件。这样可以实现只生成 libassimp.so
,并确保它在你的 APK 中被正确引用。以下是可以实现的步骤:
直接构建 Assimp 并禁用其他库的生成:
只需要 assimp
,无需自定义的库文件,可以移除自定义库的定义,只保留对 assimp
的配置与编译。
修改 CMakeLists.txt:
1 | cmake_minimum_required(VERSION 3.4.1) |
**在 build.gradle
中指定加载 libassimp.so
**:
在你的 Android 项目中确保在 Java 或 Kotlin 代码中正确加载 libassimp.so
,例如:
1 | System.loadLibrary("assimp") |
**检查并确保只有 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 工具展示了两个关键指标来帮助我们理解这些差异:
Raw File Size(原始文件大小):这是文件在 APK 解压后,在设备上占用的实际存储空间大小。在您提供的截图中,libassimpd.so
的原始文件大小为 14.4 MB,表示安装到设备后它会占用 14.4 MB 的空间。
Download Size(下载大小):这是 APK 包含的文件在压缩后的大小。由于 APK 打包时会对文件进行压缩,libassimpd.so
的压缩后下载大小仅为 3.4 MB。这个值表示用户在下载 APK 包时实际需要传输的数据量。
总结来说:
因此,虽然下载 APK 时 libassimpd.so
只占 3.4 MB,但安装后在设备上会占用 14.4 MB 的存储空间。
Q: So 文件是怎么实现从14M压缩到3.4M的
A: so
文件(共享库文件)的大小可以大幅度缩小,主要是因为 Android APK 打包过程中应用了压缩算法。以下是一些关键原因和过程:
APK 文件本质上是一个 ZIP 文件,打包时会对资源文件和库文件应用 ZIP 压缩。共享库文件(so
文件)通常包含大量重复数据和空白区域,这些内容在 ZIP 压缩时能够被有效地缩减。因此,像 libassimpd.so
这种大型的二进制文件,通过 ZIP 压缩可以大幅度减小体积。
so
文件是二进制格式,包含大量的结构化数据和代码段。这些二进制数据在结构上非常紧密,包含许多重复的模式和数据块,非常适合被压缩算法处理。常用的压缩算法(例如 DEFLATE)能够识别这些模式,减少冗余,从而将文件压缩到较小的尺寸。
一些调试信息和符号信息在生成 so
文件时可能会被包含在内,但是在发布 APK 时通常会将这些调试符号信息剥离。这会进一步减少 so
文件的体积,尤其在压缩之后,对这些无用数据的移除效果显著。
so
文件可能包含一些预留的数据段或对齐填充,这些部分在压缩后也会被进一步缩小。比如对齐填充区域在压缩时几乎不会占用空间,从而进一步减小了整体体积。
在您的示例中,libassimpd.so
原始文件大小为 14.4 MB,而在 APK 中压缩后的下载大小是 3.4 MB。这种大幅缩小主要得益于 ZIP 压缩的高效性,以及 so
文件的二进制结构带来的高压缩比。
APK 中的 so
文件能够实现从 14 MB 压缩到 3.4 MB,主要是由于以下原因:
这些因素共同作用,使得 so
文件在安装包中的大小远小于其解压后的原始大小。
layout_constrainedWidth
是ConstraintLayout
中的一个属性,用于在处理宽度约束时提供更多的灵活性和控制。当你使用ConstraintLayout
构建复杂的布局时,可能会遇到某些视图在其内容过长时需要被约束宽度的情况,以避免它们超出屏幕边界或影响其他视图的布局。layout_constrainedWidth
属性就是为了解决这类问题而设计的。
1 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
1 | anyView.setOnTouchListener(object : View.OnTouchListener { |
作者:朕小猫与GPT4
在 C++ 程序中,几乎所有元素(包括变量、对象、函数等)都可以通过它们的内存地址来引用或操作。这一点归根结底是因为 C++ 是一种底层的编程语言,它允许直接访问内存和底层硬件资源。这种能力带来了极高的灵活性和控制能力,但同时也要求程序员对程序的内存使用有深入的理解。下面是如何通过内存地址来引用或操作元素的几个方面:
在 C++ 中,每个变量或对象都占据内存中的一个或多个连续位置。你可以使用 &
操作符获得一个变量的内存地址,这个地址可以赋给一个指针变量。通过指针,你可以读取或修改存储在该地址的数据。这对于数组和自定义数据结构尤其有用,因为可以通过指针算术进行高效的遍历和访问。
C++ 允许获取函数的地址,并将其赋给函数指针。这使得程序可以在运行时决定调用哪个函数,提高了代码的灵活性和可重用性。函数指针广泛应用于回调机制、事件驱动编程和接口设计中。
C++ 的引用机制实际上是一种特殊的语法糖,它在内部使用地址来实现。引用允许你创建一个变量的别名,通过别名可以直接访问和修改原始变量,而无需显式解引用。这样不仅代码更加简洁,而且避免了指针可能带来的错误。
C++ 通过 new
和 delete
操作符提供了动态内存分配的能力。使用 new
分配的内存位于堆区,它会返回指向分配内存的指针。通过这个指针,程序可以在运行时动态地分配、访问和释放内存。
通过上述机制,C++ 程序可以精确地控制内存的使用方式,从而优化程序性能和资源管理。然而,这也意呀着程序员必须对指针、内存地址以及内存管理有充分的理解,才能有效地利用这些特性编写高效、安全的代码。
编写教程时,目标是清晰、简洁且具有实用性。以下是一个关于“如何在 C++ 中使用指针和地址”教程的示例内容。这段内容旨在介绍初学者到 C++ 的指针和地址概念,帮助他们理解和运用“万物皆地址”的原则。
在 C++ 中,理解指针和地址是掌握语言的基石。本教程将引导你通过简单的步骤来理解和使用指针和地址。
在 C++ 中,每个变量都存储在内存的一个特定位置,这个位置可以通过地址来访问。你可以使用 &
操作符来获取变量的地址。
1 |
|
这段代码会输出变量 var
的内存地址。
指针是存储另一个变量地址的变量。在 C++ 中,你可以使用 *
来声明指针。
1 |
|
在这个例子中,ptr
是一个指向 var
地址的指针。通过 *ptr
,我们可以访问这个地址上的值。
指针在 C++ 中的应用非常广泛,包括:
new
和 delete
操作符分配和释放内存。1 |
|
这个练习演示了如何使用指针来交换两个变量的值。
在 C++ 中,通过地址修改值是指针操作的一个基本用途。指针是一种特殊的变量,其值为另一个变量的内存地址。
通过指针,你可以直接访问和修改它指向的内存位置上存储的数据。
这一过程涉及几个关键步骤:获取变量的地址、使用指针指向该地址、通过指针修改该地址上的值。
下面通过一个简单的示例来说明这一过程:
1 |
|
在上述代码中:
int
的变量 value
并初始化为 10。int*
的指针 ptr
并将其初始化为 value
的地址(&value
)。cout
输出语句,我们可以看到 value
的原始值。ptr
来修改 value
的值。这里的 *ptr = 20;
表示将 ptr
指向的内存位置(即 value
的位置)上的数据修改为 20。*ptr
是解引用操作符的应用,它获取指针指向的内存地址上存储的值。cout
输出 value
的值,可以看到它已经被修改为 20。在 C++ 中,理解函数传参的内存地址涉及到两个主要概念:按值传递(Pass by Value)和按引用传递(Pass by Reference)。理解这些概念有助于深入理解 C++ 如何在函数调用中处理参数的内存地址。
当函数参数是按值传递时,函数接收的是实参的一个副本。这意味着函数内部对参数所做的任何修改都不会影响到原始数据。在内存层面,这个过程涉及到将原始数据的值复制到新的内存地址中。这个新的地址是函数参数在函数调用栈上的局部地址。
按引用传递意味着函数接收的是实参的引用(或者说是内存地址)。这样,函数内部对参数的任何修改都会直接影响到原始数据。在内存层面,这避免了数据的复制,函数参数直接使用了实参的地址。
假设我们有一个简单的函数,目的是修改一个整数的值。
1 | // 按值传递 |
在这个例子中,addTenByValue
函数无法修改外部变量的值,因为它仅操作了参数的副本。而 addTenByReference
函数则直接操作了实参的内存地址,因此它能够修改外部变量的值。
类名:Renderer
属性
true
。Shader
对象的智能指针。Shader
对象用于编译、链接和使用顶点和片段着色器。Model
对象包含顶点数据、索引数据和纹理数据。~Renderer()
: 清理Renderer
对象,包括释放EGL资源(如显示设备、渲染表面和上下文)。width_
和height_
属性,并标记需要为着色器生成新的投影矩阵。models_
容器中。关联和依赖关系:
Shader
类:由于shader_
属性和在initRenderer()
方法中对Shader
的调用。Vertex
和Index
类型:在createModels()
方法中使用这些类型来创建模型。TextureAsset
类:在createModels()
方法中加载纹理资源。std::vector
、std::unique_ptr
。这个类图还可以展示Renderer
类如何与Android的本地应用粘合层(native_app_glue)和OpenGL ES 3.0交互,特别是如何处理EGL上下文、表面创建和渲染循环。
下面是一个简化的类图表示,重点在于Renderer
类及其直接关系:
1 | [ ] --|> [EGLDisplay] |
说明:
--|>
表示拥有或创建关系。--->
表示依赖关系。--*
表示包含或集合关系。1 |
|
作者:朕小猫与GPT4
在 C++ 中,函数指针是指向函数的指针,允许通过指针调用函数。这种特性让程序在运行时能够选择要调用的函数,增加了代码的灵活性和动态性。理解函数指针对于高级编程技巧,如回调函数、命令模式等是非常重要的。
函数指针的定义需要指定函数的返回类型、指针名称以及函数参数的类型。定义函数指针的基本语法如下:
1 | 返回类型 (*指针变量名)(参数类型列表); |
例如,如果你有一个返回类型为 int
并接受两个 int
类型参数的函数,你可以这样定义一个指向该函数的指针:
1 | int (*funcPtr)(int, int); |
一旦定义了函数指针,就可以将其指向具有相应签名的任何函数,然后通过该指针调用函数。这里有一个使用函数指针的简单示例:
1 |
|
函数指针最常见的用途包括:
随着 C++11 的引入,现代 C++ 更倾向于使用 std::function 和 lambda 表达式来替代裸函数指针,因为它们提供了更高的灵活性和更简洁的语法。例如,std::function
可以存储并调用任何可调用的实体,包括普通函数、lambda 表达式、以及其他具有 operator()
的对象。
函数指针在 C++ 中的应用非常广泛,提供了编程的灵活性和动态性。以下是几种典型场景,展示了函数指针的使用:
回调函数是由用户编写的,但由系统或库在适当的时候调用的函数。函数指针允许用户提供具体的回调函数实现,使得库或框架可以在运行时调用用户定义的代码。
示例: 设计一个简单的事件处理器,当发生某个事件时,调用用户提供的回调函数。
1 |
|
函数指针数组可以存储指向不同函数的指针,使得程序可以基于运行时决策调用不同的函数。
示例: 创建一个简单的菜单系统,用户输入不同的选项执行不同的操作。
1 |
|
通过函数指针,可以模拟面向对象编程中的接口或策略模式,允许在运行时选择不同的算法或行为。
示例: 设计一个简单的排序策略,用户可以选择不同的排序算法。
1 |
|
这些示例展示了函数指针在回调、决策和策略选择等不同编程场景中的应用,证明了其为 C++ 编程带来的灵活性和动态性。
std::function
和 lambda 表达式在 C++11 及之后的版本中提供了一种更加灵活和方便的方式来处理可调用对象。它们使得代码更加简洁且易于理解,特别是在需要函数作为参数或回调时。
Lambda 表达式提供了一种匿名函数的实现,允许你在需要函数对象的地方快速定义一个函数。Lambda 表达式的基本语法如下:
1 | [捕获列表](参数列表) -> 返回类型 { |
std::function
和 Lambda 表达式std::function
是一个模板类,它可以用来存储任何可以调用的目标,如普通函数、Lambda 表达式、成员函数和函数对象。这提供了比裸函数指针更大的灵活性和功能。
下面是一个示例,展示了如何使用 std::function
和 Lambda 表达式来替代裸函数指针:
1 |
|
在这个示例中,我们首先定义了一个普通的函数 add
。然后,我们创建了两个 std::function
对象,一个用来存储普通函数 add
,另一个用来存储一个实现相同功能的 Lambda 表达式。通过这种方式,我们可以看到 std::function
和 Lambda 表达式如何提供了一种更加灵活的方式来处理可调用对象,而不必直接使用函数指针。
std::function
可以存储任何类型的可调用对象,提供了极大的灵活性。std::function
和 Lambda 表达式可以使代码更加清晰和易于维护,特别是在复杂的项目中。通过这种方式,C++11 提供的这些特性让处理函数和回调等场景变得更加优雅和高效。
在 C++ 中,术语“指针函数”通常被误解或使用不当。实际上,人们通常是指返回指针的函数,或者简称为“函数返回指针”。这种类型的函数在使用时非常有用,尤其是当你需要从函数中返回动态分配的内存或数组、多级指针、或者指向不同数据结构的指针时。
要定义一个返回指针的函数,你需要在函数声明的返回类型前加上*
来指明返回的是指针。这里有一个简单的例子,说明如何定义和使用返回指针的函数:
1 |
|
在这个例子中,getIntPtr
函数分配了一个int
类型的动态内存,并返回一个指向这块内存的指针。在main
函数中,我们调用了getIntPtr
,并使用返回的指针来访问和操作内存。最后,不要忘记使用delete
释放动态分配的内存,以避免内存泄露。
std::unique_ptr
或std::shared_ptr
)来管理动态分配的内存,这样可以自动管理内存的生命周期,减少内存泄露的风险。返回指针的函数提供了一种灵活的方式来创建和管理动态资源,但同时也需要谨慎处理指针和内存管理相关的问题。
在 C++ 中,智能指针是一种模板类,用于自动管理动态分配的内存,确保资源在不再需要时被正确释放,从而避免内存泄露。C++11 引入了两种主要的智能指针类型:std::unique_ptr
和 std::shared_ptr
,它们分别用于独占所有权和共享所有权的场景。
std::unique_ptr
std::unique_ptr
是一种独占所有权的智能指针,保证同一时间只有一个 std::unique_ptr
指向特定的资源。当 std::unique_ptr
被销毁或被重新分配时,它指向的对象也会被自动删除。
1 |
|
std::shared_ptr
std::shared_ptr
是一种共享所有权的智能指针,允许多个 std::shared_ptr
实例指向同一个对象。内部使用引用计数来跟踪有多少个 std::shared_ptr
指向同一个资源,当最后一个这样的指针被销毁时,所指向的对象也会被删除。
1 |
|
std::shared_ptr
时。循环引用会阻止引用计数达到零,导致内存泄露。解决循环引用问题通常使用 std::weak_ptr
。std::unique_ptr
通过移动语义实现所有权的转移,不能被复制。std::shared_ptr
适用于资源需要被多个所有者共享的情况,但增加了额外的开销(引用计数管理)。智能指针是现代 C++ 管理动态资源的首选方式,相比裸指针,它们提供了更安全、更简洁的资源管理机制。