R文件详细介绍、瘦身

R 文件可能是很多 Android 开发者既熟悉又陌生的存在。它无处不在,所有使用到资源的地方都离不开它。它又有些陌生,google 已经把它封装的很完美了,以至于很多开发者并不知道它是怎么工作的。那么我们今天就来揭开它神秘的面纱。

R.id

这是一个资源的 id,用 32 位的 int 表示。格式为 PPTTNNNN。
前 8 位 PP(Package) 表示资源所属包类型,0x7f 表示应用 Apk 包资源,0x01 表示系统包资源。
中间 8 位 TT(Type) 代表资源 id 的类型
0x02:drawable
0x03:layout
0x04:values
0x05:xml
0x06:raw
0x07:color
0x08:menu
最后 16 位表示资源在此类型里面的编号。

CleanShot 2020-05-06 at 10.31.50@2x

有了 id 之后,就可以去 resource.arsc 里面去查找到真正的资源,将 id 作为 key,就可以查到此 id 对应的资源。

R 文件的存在形式

在平常的开发流程中,我们大概可以将「project」分为三种。

  • AAR
  • Module (com.android.library)
  • Application (com.android.application)

其中 module 和 aar 对于 App 来说是一样的。

为了方便演示,我们构造了以下的工程

- app- lib1- lib2- androidx.recyclerview:recyclerview:1.1.0

app 是 application 工程,依赖 lib1,lib1 依赖 lib2,lib2 依赖了 recyclerview。其中我们在 app、lib1、lib2 分别放置了 string 资源

- app <string name="string_from_app">string_from_app</string>
- lib1 <string name="string_from_lib1">string from lib1</string>
- lib2 <string name="string_from_lib2">string from lib2</string>

APK

首先我们来看一下最终生成的 apk 里面的 R

CleanShot 2020-05-06 at 11.00.03@2x

我们发现会生成所有「library」和 「appliaction」 的 R 文件,并且放在不同的包名下。我们再来看一下每个包的 AndroidManifest.xml

CleanShot 2020-05-06 at 11.09.08@2x

CleanShot 2020-05-06 at 11.08.41@2x

CleanShot 2020-05-06 at 11.09.54@2x

CleanShot 2020-05-06 at 11.12.40@2x

有没有发现什么呢?
每个模块最后都是按照 AndroidManifest.xml 里面定义的 package 来决定要生成的 R 文件的包名。

AAR

我们拆开 recyclerview-1.0.0.aar

CleanShot 2020-05-06 at 11.27.40@2x

找了一圈,除了一个 R.txt,并没有在其它地方找到 R 相关的踪迹,classes.jar 里面也没有生成的 R.class

有同学就可能有疑问了,既然 classes.jar 里面没有 R.class,那么在开发的时候,我们是怎么引用到 aar 里面的资源的呢?

首先我们明确一点,所有的 R 都是在生成 apk 的时候由 aapt 完成。为什么要这样做呢?试想如果 R 文件在 aar 打包阶段就已经生成了的话,那么很大概率会导致 id 之间的冲突。比如 recyclerview.aar 用了 0x7f020001,appcompat.aar 也有可能用了 0x7f020001 这个 id,在合并的时候,resource.arsc 只能将这个 id 映射到一个资源,这样就会出现错乱。
所以 AGP 做了一件事,所有 R 文件的生成都在 apk 生成的时候交与 aapt 完成。在开发期间对于 R 文件的引用都给一个临时的 classpath: R.java,这里面包含了编译时期所需要的 R 文件,这样编译就不会出错。并且在运行时会扔掉这些临时的 R.java,真正的 R.java 是 aapt 去生成的。

所以我们总结一下:

  • module/aar 里面临时生成的 R.java 只是为了「make compiler happy」,在编译流程中扮演着「compileOnly」的角色。
  • 在生成 apk 的时候,aapt 会根据 app 里面的资源,生成真正的 R.java 到 apk 中,运行的时候代码就会获取到 aapt 生成的 id。

这里有一个问题,我们仔细观察一下 app 和 module 里面对 R.id 引用出的地方。

CleanShot 2020-05-06 at 17.19.16@2x

App 的代码

App 生成的字节码

CleanShot 2020-05-06 at 17.21.26@2x

Module 的代码

CleanShot 2020-05-06 at 17.22.45@2x

Module 生成的字节码。

我们发现,在 App 里面的代码发生了「内联」,但是在 module 里面的代码并没有被内联,而是通过运行时查找变量的方式去获取。
结合上面的「R生成过程」,来想一下为什么 module 里面的 id 不被内联而 app 里面的 id 会被内联呢?
答案已经很清楚了。module/aar 在编译的时候,AGP 会为它们提供一个临时的 R.java 来帮助他们编译通过,我们知道,如果一个常量被标记成 static final,那么 java 编译器会在编译的时候将他们内联到代码里,来减少一次变量的内存寻址。AGP 为 module/aar 提供的 R.java 里面的 R.id 不是 final 的,因为如果设计成了 final,R.id 就会被内联到代码中去,那在运行的时候,module 里面的代码就不会去寻找 aapt 生成的 R.id,而是使用在编译时期 AGP 为它提供的假的 R.id,这个 id 肯定是不能用的,不然就会导致 resource not found。

R 文件的生成

在编译的中间产物中,R 大概有这么几种存在形式

「此 project」代表当前在编译的 module
「本地资源」代表当前在编译的 module 里面声明的资源

  • R.java(java 文件,给此 projet 做 compileOnly 用)
  • R.txt(记录了此 project 的所有资源列表,并生成了 id,最后会根据这个值生成对应的 R.java)
  • R-def.txt(记录了此 project 的本地资源,不包括依赖)
  • package-aware-r.txt(记录了此 project 的所有资源,没有生成 id)

大概的生成逻辑是

未命名文件 -5-

这是一个 module 生成 R.java 的过程。
首先,当前 module 会搜集所有它的依赖,并且拿到它的 R.txt。比如 lib1 依赖 lib2,lib2 依赖 recyclerview-1.0.0.aar,那么 lib1 会拿到这俩个 R.txt。其中

  • lib2 的 R.txt 是经历了上图的过程已经生成好的了
  • AAR 的 R.txt 是 AGP 在 transform 的时候从 aar 里面解压出来的

这样这个 module 就拿到了所有的依赖的资源。然后 AGP 会独处当前 module 的「本地资源」,结合刚刚拿到的所有依赖的 R.txt,生成 package-aware-r.txt.
它的格式是这样的

com.example.lib1
layout activity_in_lib2
string string_from_lib1
string string_from_lib2

第一行表示了它的 package name,是从 AndroidManifest.xml 里面取的,下面的几行表示这个 module 中所有的资源,包括自己的和依赖的别人的。
然后 AGP 就会根据 package-aware-r.txt 生成 R.txt,那 R.txt 里面的内容和 package-aware-r.txt 有什么不同呢?

int layout activity_in_lib2 0x7f0e0001
int string string_from_lib1 0x7f140001
int string string_from_lib2 0x7f140002

我们可以看到 R.txt 已经很接近我们的 R.java 的内容了。在从 package-aware-r.txt 拿到所有的资源后,AGP 为资源分配了「临时的 id」。

CleanShot 2020-05-06 at 18.34.04@2x

具体的分配逻辑如上,可以看到它维护了一个 “map”,每个资源的 id 都是从 0x7fxx0000 开始递增的。当然这里的分配逻辑没什么用,完全可以乱分配,反正最后也用不着。

最后一步就是通过 R.txt 生成 R.java 啦,AGP 会根据 R.txt 里面的资源及其 id 生成最后的 R.java,作为 classpath 供编译时使用。
这样一通操作下来,「此 project」也生成了 R.txt,当它的上层依赖在编译的时候,就可以拿到它的 R.txt 作为依赖,生成自己的 R.txt,重复上面的步骤。

另:
其实在 AGP 最新版本已经没有 R.java 了,取而代之的是 R.jar,R.jar 会把所有生成的 R.java 打成一个 jar 包,作为 classpath 来编译。可是这样做有什么好处呢?

看看 Jake 大神的说法:

CleanShot 2020-05-06 at 18.47.18@2x

如果你的工程是纯 kotlin 工程,那 AGP 就不用启动一个 javac 去编译 R.java,这样会大幅提升编译的速度。(可见 Google 也在很努力的优化 AGP 了,高版本的 AGP 往往能带来很多性能上的优化。

AAPT 生成 R

终于来到了激动人心的时刻了,前面 AGP 生成了这么多 R.java 最后都要被丢掉,统统交给 aapt 去生成我们运行时需要的 R.java 了。
AGP 的高版本已经默认开启 aapt2 了,这里我们就直接看 aapt2 相关的代码。
首先 aapt2 其实是 Android SDK 里面的一个命令,用 c++ 编写。你可以运行一下 aapt2,看它的 readme。你也可以在 aapt2中找到它的说明。

$ANDROID_HOME/build-tools/29.0.2/aapt2

AGP

AGP 是通过 AaptV2CommandBuilder 来生成 aapt 的具体命令。
在 aapt2 中,为了实现增量编译,aapt2 将原来的编译拆成了 compile 和 link。aapt2 先将资源文件编译成 .flat 的中间产物,然后通过 link 将 .flat 中间产物编译成最终的产物。
AGP 对于 Module 模块调用的 link 命令如下:

CleanShot 2020-05-06 at 19.59.54@2x

传入了 android.jar、AndroidManifest.xml、merge & compile 后的资源产物等等。
android.jar 是提供给代码去链接 Android 自身的资源,比如你使用了 @android:color/red 这个资源,就会从 android.jar 中去取。
--non-final-ids 表示不要为 module 生成的 R.java 使用 final 字段,这个我们上面讨论过了。
对应的,application 生成的 aapt link 命令是这样的

CleanShot 2020-05-06 at 20.10.08@2x

为 Application 生成的命令中就没有 --non-final-ids。还传入了一个 --java的参数,表示生成的 R.java 的存放路径,这里就是我们的 R 的最终存放路径了。

aapt 生成 R

调用 aapt2 命令之后就要开始执行 link 了,这里的代码比较多,就不一一啰嗦了。
我们抽一个 id 生成的逻辑来讲。

CleanShot 2020-05-06 at 20.25.20@2x

通过注释我们可以大概了解到:正常的话,id 是从 0 开始生成,每用一个会往后 +1。比如 string 是从 0x7f030000 开始,下一个 string 就是 0x7f030001。

如果你看过 aapt2 的命令,还会发现 aapt2 有个有意思的功能:「固定 id」

CleanShot 2020-05-06 at 20.27.12@2x

— emit-ids 会输出一个 resource name -> id 的映射表。
— stable-ids 可以输入一个 resource name -> 映射表,来决定这次生成的映射关系。

CleanShot 2020-05-06 at 20.29.16@2x

当有 — stable-ids 的输入时,aapt link 会解析这个文件,将映射表提前存入 stable_id_map 中。

CleanShot 2020-05-06 at 20.30.20@2x

在构造 IdAssigner 的时候,将这个 map 传进去,IdAssigner 在遇到在 map 中存在的 resource 时,就会直接分配 map 表里面存的 id。其它的 resource 在分配的时候将会 “Fill the Gap”,找到空缺的 id 分配给它。

— stable-ids 在「热修复」、「插件化」中有很大的用处,我们知道,如果新增了一个资源,按照原来的分配逻辑,是会在原来的 id 里面插入一个新的 id 的。比如原来是

int string string_from_lib1 0x7f140001
int string string_from_lib2 0x7f140002
int string string_from_lib3 0x7f140003

这个时候,如果不固定 id,在 lib1 和 lib2 中间插入一个 lib4,它将会变成如下的样子

int string string_from_lib1 0x7f140001
int string string_from_lib4 0x7f140002
int string string_from_lib2 0x7f140003
int string string_from_lib3 0x7f140004

这就导致原来的 lib2 和 lib3 都发生了变动。
但是如果我们固定了 id,那生成的 id 可能就是以下这样

int string string_from_lib1 0x7f140001
int string string_from_lib4 0x7f140004
int string string_from_lib2 0x7f140002
int string string_from_lib3 0x7f140003

R 文件相关的优化

其实在细品了 R 文件生成的流程之后,我们发现其实在很多方向上 R 文件有优化的空间。

比如,我们可以在编译完成之后将 module 里面对于 R 的引用换成「内联」的,这样就可以少了一次内存寻址,也可以删掉被内联后的 R.class,减少了包体积又做了性能优化。

比如,我们可以在编译的时候通过固定 id 来减少增删改资源带来的大量 id 变动,导致 R.java 被“连根拔起”,带来下游依赖它的 java/kotlin 文件重新编译。

agp 4.1.0升级如下:

App size significantly reduced for apps using code shrinking

Starting with this release, fields from R classes are no longer kept by default, which may result in significant APK size savings for apps that enable code shrinking. This should not result in a behavior change unless you are accessing R classes by reflection, in which case it is necessary to add keep rules for those R classes.

从标题看 apk 包体积有显著减少(这个太有吸引力了),通过下面的描述,大致意思是不再保留 R 的 keep 规则,也就是 app 中不再包括 R 文件?(要不怎么减少包体积的)

在分析这个结果之前先介绍下 apk 中,R 文件冗余的问题;

R 文件冗余问题

android 从 ADT 14 开始为了解决多个 library 中 R 文件中 id 冲突,所以将 Library 中的 R 的改成 static 的非常量属性。

在 apk 打包的过程中,module 中的 R 文件采用对依赖库的R进行累计叠加的方式生成。如果我们的 app 架构如下:

编译打包时每个模块生成的 R 文件如下:

  1. R_lib1 = R_lib1;
  2. R_lib2 = R_lib2;
  3. R_lib3 = R_lib3;
  4. R_biz1 = R_lib1 + R_lib2 + R_lib3 + R_biz1(biz1本身的R)
  5. R_biz2 = R_lib2 + R_lib3 + R_biz2(biz2本身的R)
  6. R_app = R_lib1 + R_lib2 + R_lib3 + R_biz1 + R_biz2 + R_app(app本身R)

在最终打成 apk 时,除了 R_app(因为 app 中的 R 是常量,在 javac 阶段 R 引用就会被替换成常量,所以打 release 混淆时,app 中的 R 文件会被 shrink 掉),其余的 R 文件全部都会打进 apk 包中。这就是 apk 中 R 文件冗余的由来。而且如果项目依赖层次越多,上层的业务组件越多,将会导致 apk 中的 R 文件将急剧的膨胀。

R 文件内联(解决冗余问题)

系统导致的冗余问题,总不会难住聪明的程序员。在业内目前已经有一些R文件内联的解决方案。大致思路如下:

由于 R_app 是包括了所有依赖的的 R,所以可以自定义一个 transform 将所有 library module 中 R 引用都改成对 R_app 中的属性引用,然后删除所有依赖库中的 R 文件。这样在 app 中就只有一个顶层 R 文件。(这种做法不是非常彻底,在 apk 中仍然保留了一个顶层的 R,更彻底的可以将所有代码中对 R 的引用都替换成常量,并在 apk 中删除顶层的 R )

agp 4.1.0 R 文件内联

首先我们分别用 agp 4.1.0 和 agp 3.6.0 构建 apk 进行一个对比,从最终的产物来确认下是否做了 R 文件内联这件事。
测试工程做了一些便于分析的配置,配置如下:

  1. 开启 proguard
buildTypes {release {minifyEnabled true // 打开proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}
}
  1. 关闭混淆,仅保留压缩和优化(避免混淆打开,带来的识别问题)
// proguard-rules.pro中配置
-dontobfuscate

构建 release 包。
先看下 agp 3.6.0 生成的 apk:

从图中可以看到 bizlib module 中会有 R 文件,查看 SecondActivity 的 byte code ,会发现内部有对 R 文件的引用。

接着再来看 agp 4.1.0 生成的 apk:

可以看到,bizlib module 中已经没有 R 文件,并且查看 SecondActivity 的 byte code ,会发现内部的引用已经变成了一个常量。

由此可以确定,agp 4.1.0 是做了对 R 文件的内联,并且做的很彻底,不仅删除了冗余的 R 文件,并且还把所有对 R 文件的引用都改成了常量。

具体分析

现在我们来具体分析下 agp 4.1.0 是如何做到 R 内联的,首先我们大致分析下,要对 R 做内联,基本可以猜想到是在 class 到 dex 这个过程中做的。确定了大致阶段,那接下看能不能从构建产物来缩小相应的范围,最好能精确到具体的 task。(题外话:分析编译相关问题一般四板斧:1. 先从 app 的构建产物里面分析相应的结果;2.涉及到有依赖关系分析的可以将所有 task 的输入输出全部打印出来;3. 1、2满足不了时,会考虑去看相应的源码;4. 最后的大招就是调试编译过程;)

首先我们看下构建产物里面的 dex,如下图:

接下来在 app module 中增加所有 task 输入输出打印的 gradle 脚本来辅助分析,相关脚本如下:

gradle.taskGraph.afterTask { task ->try {println("---- task name:" + task.name)println("-------- inputs:")task.inputs.files.each { it ->println(it.absolutePath)}println("-------- outputs:")task.outputs.files.each { it ->println(it.absolutePath)}} catch (Exception e) {}
}

minifyReleaseWithR8 相应的输入输出如下:

从图中可以看出,输入有整个 app 的 R 文件的集合(R.jar),所以基本明确 R 的内联就是在 minifyReleaseWithR8 task 中处理的。

接下来我们就具体分析下这个 task。
具体的逻辑在 R8Task.kt 里面.

创建 minifyReleaseWithR8 task 代码如下:

class CreationAction(creationConfig: BaseCreationConfig,isTestApplication: Boolean = false) : ProguardConfigurableTask.CreationAction<R8Task, BaseCreationConfig>(creationConfig, isTestApplication) {override val type = R8Task::class.java// 创建 minifyReleaseWithR8 taskoverride val name =  computeTaskName("minify", "WithR8").....
}

task 执行过程如下(由于代码过多,下面仅贴出部分关键节点):

    // 1. 第一步,task 具体执行override fun doTaskAction() {......// 执行 shrink 操作shrink(bootClasspath = bootClasspath.toList(),minSdkVersion = minSdkVersion.get(),......)}// 2. 第二步,调用 shrink 方法,主要做一些输入参数和配置项目的准备companion object {fun shrink(bootClasspath: List<File>,......) {......// 调用 r8Tool.kt 中的顶层方法,runR8runR8(filterMissingFiles(classes, logger),output.toPath(),......)}// 3. 第三步,调用 R8 工具类,执行混淆、优化、脱糖、class to dex 等一系列操作fun runR8(inputClasses: Collection<Path>,......) {......ClassFileProviderFactory(libraries).use { libraryClasses ->ClassFileProviderFactory(classpath).use { classpathClasses ->r8CommandBuilder.addLibraryResourceProvider(libraryClasses.orderedProvider)r8CommandBuilder.addClasspathResourceProvider(classpathClasses.orderedProvider)// 调用 R8 工具类中的run方法R8.run(r8CommandBuilder.build())}}}

至此可以知道实际上 agp 4.1.0 中是通过 R8 来做到 R 文件的内联的。那 R8 是如果做到的呢?这里简要描述下,不再做具体代码的分析:

R8 从能力上是包括了 Proguard 和 D8(java脱糖、dx、multidex),也就是从 class 到 dex 的过程,并在这个过程中做了脱糖、Proguard 及 multidex 等事情。在 R8 对代码做 shrink 和 optimize 时会将代码中对常量的引用替换成常量值。这样代码中将不会有对 R 文件的引用,这样在 shrink 时就会将 R 文件删除。
当然要达到这个效果 agp 在 4.1.0 版本里面对默认的 keep 规则也要做一些调整,4.1.0 里面删除了 默认对 R 的 keep 规则,相应的规则如下:
-keepclassmembers class **.R$* {
public static <fields>;
}

总结

  1. 从 agp 对 R 文件的处理历史来看,android 编译团队一直在对R文件的生成过程不断做优化,并在 agp 4.1.0 版本中彻底解决了 R 文件冗余的问题。
  2. 编译相关问题分析思路:
    1. 先从 app 的构建产物里面分析相应的结果;
    2. 涉及到有依赖关系分析的可以将所有 task 的输入输出全部打印出来;
    3. 1、2满足不了时,会考虑去看相应的源码;
    4. 最后的大招就是调试编译过程;
  3. 从云音乐 app 这次 agp 升级的效果来看,app 的体积降低了接近 7M,编译速度也有很大的提升,特别是 release 速度快了 10 分钟+(task 合并),整体收益还是比较可观的。

文章中使用的测试工程;

参考资料

  1. Shrink, obfuscate, and optimize your app
  2. r8
  3. Android Gradle plugin release notes

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/190814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8.整数转换为浮点数【2023.11.30】

1.问题描述 整数转换为浮点数。 2.解决思路 使用input函数读取输入的整数 input_int int(input()) #将整数转换为浮点数类型 output_float float(input_int) 3.代码实现 numint(input("请输入一个整数")) num1float(num) print(num1)4.运行结果

GD32 定时器输入捕获模式测量PWM占空比和频率

简介 利用GD32 定时器的PWM输入捕获模式来实现PWM波形的占空比和频率的测量。相应的简介可以参考GD32用户手册中关于定时器输入捕获的章节&#xff0c;PWM输入捕获模式是输入捕获模式的一个特例。(记录自己学习过程&#xff0c;如有错误请留言指出) 原理 如何利用定时器测量…

VUE设计与实现共读系列之ref的实现【响应式原理】

前言 我们先顺一下vue使用响应式数据的流程&#xff1a; vue 是通过 ref 和 reactive 来创建响应式值&#xff0c;改变响应式值&#xff0c;视图跟着发生变化。 我们今天就来看一下ref和reactive是如何实现的 准备 首先&#xff0c;打开ref函数的位置 我们可以看到一个被re…

SmartSoftHelp8,SQL语句优化,耗时,返回数据行,kb

SQL语句优化 SQL语句耗时测试&#xff0c;耗时优化 SQL语句查询返回数据行统计 SQL语句查询返回数据大小统计&#xff0c;kb 总量统计 下载地址&#xff1a;https://pan.baidu.com/s/1zBgeYsqWnSlNgiKPR2lUYg?pwd8888

施人玫瑰手留余香和影像组学、医学人工智能未来漫谈

今天收到进阶班学员的留言&#xff1a; 提示&#xff1a;本文有硬核软文嫌疑&#xff0c;请慎重阅读。“ 我用您给我们讲的CLEAR&#xff0c;与一个审稿人进行了battle。有理有据。评估下来&#xff0c;我感觉我们的文章还是挺符合CLEAR的。” 我从来不排斥在商言商&#xff0…

12月02日每日信息差

_灵感 &#x1f396; 六国入境免签首日2029人次享便利 &#x1f384; 国内首个超大规模“光伏气膜”项目在江苏投运 &#x1f30d; 东京将推出氢气交易市场 &#x1f30b; 中国疾控中心&#xff1a;建议尽早接种流感疫苗&#xff0c;尤其是老年人和儿童 &#x1f381; 偏高1.…

【Qt开发流程】之事件系统1:事件系统描述及键盘事件

Qt的事件系统 在Qt中&#xff0c;事件是对象&#xff0c;派生自抽象的QEvent类&#xff0c;它表示应用程序内部发生的事情或作为应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理&#xff0c;但它们与小部件特别相关。以下描述了在典型应用程序中…

基于ZLMediaKit的webrtc实时视频传输demo搭建

环境 ubuntu 20.04 ​ gcc version 9.4.0 ​ cmake version 3.16.3 部署ZLMediaKit流媒体服务器 安装openssl 首先可以检查一下自己的openssl的版本如果是1.1.1以上就可以忽略这一步 wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz tar -xvzf openssl-1.1.1k…

酷开科技 | 酷开系统,让家庭娱乐方式焕然一新!

在这个快节奏的社会&#xff0c;家庭娱乐已成为我们日常生活中不可或缺的一部分&#xff0c;为了给家庭带来更多欢笑与感动&#xff0c;酷开科技发力研发出拥有丰富内容和技术的智能电视操作系统——酷开系统&#xff0c;它集合了电影、电视剧、综艺、游戏、音乐等海量内容&…

C语言-指针_01

指针基础 1. 概述 地址编号&#xff1a;计算机为了存储数据&#xff0c;每一个程序在 32位 机中 占4G&#xff0c;最小操作单位 是 一个字节&#xff0c;每一个字节都有其对应的地址&#xff0c;该地址就是 地址编号。 指针&#xff1a;地址编号这个数据 的 数据类型。 指针变…

TPC通信-BS架构

BS架构-基本原理 BS框架基本原理 使用线程池对BS架构进行优化

docker部署typecho博客

文章目录 1.安装git2.安装compose3.拉取仓库4.创建目录5.配置文件修改6.启动容器7.修改MYSQL数据库8.安装成功9.参考GitHub文档 1.安装git 安装git yum -y install git2.安装compose &#xff08;docker安装参考&#xff1a;docker基本知识&#xff09; 确保已经安装了 Doc…

爬虫学习-基础(HTTP原理)

目录 一、URL和URI 二、HTTP和HTTPS &#xff08;1&#xff09;HTTP &#xff08;2&#xff09;HTTPS &#xff08;3&#xff09;HTTP与HTTPS区别 &#xff08;4&#xff09;HTTPS对HTTP的改进&#xff1a;双问的身份认证 三、TCP协议 &#xff08;1&#xff09;TCP三次握手…

⭐ Unity 里让 Shader 动画在 Scene 面板被持续刷新

写 Unity Shader的时候&#xff0c;只有播放状态下的 Game 面板能看到Shader 顺畅的动态效果&#xff0c;不方便。 想要带有动态效果的 Shader 在 Scene 面板持续更新动画&#xff0c;只需要打开一个开关就能让 Scene 持续刷新动画了。 感谢大家的观看&#xff0c;您的点赞和关…

在oracle中的scn技术

SCN可以说是Oracle中一个很基础的部分&#xff0c;但同时它也是一个很重要的。它是系统中维持数据的一致性和顺序恢复的重要标志&#xff0c;是数据库非常重要的一种数据结构。 转载&#xff1a;深入剖析 - Oracle SCN机制详细解读 - 知乎 (zhihu.com)https://zhuanlan.zhihu.…

基于运算放大器的电压采集电路

一、运算放大器 运放推导的两个重要概念&#xff1a;虚短、虚断。 1、差分放大器 以差分放大器为例进行推导分析。 虚断–运放的"-“端、”“端的引脚电流接近为0&#xff1b; 根据基尔霍夫电流定律可知&#xff1a;iR1iRF&#xff0c;iR2iR3&#xff1b; iR1(Ui1-(V-…

C语言结构体详解(一)(能看懂文字就能明白系列)

&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;个人主页&#xff1a; 古德猫宁- &#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;…

04.PostgreSQL是如何实现隔离级别的?

PostgreSQL是如何实现隔离级别的&#xff1f; 事务有哪些特性&#xff1f; 事务看起来感觉简单&#xff0c;但是要实现事务必须要遵守 4 个特性&#xff0c;分别如下&#xff1a; 原子性&#xff08;Atomicity&#xff09;&#xff1a;一个事务中的所有操作&#xff0c;要么…

Istio新架构揭秘:环境化Mesh

自问世以来&#xff0c;Istio因其使用Sidecar&#xff08;可编程代理与应用容器一同部署&#xff09;而备受认可。这种架构选择使Istio用户能够享受其好处&#xff0c;而无需对其应用进行 drast 改变。这些可编程代理&#xff0c;与应用容器紧密部署在一起&#xff0c;因其能够…

java学习part27线程死锁

基本就是操作系统的内容 138-多线程-线程安全的懒汉式_死锁_ReentrantLock的使用_哔哩哔哩_bilibili