Android Gradle下载编译失败全链路排错指南:从环境到缓存
你正兴高采烈地打开一个刚拉回来的 Android 项目IDE 还在转圈导入Gradle sync 的进度条就停在某个百分数不动了。几秒后Event Log 里多了一大串红色报错有 Could not resolve有 Connection refused还有 Download 卡了几分钟最后超时。这种场面我见过太多次甚至曾经在群里看到人发一张报错截图下面紧接着就是“怎么搞”。其实在这个阶段九成问题都不是你代码的问题而是环境、网络、缓存、版本四个环节里至少有一个在拖后腿。所以我打算把 Android Gradle 项目下载和编译失败这件事做成一份足够系统的排错笔记。它不会只给零散答案而是按“报错发生在哪个环节”来拆解告诉你先看什么、再改什么、什么情况下要下重手。这篇文章会持续更新每次我自己或身边同事又踩到新坑我都会把排查过程补进来。适合正在被 sync 卡住的初学者也适合 CI 上突然挂掉、对着日志一头雾水的团队同学拿来对照。1. 别急着一路 clean先定位编译失败发生在哪一层1.1 报错信息里最有价值的三个字段看到一堆报错别慌。第一步不是 clean不是重启 IDE而是把报错完整复制下来从里面抓三个信息。第一个是依赖坐标像Could not resolve com.example:library:1.0.0告诉你具体是哪个依赖出了问题第二个是失败任务Execution failed for task :app:compileDebugKotlin告诉你在构建的哪一步挂掉第三个是底层原因Connect timeout、checksum mismatch、No cached version available这直接决定了改仓库、改缓存还是改工具链。报错字段常见样子它告诉你什么依赖坐标Could not resolve com.example:library:1.0.0哪个依赖拉不到失败任务Execution failed for task :app:compileDebugKotlin构建在哪一步挂掉底层原因Connect timeout / checksum mismatch / no cached version该修网络、缓存还是仓库地址很多人不看底层原因直接把整段日志发群最后得到的答案也只是“换个镜像试试”。如果自己先把这三个字段拆出来排查范围可以缩小一大半。另外建议把完整报错保存下来不要只在 IDE 弹窗里看因为弹窗可能折叠掉关键上下文尤其是任务名和某个依赖路径。1.2 Sync 失败和 Build 失败是两种完全不同的场景Gradle sync 是项目导入和配置阶段它主要做的事情是加载插件、解析依赖、构建设置。如果 sync 失败报错往往发生在配置阶段根因集中在仓库访问、插件坐标、SDK 路径、Gradle 版本这些地方。而 Build 失败是执行阶段可能是 Kotlin 编译、资源合并、代码混淆等具体 task 出问题。如果你看到 sync 成功但真正运行 build 才爆红那多半要检查代码和插件配置而不是继续折腾仓库镜像。很多人把这两种场景混在一起排查白白浪费了不少时间。举一个我见过很多次的例子sync 成功执行打包时 AAPT2 报错有人立刻去换仓库源换完问题照旧。AAPT2 报错通常是资源文件本身有语法问题或者 AAPT2 可执行文件与当前系统/AGP 版本不匹配跟依赖仓库没有任何关系。把问题先归类才不会被报错表面的英文字母带偏。1.3 一个快速的分层排查表下面这个表是我处理问题时最常对照的。你可以把它当成第一张筛子看到报错先按表归个类再决定下一步动作。报错特征优先排查层第一动作Could not resolve / Could not GET / Download 超时仓库与网络换镜像、检查仓库声明SDK location not found本机 SDK 路径配置 local.properties 或环境变量Unsupported class file major versionJDK 与 Gradle 版本检查 Java 版本和 Gradle 运行时Execution failed for task :app:mergeDebugResources资源文件与 AGP检查资源文件、AAPT2 和 AGP 版本Kotlin could not find required JDKJDK 配置给 Gradle daemon 指定正确 JDK这张表不能覆盖全部情况但能覆盖大多数我实际遇到过的下载编译失败。后面的章节会展开每一项怎么查、怎么改以及几个容易被误判的案例。2. 依赖拉不下来先从 Maven 仓库的“远近”开始管2.1 卡在 Download 的本质是仓库可达性问题Gradle 默认声明的公共仓库服务位于海外某些网络环境下连接不稳定甚至直接超时。尤其是项目第一次 sync、本地缓存为空的时候每个依赖都要从远端拉一遍只要有一个连接超时整个构建就会失败。这不是项目代码问题是仓库可达性不好。把仓库换成国内可用的镜像源或者公司内部搭建的私服是最直接的解决手段。就算你平时打开网页很顺畅也不代表 Gradle 下载能稳定通过。浏览器有缓存、有 CDN而 Gradle 请求依赖库的后端路径可能因为 TLS 握手、DNS 解析等因素超时。所以我常跟人说先别怀疑代码先怀疑到仓库之间的那段链路。判断方式也简单在报错日志里搜Could not GET如果后面跟的是一长串超时时间基本就是仓库访问问题。此时再换仓库比反复 clean 有用得多。2.2 仓库声明顺序会影响解析速度在 Gradle 里依赖仓库是按声明顺序查询的。如果第一个仓库刚好是一个慢源Gradle 会先去那边找超时之后才轮到第二个仓库来回几次等待时间就非常可观。反过来把可达性好的镜像放在最前面大多数依赖第一次就能命中。不过要注意不是所有依赖在任何仓库里都存在。比如很多 Android 官方库只在 Google 仓库能拿到你只放镜像不够。所以合理的策略是国内镜像放第一名google() 和 mavenCentral() 放在后面兜底。这样既保证速度又保证覆盖。镜像往往有同步延迟如果某个依赖刚发布、镜像里还没有Gradle 会自动继续找下一个仓库不会影响解析。这也解释了为什么有些人设置了镜像仍然失败要么是仓库顺序不对要么是声明位置不对。2.3 配置镜像的推荐位置和正确打开方式现代 Gradle 项目建议在 settings.gradle(.kts) 里用 dependencyResolutionManagement 统一管理仓库。比如// settings.gradle dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url uri(https://mirrors.example.com/maven) } google() mavenCentral() } }这里镜像地址我给的是一个占位符因为它经常变化你搜索“国内 maven 仓库镜像”就能找到当前可用的地址。设置之后所有模块都会走这套仓库配置不用在每个 build.gradle 里重复。如果你还在用老式的 allprojects 写法也建议迁移到这里统一管理很多依赖解析的怪问题就是这么被避免的。还要注意一个细节repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)会禁止模块单独声明仓库强制所有仓库配置都集中处理。这是好习惯但迁移到老项目时可能触发“Repository was not found”之类的错误。遇到这种情况需要检查各个模块里有没有自己声明的仓库把它们挪到 settings.gradle 里统一处理再去掉模块内的仓库代码。2.4 还有哪些隐藏网络问题Gradle Wrapper 下载、动态版本除了依赖本身Gradle 发行包也可能下载失败。打开项目时IDE 会根据 gradle-wrapper.properties 里的 distributionUrl 去下载指定版本的 Gradle如果这个地址访问不通界面会一直卡在下载 Gradle 的提示上。症状跟依赖下载失败很像。解决思路有两个一是手动把 Gradle 发行包下载到本机修改 distributionUrl 指向本地文件二是用一个局域网内可访问的镜像地址。注意修改之后要重启 Gradle daemon否则旧进程还占着文件。另一个和网络相关的坑是动态版本。有人为了省事写implementation com.example:lib:或者latest.release这样每次 sync 都要向仓库查询最新版本仓库稍不稳定就会失败。上线项目尽量锁死具体版本号把构建行为变成可预期的东西。动态版本在公司团队的公共模块中尤其危险你永远不知道下一次构建拉回来的代码是什么。3. 构建工具链的三件套Gradle、JDK、Android SDK3.1 版本矩阵不符会直接编造出“奇怪报错”AGPAndroid Gradle 插件、Gradle、JDK 三者之间存在兼容性矩阵。版本不匹配时报错往往不会直接说“你该升级”而是变成一堆看起来很奇怪的异常。常见的对应关系大概是AGP 7.4 一般需要 Gradle 7.5 和 JDK 11AGP 8.1 以上建议 Gradle 8.0、JDK 17Gradle 8.x 多数也需要 JDK 17 才能运行。如果你开了新项目却用了旧模板报错往往看着像 Kotlin 或依赖解析问题实际是版本矩阵不对。检查方式很简单在项目根目录执行./gradlew --version看输出的 Gradle 版本和 JVM 版本再对照你项目里声明的 AGP 版本。如果发现某个版本偏低优先升级 Gradle Wrapper而不是立刻降 AGP因为降 AGP 可能引入其他不兼容。在 IDE 里开发时建议用自带 JDK 而不是系统 JDK减少同一台机器多项目之间的版本打架。CI 环境则单独安装 JDK 17并确保环境变量 JAVA_HOME 指向它。3.2 SDK 找不到时先看一眼项目里的 local.properties“SDK location not found”是我见过最多的配置类报错之一。项目根目录的 local.properties 是 IDE 生成的本地配置文件里面会写一行sdk.dir...指向本机 Android SDK 所在目录。如果这个文件缺失、路径不对Gradle 就完全不知道 SDK 在哪。解决方法是在项目根目录创建或修正 local.properties或者设置环境变量 ANDROID_HOME 指向 SDK 根目录。注意 local.properties 里是本机绝对路径默认不应该提交到版本控制。从开源平台 clone 的项目没有这个文件是正常的自己建一个就行。构建服务器上如果没有装 Android SDK需要先安装命令行工具并在 CI 脚本里配置路径。还有一个容易被忽略的点sdk.dir 指向的目录需要对当前用户有读写权限否则下载 SDK 组件或生成构建产物时又会失败。3.3 缓存目录能解决 90% 的“玄学失败”当你反复遇到依赖下载失败、jar 包半途损坏、校验和始终对不上时问题很可能出在 Gradle 本地缓存。Gradle 会把下载过的依赖缓存在~/.gradle/caches项目构建产物在项目根目录的build/下。缓存损坏后常见报错是checksum mismatch或者一些看起来莫名其妙的文件锁定提示。处理步骤并不复杂先执行./gradlew --stop把 Gradle daemon 停掉然后删除~/.gradle/caches里对应的缓存目录再重新 sync。不需要每次把整个缓存目录全删掉可以先按模块删删错了无非重新下载一次。如果实在定位不到是哪个文件损坏才考虑移除整个 caches 目录。另外磁盘空间不足也会让异步下载半途失败遇到下载卡住时顺手检查一下磁盘剩余空间能省不少时间。clean 命令主要清的是项目 build 产物清不掉全局缓存所以别再迷信一路 clean。4. 几个典型的失败案例复盘照着比对4.1 案例 AAGP 与 Gradle 版本不匹配只在构建时爆红有次我帮人看一个从旧仓库里拉下来的工程sync 一直报“AGP requires Gradle 8.0”但项目 gradle-wrapper.properties 里写的还是 Gradle 7.6。这种问题在团队协作中很典型仓库里代码是新写的AGP 版本被升级过但 Gradle Wrapper 没有一起提交或者有人用了本机老版本覆盖了 wrapper。解决办法很简单编辑 gradle-wrapper.properties 里的 distributionUrl 指向需要的 Gradle 版本重新 sync。这里要提醒一句不要看到 AGP 要求高就顺手把 AGP 降回旧版。降级 AGP 可能让代码里的新特性全部报错比如某些 API 只在 AGP 8 里可用。改版本之前最好备份项目里的 gradle 配置文件和 wrapper.properties因为 Gradle 升级过程中会自动迁移部分配置有时候会改掉你原本的插件声明。保持“先升级 Gradle再升级 AGP最后统一 JDK”的顺序会稳很多。4.2 案例 B依赖全部解析成功却在 Transform 阶段失败另一个项目 sync 一切正常依赖也都能解析只有执行assembleDebug时在某个 Transform 阶段崩溃堆栈里完全看不到业务代码。查到最后是 Kotlin 插件版本在不同模块之间不统一一个模块用 1.8另一个模块用 1.9导致编译器插件链互相不兼容。这种情况盯着报错位置查很难定位正确做法是先从根目录的版本目录文件或 build.gradle 里确认 Kotlin 版本只用了一个再执行./gradlew dependencies看实际解析出来的版本有没有冲突。类似的“伪编译错误”还遇到过源码目录里突然出现两个同名类、混淆规则写错导致 R8 阶段崩溃、资源文件命名不规范触发 AAPT2 异常。它们的共同特点是 sync 正常、依赖正常但真正执行任务的时候挂掉。遇到这类问题不要碰仓库源先看 task 名再回溯对应模块的配置和代码。4.3 案例 CNDK 与 ABI 过滤导致安装包编译崩溃还有一个典型的坑项目包含原生代码模块里写了 ABI 过滤配置比如只保留 arm64-v8a但本机 NDK 版本和代码预期版本不一致链接过程频繁报错。看到 C 报错很多人第一反应是代码写错了结果最后发现只是 NDK 版本不对。解决方式是在模块 build.gradle 里明确指定ndkVersion并在 SDK Manager 或命令行中安装对应的 NDK 版本让本机和项目声明完全对齐。如果项目根本不需要原生代码直接关掉 externalNativeBuild或者把相关依赖模块移除能省下一整类问题。这个案例也再次说明下载编译失败的原因千奇百怪但大多数都能在“版本对齐”和“环境配置”两个方向找到答案。把这些案例记下来比临时翻文档有用得多。5. 要把这份笔记做成“持续更新”我的整理思路5.1 建立自己的排错记录模板我建议每个 Android 开发者维护一份本地排错记录哪怕不公开发表也很有价值。模板很简单日期、项目类型、Gradle/AGP/JDK 版本、完整报错关键行、根因、修复动作、备注。遇到新的失败时先搜索旧记录里有没有相同报错。很多人二次踩到同一个坑就是因为全靠记忆没有留下可检索的记录。记录时不要只抄结论要把排查链路写下来。比如从报错关键字看到Could not resolve然后怎么判断是仓库问题而不是版本问题中间做了什么测试。这些思考路径才是最有价值的下次遇到相似问题可以直接复用而不需要从头开始分析。5.2 升级依赖前先做三件事第一查兼容性矩阵。AGP、Gradle、JDK 的版本关系在官方文档里有明确说明每次升级前花十分钟查清楚比之后折腾一整晚报错要划算。第二在干净分支上升级。不要在功能开发到一半的分支上直接动构建工具链否则报错和代码问题混在一起极难定位。第三逐模块编译。改完插件版本先跑./gradlew :app:compileDebugKotlin通过之后再跑全量构建。忌讳一次性把 AGP、Kotlin、Gradle 全部升到最新多个新版本叠加后报错会很复杂。如果项目里存在依赖覆盖还要留意./gradlew dependencies的输出看有没有某个依赖被偷偷升级到不兼容的版本。这个命令在排查“sync 成功但运行报错”时非常有用比怀疑业务代码靠谱得多。5.3 关于环境重构的一点个人体会遇到莫名其妙的失败很多人第一反应是重装 IDE、清空所有缓存甚至重装系统但绝大部分情况下不需要。我推荐的重建顺序是先停 daemon清 Gradle 缓存换镜像仓库再核对 SDK/JDK/版本矩阵还不行才考虑重建项目。重装只是把问题搬到另一个环境里根因还在那里。持续更新这份笔记本质上是在给自己建立一套人肉缓存。每次遇到新的失败类型我都会按前面的模板补一条然后更新对应的章节。等你把排查模板也用起来多半也能沉淀出自己的版本。希望这套方法能让正在被下载编译失败折磨的你少交点无谓的学费。

相关新闻

PS5全设备通吃改造指南:键鼠适配、M.2扩容与多区账号实操

PS5全设备通吃改造指南:键鼠适配、M.2扩容与多区账号实操

上个月朋友搬来一台PS5,说想拉着我联机打某款射击游戏。刚上手那会儿我是真有点崩溃,习惯了键鼠的手突然握着手柄,瞄准准星飘得跟喝多了一样。为了不让朋友看笑话,我给自己折腾了一套方案,起名叫AnyPS5。说白了就是一句…

2026/10/11 4:31:15 阅读更多 →
PS5通用优化指南:存储扩容、串流调优与系统设置避坑

PS5通用优化指南:存储扩容、串流调优与系统设置避坑

很多人看到"AnyPS5"这个项目名,第一反应是"又是什么魔改系统?"或者是"破解相关?"——都不是。这个项目拆开看就是"Any PS5",意思是:不管手头是初版机、轻薄版还是数字版&…

2026/10/12 6:50:51 阅读更多 →
后台服务插件对比分析:从选型方法到实测数据

后台服务插件对比分析:从选型方法到实测数据

后台服务跑得稳不稳,往往不是看主流程代码写得多漂亮,而是看那些挂在服务边上的插件选得对不对。我这里说的插件,是日志采集、缓存加速、定时任务、消息转发这一类辅助组件。它们单个看起来都不大,可是任何一个拉胯,都…

2026/10/11 4:30:14 阅读更多 →

最新新闻

上下文管理实战:从预算、分层到压缩与外部记忆的工程取舍

上下文管理实战:从预算、分层到压缩与外部记忆的工程取舍

1. 上下文管理不是"记性好",而是"知道什么时候该忘"很多人第一次听到"上下文管理"这个词,脑子里浮现的是"让模型记住更多东西"。这个理解不能说错,但只对了一半。真正做过落地项目的人会告诉你&…

2026/10/12 6:51:59 阅读更多 →
AnyPS5跨平台串流工具:从设计到部署的完整指南

AnyPS5跨平台串流工具:从设计到部署的完整指南

1. 从“AnyPS5”这个标题说起:一个跨平台串流工具的设计与实现第一次看到“AnyPS5”这个标题,我的直觉是:这应该是一个围绕主机游戏串流展开的项目。所谓串流,就是把主机上运行的游戏画面,经过编码压缩后通过网络传输到…

2026/10/12 6:51:59 阅读更多 →
CentOS 8源码编译安装Redis并用systemd托管全流程

CentOS 8源码编译安装Redis并用systemd托管全流程

先把话说在前头:如果你在CentOS 8上装Redis只是图省事,那用系统自带的DNF装个老版本确实最快,但我自己实际部署过之后,还是建议你走源码编译这条路。CentOS 8的软件仓库里Redis版本偏老,而且模块化机制跟第三方软件源混…

2026/10/12 6:51:59 阅读更多 →
从 0 到 1 玩 AI 开发:OpenManus 免邀请、ChatDev 自动组队、MetaGPT 全流程,TaoToken 统一 Key 打通多智能体调用

从 0 到 1 玩 AI 开发:OpenManus 免邀请、ChatDev 自动组队、MetaGPT 全流程,TaoToken 统一 Key 打通多智能体调用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 6:51:59 阅读更多 →
CentOS 8 Redis安装配置与卸载清理实操指南

CentOS 8 Redis安装配置与卸载清理实操指南

说实话,CentOS 8 上装 Redis 这事儿,看着简单,但这两年坑是真不少。系统源里自带的版本偏老、模块流切换搞不明白、装完忘记配密码被扫描器盯上,卸载的时候又因为残留文件导致重装后数据错乱——这些场面我见过太多次了。这篇文章…

2026/10/12 6:51:59 阅读更多 →
Hadoop流量日志分析全链路实战:从NetFlow接入到Presto秒级查询

Hadoop流量日志分析全链路实战:从NetFlow接入到Presto秒级查询

简介:本资源是一篇万字原创学士学位毕业论文,面向计算机科学与技术、软件工程等专业的本科及专科毕业生,聚焦Hadoop架构在流量日志分析场景中的落地应用,系统解决大数据环境下日志采集、分布式存储、并行计算与可视化分析等核心问…

2026/10/12 6:50:59 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →