Glide源码解析
前言
Glide是一款专为Android设计的开源图片加载库。有以下特点:1.支持高效加载网络、本地及资源图片;2.具备良好的缓存策略及生命周期管理策略;3.提供了简易的API和强大的功能。本文将对其源码进行剖析。
基本使用
dependencies {compile 'com.github.bumptech.glide:glide:3.7.0'
}
<uses-permission android:name="android.permission.INTERNET" />
// 基础用法:加载网络图片
Glide.with(context).load("https://ts1.tc.mm.bing.net/th/id/R-C.3a1b98d8aa749503cc2ff9c224bc8b40?rik=xxNkH0iChSUYqg&riu=http%3a%2f%2fd.ifengimg.com%2fq100%2fimg1.ugc.ifeng.com%2fnewugc%2f20190119%2f10%2fwemedia%2fabbab6554fa54232bec645b46e6e7bb3f0e4cc5b_size2326_w3000_h2000.JPG&ehk=UzIcp%2fHqCMHntTpDKBDEvAT%2bhhu8xR805ZL0enQCZ%2fY%3d&risl=1&pid=ImgRaw&r=0").override(800, 600) // 指定分辨率.skipMemoryCache(true) // 跳过内存缓存.diskCacheStrategy(DiskCacheStrategy.ALL) // 全量磁盘缓存.into(imageView)
核心API设计遵循with().load().into()
三步式结构,隐藏底层复杂实现。
-
with()
:绑定生命周期,初始化并返回RequestManager
。 -
load()
:指定资源(URL、本地路径、资源ID等),返回RequestBuilder
。 -
into()
:触发加载流程,最终显示到Target
(通常是ImageView
)。
Glide执行流程图
Glide.with(Activity).load(url).into(ImageView)│├─ with: 创建 RequestManager(绑定生命周期)│ └─ 注入 SupportRequestManagerFragment 监听生命周期│├─ load:构建 RequestBuilder(设置 Model 和 Options)│├─ into: 创建 ImageViewTarget(包装 ImageView)│├─ into: Engine.load()│ ├─ 生成 EngineKey(唯一标识请求)│ ├─ 检查 Active Resources → 命中则直接返回│ ├─ 检查 Memory Cache → 命中则返回并加入 Active│ └─ 未命中 → 创建 EngineJob 和 DecodeJob│ ││ ├─ DecodeJob.run()│ │ ├─ 尝试从 RESOURCE_CACHE 加载解码后的资源│ │ ├─ 尝试从 DATA_CACHE 加载原始数据│ │ ├─ 从 SOURCE(网络/文件)加载数据│ │ ├─ 解码数据(使用 BitmapPool 复用)│ │ └─ 转码为目标类型(Drawable/Gif)│ ││ └─ 将结果写入 Active Resources 和 Memory Cache│└─ into: 主线程回调 onResourceReady() → 显示图片
Glide 初始化(with)
// 初始化调用链示例
Glide.with(context) → Glide.get(context) // 触发GlideBuilder构建实例→ GlideBuilder.build() → 初始化Engine、Registry、MemoryCache等核心组件→ RequestManagerRetriever.get() // 注入Fragment并绑定生命周期
单例模式
通过双重校验锁(DCL)实现线程安全的单例初始化,首次调用时触发GlideBuilder.build()
。
public static Glide get(Context context) {if (glide == null) {synchronized (Glide.class) {if (glide == null) {//....GlideBuilder builder = new GlideBuilder(applicationContext);for (GlideModule module : modules) {module.applyOptions(applicationContext, builder);}glide = builder.createGlide();for (GlideModule module : modules) {module.registerComponents(applicationContext, glide);}}}}return glide;}
绑定生命周期
创建透明Fragment以管理Glide生命周期,透明Fragment与外层页面生命周期保持一致。
Glide 通过 Glide.with(context)
中传入的 context 管理生命周期。有以下两种情况:
-
当传入的context是Activity/Fragment上下文时:
-
Glide会向当前页面注入透明的Fragment(如
SupportRequestManagerFragment
),该Fragment通过FragmentManager
与页面生命周期同步。在onStart
/onStop
/onDestroy
时(LifecycleListener)触发Glide的请求管理(暂停加载或释放资源)。
-
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {RequestManagerFragment current = getRequestManagerFragment(fm);RequestManager requestManager = current.getRequestManager();if (requestManager == null) {requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());current.setRequestManager(requestManager);}return requestManager;
}@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingRequestManagerFragments.get(fm);if (current == null) {// 在Activity中注入Fragmentcurrent = new RequestManagerFragment();pendingRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;
}
-
当传入的context为Application上下文或者在非主线程调用
with()
时。-
绑定应用全局生命周期,直接创建RequestManager对象,适用于后台线程或服务等场景。
-
private RequestManager getApplicationManager(Context context) {// Either an application context or we're on a background thread.if (applicationManager == null) {synchronized (this) {if (applicationManager == null) {// Normally pause/resume is taken care of by the fragment we add to the fragment or activity.// However, in this case since the manager attached to the application will not receive lifecycle// events, we must force the manager to start resumed using ApplicationLifecycle.applicationManager = new RequestManager(context.getApplicationContext(),new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());}}}return applicationManager;}
核心组件初始化
负责GlideContext,Engine,Registry 等对象的创建,并向 Registry 中注册各种工具类。
-
RequestManager
:Glide管理中心。-
生命周期管理、请求配置管理、分发调度管理等
-
RequestManager
不直接管理Request
,而是交由RequestTracker
管理Request
的启动,取消,暂停等。
-
-
Engine:任务调度与资源管理核心。负责图片加载执行,协调缓存查找、资源加载、线程调度及生命周期管理:
-
内存缓存管理和磁盘缓存接口(Glide缓存一节阐述)
-
任务调度器和线程池(Glide加载一节阐述)
-
-
Registry:组件注册与数据处理中枢。负责扩展能力,用于注册和管理所有数据处理组件,确保灵活支持多种数据源与处理逻辑。
-
ModelLoader:将复杂数据模型(如 URL、File)转换为可解码的数据流(如
InputStream
)。 -
ResourceDecoder:将原始数据(如
InputStream
)解码为资源(如Bitmap
、GIF
)。 -
Transcoder:转换资源格式(如
Bitmap
转Drawable
),通过BitmapDrawableTranscoder
实现。
-
Glide 加载(load)
负责匹配数据加载器(ModelLoader),并返回DrawableTypeRequest请求对象。
Glide.with(context) .load(url) // 创建RequestBuilder,匹配ModelLoader→ loadGeneric(String.class) // 根据数据类型选择ModelLoader→ 创建DrawableTypeRequest<String>实例
选择数据加载器
ModelLoader<T, Data> :将数据模型(T)转换为可解码的数据流(Data)。我们传入的是String,这块加载的是StreamStringLoader,功能是将URL转为InputStream。我们甚至可以自定义ModelLoader。
// RequestManager.java
public DrawableTypeRequest<String> load(String string) {return (DrawableTypeRequest<String>) loadGeneric(String.class).load(string);
}private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {// 关键步骤:通过Registry匹配ModelLoaderModelLoader<T, InputStream> streamLoader = glide.buildStreamModelLoader(modelClass, context);ModelLoader<T, ParcelFileDescriptor> fileLoader = glide.buildFileDescriptorModelLoader(modelClass, context);return new DrawableTypeRequest<>(modelClass, streamLoader, fileLoader, ...);
}
构造请求构建器
DrawableTypeRequest:针对Drawable资源的请求构建器,继承了DrawableRequestBuilder,管理数据加载、解码、转换流程等;
Glide渲染(into)
Glide 的缓存术语:
Active 内存缓存:正在使用的图片对应的内存缓存
Cache 内存缓存:不在使用的图片对应的内存缓存
Data 磁盘缓存:原始数据对应的磁盘缓存
Resource 磁盘缓存:解码后数据对应的磁盘缓存
流程图
加载流程:
-
请求发起与构建阶段
-
发起图片加载请求:通过
Glide.with(context).load(uri).into(imageView)
等 API 发起加载 -
构建 Request 对象:封装加载参数(URL、宽高、转换规则等);生成唯一的请求标识(Key);确定图片加载优先级。
-
-
多级缓存检查流程
-
活动资源缓存(ActiveResources):存储当前正在使用的图片资源(被 View 引用的图片);使用
WeakReference
+ReferenceQueue
实现,避免内存泄漏;优先检查:若图片正在被使用,直接复用资源。未命中处理:进入内存缓存检查。 -
内存缓存检查(
MemoryCache
):存储近期使用的图片(LruCache 实现);若活动资源缓存未命中,再检查内存缓存;命中后会将图片转移到活动资源缓存中。 -
磁盘缓存检查(
DiskCache
):缓存位置分为SOURCE
(原始资源)和RESULT
(处理后资源)两种类型;命中处理:从磁盘读取缓存文件,解码图片数据并进入后续处理流程;未命中处理:发起网络请求获取图片。
-
- 图片获取与处理阶段
-
网络请求阶段:使用
HttpUrlConnection
或OkHttp
发起请求;支持断点续传和重试机制;下载图片数据到临时文件。 -
图片解码与转换
-
解码流程:使用
BitmapFactory
或ImageDecoder
解码图片‘’支持自动判断图片格式(JPEG、PNG、WEBP 等)。 -
转换处理:按请求参数进行尺寸缩放(
override(width, height)
);应用图片转换(圆角、高斯模糊、旋转等);支持自定义转换接口(Transformation
)。
-
-
- 缓存与显示阶段
-
缓存处理:内存缓存将处理后的图片存入
LruCache
;活动资源缓存:将正在显示的图片存入ActiveResources
;磁盘缓存:将处理后的图片写入磁盘(RESULT
类型)。 -
图片显示:通过
ImageViewTarget
或自定义 Target 绑定显示组件;支持动画效果(淡入、缩放等);处理图片显示异常(如加载失败、内存不足)。 -
回调与监听:提供了完整的生命周期回调:o
nStart()
:加载开始;onSuccess()
:加载成功;onError()
:加载失败;onResourceReady()
:资源准备完成。
-
关键机制:
-
唯一标识(EngineKey):根据请求参数(URL、尺寸、变换、签名等)生成唯一键,确保缓存匹配和请求合并的准确性。
-
缓存层级与回退:1.查询顺序:
Active缓存
→Memory缓存
→Resource磁盘缓存
→Data磁盘缓存
。2.逐级回退:优先复用活跃资源,逐级下沉查询,最大限度减少耗时操作。 -
请求合并:相同
EngineKey
的请求复用回调,避免重复加载和解码,提升性能。 -
资源释放:1.引用计数:
Active缓存
通过引用计数管理资源生命周期,解绑Target
时计数归零则移入Memory缓存
。2.LRU清理:内存和磁盘缓存按最近最少使用策略淘汰旧资源,防止内存泄漏。
加载阶段:
生成唯一标识(EngineKey)
- 参数收集:根据
load(url)
的输入参数(URL、尺寸override(800,600)
、转换选项centerCrop()
、签名signature()
等)生成唯一标识。 - 哈希计算:将参数组合序列化后,通过
SHA-256
生成哈希值,确保不同参数组合的请求哈希不同。 - 构建 EngineKey:将哈希值与其他上下文参数(如
Target
类型)合并,生成最终EngineKey
。
检查内存缓存
-
ActiveResources 查询:使用
EngineKey
在ActiveResources
(弱引用缓存)中查找正在使用的资源。命中:更新引用计数(acquire()
),直接返回资源。未命中:进入MemoryCache
查询。 -
MemoryCache 查询:使用
EngineKey
在LruResourceCache
(LRU 内存缓存)中查找。命中:资源移至ActiveResources
,引用计数初始化为 1。未命中:进入磁盘缓存查询。
处理重复请求
- 旧请求检测:通过
RequestTracker
检查同一Target
是否已绑定旧请求。 - 取消旧请求:若旧请求未完成,调用
Request#clear()
释放资源并移除任务队列。 - 合并新请求:若新旧请求的
EngineKey
相同,直接复用旧请求的回调,避免重复加载。
查询磁盘缓存
-
Resource 缓存查询:根据
EngineKey
的变体(如尺寸调整后的ResourceCacheKey
)查找已解码资源。命中:解码资源并缓存到ActiveResources
。未命中:进入 Data 缓存查询。 -
Data 缓存查询:根据原始数据标识(
DataCacheKey
)查找未解码的原始数据(如网络响应字节流)。命中:解码数据并应用转换,写入 Resource 缓存(若策略允许)。未命中:触发网络请求。
发起新请求
- 任务创建:
Engine
创建EngineJob
(管理生命周期)和DecodeJob
(执行加载)。 - 线程池分配:
DecodeJob
被提交到GlideExecutor
(磁盘或网络线程池)。 - 数据加载:
- 网络请求:通过
HttpUrlFetcher
下载数据,写入 Data 缓存(若策略为DiskCacheStrategy.DATA
)。 - 本地加载:通过
FileLoader
直接读取文件。
- 网络请求:通过
-
解码与转换:使用
Downsampler
解码数据,应用Transformation
(如CenterCrop
),生成最终资源
缓存更新与资源释放
-
Active缓存写入:新资源通过
Engine#onEngineJobComplete()
加入ActiveResources
。 -
Memory缓存淘汰:当资源引用计数归零时(如
Target
解绑),资源移入MemoryCache
。 -
LRU 清理:当
MemoryCache
或磁盘缓存达到上限时,按 LRU 规则淘汰旧资
图片显示(主线程回调)
-
资源就绪通知:
DecodeJob
完成解码后,通过EngineJob#notifyCallbacksOfResult()
通知主线程。 -
主线程切换:通过
MainThreadExecutor
(内部使用Handler(Looper.getMainLooper())
)切换到主线程。 -
应用资源到 Target:
-
ImageView 显示:调用
ImageViewTarget#onResourceReady()
,将资源(如Bitmap
、Drawable
)设置到ImageView
。 -
动画处理:若配置了过渡动画(如
crossFade()
),通过ViewPropertyTransition.animate()
执行动画。
-
-
错误与占位符处理:
-
加载失败:调用
onLoadFailed()
,显示错误占位符(通过error(Drawable)
配置)。 -
占位符替换:在加载完成前显示
placeholder(Drawable)
,加载成功后替换。
-
相关文章:

Glide源码解析
前言 Glide是一款专为Android设计的开源图片加载库。有以下特点:1.支持高效加载网络、本地及资源图片;2.具备良好的缓存策略及生命周期管理策略;3.提供了简易的API和强大的功能。本文将对其源码进行剖析。 基本使用 dependencies {compile …...

7.RV1126-OPENCV cvtColor 和 putText
一.cvtColor 1.作用 cvtColor 是 OPENCV 里面颜色转换的转换函数。能够实现 RGB 图像转换成灰度图、灰度图转换成 RGB 图像、RGB 转换成 HSV 等等 2.API CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0 ); 第一个参数:…...
Android 之 kotlin 语言学习笔记二(编码样式)
参考官方文档:https://developer.android.google.cn/kotlin/style-guide?hlzh-cn#whitespace 1、源文件命名 所有源文件都必须编码为 UTF-8。如果源文件只包含一个顶级类,则文件名应为该类的名称(区分大小写)加上 .kt 扩展名。…...

Redisson单机模式
redisson调用unlock的过程 Redisson 是一个基于 Redis 的 Java 驻内存数据网格(In-Memory Data Grid)框架,提供了分布式和可扩展的数据结构和服务。Redisson 的 unlock 方法用于释放锁。下面是 unlock 方法的调用过程: 获取锁的状…...

数据结构第6章 图(竟成)
第 6 章 图 【考纲内容】 1.图的基本概念 2.图的存储及基本操作:(1) 邻接矩阵法;(2) 邻接表法;(3) 邻接多重表、十字链表 3.图的遍历:(1) 深度优先搜索;(2) 广度优先搜索 4.图的基本应用:(1) 最小 (代价) 生…...

机器人现可完全破解验证码:未来安全技术何去何从?
引言 随着计算机视觉技术的飞速发展,机器学习模型现已能够100%可靠地解决Google的视觉reCAPTCHAv2验证码。这标志着一个时代的结束——自2000年代初以来,CAPTCHA("全自动区分计算机与人类的图灵测试"的缩写)一直是区分…...

CppCon 2014 学习:(Costless)Software Abstractions for Parallel Architectures
硬件和科学计算的演变关系: 几十年来的硬件进步:计算机硬件不断快速发展,从提升单核速度,到多核并行。科学计算的驱动力:科学计算需求推动硬件创新,比如需要更多计算能力、更高性能。当前的解决方案是并行…...

网络爬虫 - App爬虫及代理的使用(十一)
App爬虫及代理的使用 一、App抓包1. App爬虫原理2. reqable的安装与配置1. reqable安装教程2. reqable的配置3. 模拟器的安装与配置1. 夜神模拟器的安装2. 夜神模拟器的配置4. 内联调试及注意事项1. 软件启动顺序2. 开启抓包功能3. reqable面板功能4. 夜神模拟器设置项5. 注意事…...
Kafka集群部署(docker容器方式)SASL认证(zookeeper)
一、服务器环境 序号 部署版本 版本 1 操作系统 CentOS Linux release 7.9.2009 (Core) 2 docker Docker version 20.10.6 3 docker-compose docker-compose version 1.28.2 二、服务规划 序号 服务 名称 端口 1 zookeeper zookeeper 2181,2888,3888 2 ka…...
【python爬虫】利用代理IP爬取filckr网站数据
亮数据官网链接:亮数据官网...

群晖 NAS 如何帮助培训学校解决文件管理难题
在现代教育环境中,数据管理和协同办公的效率直接影响到教学质量和工作流畅性。某培训学校通过引入群晖 NAS,显著提升了部门的协同办公效率。借助群晖的在线协作、自动备份和快照功能,该校不仅解决了数据散乱和丢失的问题,还大幅节…...

NLP学习路线图(十八):Word2Vec (CBOW Skip-gram)
自然语言处理(NLP)的核心挑战在于让机器“理解”人类语言。传统方法依赖独热编码(One-hot Encoding) 表示单词,但它存在严重缺陷:每个单词被视为孤立的符号,无法捕捉词义关联(如“国…...
P1438 无聊的数列/P1253 扶苏的问题
因为这两天在写线性代数的作业,没怎么写题…… P1438 无聊的数列 题目背景 无聊的 YYB 总喜欢搞出一些正常人无法搞出的东西。有一天,无聊的 YYB 想出了一道无聊的题:无聊的数列。。。 题目描述 维护一个数列 ai,支持两种操…...

嵌入式学习笔记 - 新版Keil软件模拟时钟Xtal灰色不可更改的问题
在新版Keil软件中,模拟时钟无法修改XTAL频率,默认只能使用12MHz时钟。这是因为Keil MDK从5.36版本开始,参数配置界面不再支持修改系统XTAL频率,XTAL选项变为灰色,无法修改。这会导致在软件仿真时出现时间错误的问题&…...
k8s的出现解决了java并发编程胡问题了
Kubernetes(K8s)作为一种开源的容器编排平台,极大地简化了应用程序的部署、管理和扩展。这不仅解决了很多基础设施方面的问题,也间接解决了Java并发编程中的一些复杂问题。本文将详细探讨Kubernetes是如何帮助解决Java并发编程中的…...
如何利用大语言模型生成特定格式文风的报告类文章
在这个算法渗透万物的时代,我们不再仅仅满足于大语言模型(LLM)能“写”,更追求它能“写出精髓,写出风格”。尤其在专业且高度格式化的报告类文章领域,仅仅是内容正确已远远不够,文风的精准复刻才是决定报告是否“对味儿”、能否被目标受众有效接受的关键。这不再是简单的…...

黑马Java面试笔记之 集合篇(算法复杂度+ArrayList+)
一. 算法复杂度分析 1.1 时间复杂度 时间复杂度分析:来评估代码的执行耗时的 常见的复杂度表示形式 常见复杂度 1.2 空间复杂度 空间复杂度全称是渐进空间复杂度,表示算法占用的额外存储空间与数据规模之间的增长关系 二. 数组 数组(Array&a…...
【从0-1的HTML】第2篇:HTML标签
文章目录 1.标题标签2.段落标签3.文本标签brbstrongsubsup 4.超链接标签5.图片标签6.表格标签7.列表标签有序列表ol无序列表ul定义列表dl 8.表单标签9.音频标签10.视频标签11.HTML元素分类块级元素内联元素 12.HTML布局13.内联框架13.内联框架 1.标题标签 标题标签:…...
从“Bucharest”谈起:词语翻译的音译与意译之路
在翻译中,面对地名、人名或新兴术语时,我们常常会遇到一个抉择:到底是“音译”,保留其原发音风貌,还是“意译”,让它意义通达? 今天我们以“Bucharest”为例,展开一次语言与文化的微…...

Nginx+Tomcat负载均衡
目录 Tomcat简介 Tomcat 的核心功能 Tomcat架构 Tomcat 的特点 Tomact配置 关闭防火墙及系统内核 Tomcar 主要文件信息 配置文件说明 案例一:Java的Web站点 案例二:NginxTomcat负载均衡、动静分离 Tomcat简介 Tomcat 是由 Apache 软件基金会&am…...
JVM——JVM中的字节码:解码Java跨平台的核心引擎
引入 在Java的技术版图中,字节码(Bytecode)是连接源代码与机器世界的黄金桥梁。当开发者写下第一行public class HelloWorld时,编译器便开始了一场精密的翻译工程——将人类可读的Java代码转化为JVM能够理解的字节码指令。这些由…...

【论文解读】ReAct:从思考脱离行动, 到行动反馈思考
认识从实践开始,经过实践得到了理论的认识,还须再回到实践去。 ——《实践论》,毛泽东 1st author: About – Shunyu Yao – 姚顺雨 paper [2210.03629] ReAct: Synergizing Reasoning and Acting in Language ModelsReAct: Synergizing Reasoning and…...
数据解析:一文掌握Python库 lxml 的详细使用(处理XML和HTML的高性能库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、lxml 概述1.1 lxml 介绍1.2 安装和第一个案例1.3 性能优化技巧二、XML处理2.1 解析XML2.2 访问元素2.3 遍历XML树2.4 修改XML2.5 写入XML三、HTML处理3.1 解析HTML3.2 XPath查询3.3 CSS选择器四、高级功能4.1 使用命…...
react native webview加载本地HTML,解决iOS无法加载成功问题
在react native中使用 “react-native-webview”: “^13.13.5”,加载HTML文件 Android: 将HTML文件放置到android/src/main/assets目录,访问 {uri: file:///android_asset/markmap/index.html}ios: 在IOS中可以直接可以直接放在react native项目下,访问…...

简单配置RHEL9.X
切换默认运行级别 将系统默认启动模式从多用户的图形界面调整为多用户的文本界面,适用于优化系统资源占用或进行远程服务器管理的场景。 注意:安装选择“带GUI的服务器”部分常用命令默认安装;如果选择“最小安装”时,部分常用命…...
默认网关 -- 负责转发数据包到其他网络的设备(通常是路由器)
✅ 默认网关概括说明: 默认网关(Default Gateway)是网络中一台负责转发数据包到其他网络的设备(通常是路由器)。当一台主机要访问不在本地子网内的设备时,会将数据包发给默认网关,由它继续转发…...
python调用硅基流动的视觉语言模型
参考: https://docs.siliconflow.cn/cn/userguide/capabilities/vision import base64 import json from openai import OpenAI from PIL import Image import io# 初始化OpenAI客户端 client OpenAI(api_key"sk-**********", # 替换为实际API密钥b…...

下载并运行自制RAG框架
项目部署 https://github.com/huangjia2019/rag-project01-framework git clone https://github.com/huangjia2019/rag-project01-framework.git 一 、 前端分部分部署 在 Ubuntu 系统 上安装 Node.js 和 npm(Node Package Manager),并初始…...

Rust 学习笔记:Cargo 工作区
Rust 学习笔记:Cargo 工作区 Rust 学习笔记:Cargo 工作区创建工作区在工作区中创建第二个包依赖于工作区中的外部包向工作区添加测试将工作区中的 crate 发布到 crates.io添加 add_two crate 到工作区总结 Rust 学习笔记:Cargo 工作区 随着项…...

颈部的 “异常坚持”
生活中,有些人的颈部会突然变得 “异常坚持”—— 头部不受控制地偏向一侧,或是不自主地旋转、后仰,仿佛被无形的力量牵引着。这种情况不仅影响外观,还会带来强烈的不适感,颈部肌肉紧绷、酸痛,像被一根绳索…...