- 移动开发
- 文件管理
【免费下载链接】sdmaid-se
SD Maid 2/SE is Android's most thorough cleaning tool.
本文以仓库根目录
.claude/CLAUDE.md为骨架,面向使用 Claude Code 等 AI 编码助手参与 SD Maid SE(SD Maid 2)开发的开发者,系统讲解该 Android 清理工具的核心定位、九大清理工具模块、FOSS/GPLAY 构建变体、.claude/rules/路径作用域规则加载机制,并深入到本地化、测试、架构、DataStore 设置等工程约定的源码实现,帮助 AI 助手和开发者准确理解代码库并规避常见坑。
一、SD Maid SE 是什么:面向维护场景的 Android 文件管理工具
.claude/CLAUDE.md开篇即给出项目定位:SD Maid SE(又名 SD Maid 2)是一款专精于维护与系统清理的 Android 文件管理工具,其核心目的是释放存储空间、移除不需要的数据。这与仓库根目录 README.md 的描述一致——它是经典 SD Maid 的继任者,针对新版本 Android 做了完整重写,聚焦于前代最受欢迎的功能。
对于 AI 编码助手而言,理解这一定位至关重要:仓库中的一切代码(工具模块、IO 网关、自动化服务、设置存储)都服务于"扫描并删除无用数据"这条主线,阅读代码时应始终围绕清理任务的扫描(scan)、处理(process)两条路径展开,而不是把它当作通用的文件管理器。
二、九大清理工具:模块划分与源码对应
CLAUDE.md列出了仓库的九大功能工具。从源码结构看(settings.gradle 是权威的模块清单),每个工具都对应一个独立的app-tool-*Gradle 模块,遵循"一工具一模块"的命名约定:
| 工具 | 职责 | 对应模块 |
|---|---|---|
| AppCleaner | 删除 expendable 文件(缓存、垃圾数据) | app-tool-appcleaner |
| CorpseFinder | 清理已卸载应用遗留的数据 | app-tool-corpsefinder |
| SystemCleaner | 用户可配置的随机文件过滤与系统清理 | app-tool-systemcleaner |
| Deduplicator | 查找并移除重复数据 | app-tool-deduplicator |
| Analyzer | 存储概览与分析 | app-tool-analyzer |
| AppControl | 控制/禁用应用及其组件 | app-tool-appcontrol |
| Scheduler | 调度清理动作 | app-tool-scheduler |
| Swiper | 面向旧文件的"滑动清理"工作流 | app-tool-swiper |
| Squeezer | 存储优化(压缩) | app-tool-squeezer |
以 AppCleaner 为例,其模块内部结构印证了.claude/rules/architecture.md所述的模式:app-tool-appcleaner/src/main/java/eu/darken/sdmse/appcleaner/core/下分为核心逻辑(AppCleaner.kt、AppCleanerSettings.kt、AppJunk.kt)与自动化子包(automation/ClearCacheTask.kt、ClearCacheModule.kt、ClearCacheLoop.kt,以及按厂商 ROM 划分的specs/aosp/、specs/coloros/、specs/alcatel/、specs/androidtv/等规格实现)。这正是"每个工具独立模块、核心逻辑 + 任务定义 + 扫描器/探测器 + UI 组件"的典型布局。
三、构建变体:FOSS 与 GPLAY
CLAUDE.md明确指出仓库有两大构建变体:
- FOSS:开源版本,不依赖 Google Play 服务;
- GPLAY:Google Play 版本,包含额外功能(如 Play 渠道的升级检查等)。
源码层面,这两个变体在 app/src 下分别对应foss/与gplay/两个 source set,各有独立的java/eu/实现(FOSS 19 个 Kotlin 文件、GPLAY 41 个 Kotlin 文件)与独立的混淆规则(proguard-foss.pro、proguard-gplay.pro)。
CLAUDE.md给出的开发建议非常明确:本地开发一律使用 FOSS debug flavor。理由可以从 .claude/rules/testing.md 中找到佐证——CI 只跑:app:connectedFossDebugAndroidTest,且 FOSS 变体没有 Google Play 依赖,无需配置上传密钥即可构建运行。
四、规则加载机制:.claude/rules/ 的路径作用域
CLAUDE.md最关键的信息是仓库为 AI 助手设计了按路径作用域加载的规则体系:.claude/rules/下的部分规则文件通过 YAML frontmatter 中的paths:声明作用域,只有当 AI 助手触碰匹配的文件时,对应规则才会加载进上下文。仓库中实际存在 8 个规则文件:
| 规则文件 | 路径作用域(由 frontmatter 声明) |
|---|---|
| architecture.md | 无 paths(全局加载) |
| code-style.md | 无 paths(全局加载) |
| commit-guidelines.md | 无 paths(全局加载) |
| localization.md | **/res/values*/strings.xml、tooling/translation/**、crowdin.yml |
| testing.md | **/src/test*/**、**/src/androidTest/**、**/src/screenshotTest/**、app-common-test/**、app-e2e/**等 |
| automation.md | app-common-automation/**、**/automation/**、**/*Automation*.kt |
| datastore-settings.md | **/*Settings.kt、**/datastore/** |
| release.md | version.properties、VERSION、tools/release/**、CI 工作流等 |
CLAUDE.md特别强调有两条规则必须在相关文件被触碰之前就生效(即全局性的硬约束):
- 所有用户可见文本必须抽取到所属模块的
strings.xml,绝不硬编码 UI 字符串——详见 .claude/rules/localization.md; - 动手写测试前先读一个相似的既有测试——这会顺带把测试规则拉入上下文,详见 .claude/rules/testing.md。
这种设计使得 AI 助手在编辑不同领域的文件(如改一个strings.xml、写一个单元测试、动一次version.properties)时,只会加载与当前任务相关的约束,避免上下文被无关规则稀释。
五、本地化约定:字符串抽取与 CaString
本地化规则是本仓库对 AI 助手最重要的硬约束之一。核心要求包括:
- 用户可见文本必须抽取到所属功能模块的
strings.xml;跨模块复用的通用文案放在app-common模块的strings.xml;新建条目前先检查app-common是否已有通用版本; - 带多个参数的本地化字符串使用有序占位符:
%1$s is %2$d; - 省略号使用
…而不是三个点...; - String ID 以模块名作前缀,复用文案以
general/common作前缀,且优先"动作后缀"命名(module_screen_open_action而非module_screen_button_open)。
规则还规定了 UI 组件与后端类的差异化取字符串方式,这在源码中有精确对应。UI 组件直接使用getString(R.string.my_string)/context.getString(...);而后端core包中的类与其它非 UI 类必须使用CaString体系,其实现位于 CaString.kt:
R.string.xxx.toCaString() R.string.xxx.toCaString("Argument") caString { getString(R.plurals.xxx, count, count) }从源码看,toCaString()是Int的扩展函数,通过caString { it.getString(this@toCaString, *args) }.cache()实现"延迟解析 + 缓存"(CachedCaString内部以synchronized保证线程安全的懒初始化)。也就是说,后端类在需要字符串时并不立即访问 Android 资源,而是拿到一个"上下文感知字符串"对象,在真正需要渲染时才解析——这是解耦非 UI 层与Context的典型手段。规则还提到getString(R.plurals.xxx, count, count)这类带复数的场景应直接使用caString { ... }块形式。
六、测试约定:从框架选择到 MockK 陷阱
.claude/rules/testing.md 是仓库中最详尽的规则文件,CLAUDE.md建议"写测试前先读相似测试"正是为了把它带入上下文。核心约定如下。
6.1 框架选择与基类体系
- JUnit 5:普通单元测试必须继承
BaseTest(位于 BaseTest.kt),它提供自定义日志、测试清理与IO_TEST_BASEDIR常量; - JUnit 4:Robolectric 测试(Robolectric 不支持 JUnit 5);
BaseComposeRobolectricTest:JVM Compose UI 测试(Robolectric + JUnit 4),位于 BaseComposeRobolectricTest.kt,统一提供composeRule、@RunWith(RobolectricTestRunner)、@Config(sdk = [33], application = TestApplication)与类级unmockkAll()清理,子类无需重复声明;BaseTestInstrumentation/BaseUITest:Android 仪器测试与 UI 仪器测试(均在 app/src/androidTest 的testhelper包下);BaseCSITest:取证/CSI 单元测试基类(BaseCSITest.kt)。
一个 JUnit 5/JUnit 4 混用的关键陷阱:BaseTest的@AfterAll清理钩子是 JUnit 5 注解,在 JUnit 4 / Robolectric 下不会执行;若依赖unmockkAll()需在@After方法中显式调用。
6.2 三个典型的 MockK 坑
规则用专门小节讲解了仓库中无法直接 mock 的三类对象:
DataStoreValue<T>:其.value()读与.value(T)写都是扩展函数(见 DataStoreValue.kt 第 117-119 行),MockK 无法直接 stub。读扩展是flow.first(),写扩展是update { value },应 stub 或 verify 这两个底层方法;单行读取可用app-common-test现成的mockDataStoreValue(value)辅助函数(见 DataStoreHelper.kt);ExclusionManager.save(exclusion):同样是扩展,最终调用真实方法save(toSave: Set<Exclusion>),测试应 mock 真实方法并用slot<Set<Exclusion>>()捕获;NavigationController.consumeResults:Picker 结果经MutableSharedFlow<PickerResult>(replay = 1)这类热流返回,避免 ViewModel 尚未订阅时发射被丢弃。
6.3 协程与 Flow 测试工具
仓库在app-common-test中提供了增强的测试工具:runTest2(TestExtensions.kt)支持expectedError参数与autoCancel;FlowTest(FlowTest.kt)提供flow.test("tag", scope)的收集器,支持await { values, latest -> ... }条件等待、assertNoErrors()、cancelAndJoin()。
针对ViewModel4.safeStateIn(...)(内部是.stateIn(scope, SharingStarted.WhileSubscribed(5000), initialValue),实现见 ViewModel4.kt),规则明确指出:上游 Flow 只在存在下游订阅者时才收集,因此测试中vm.state.value读到的是 initialValue,vm.state.first()会与上游链竞争。正确做法是用TestScope.backgroundScope保持订阅,它在runTest结束时自动取消、不会阻塞测试体——而vmScope.launch { state.collect {} }永远不会完成,会触发 runTest 的"未完成协程"超时。
6.4 什么测试才需要真机
规则列了四类"确实需要设备"的场景,并都给出了仓库内的实证:
- 反射访问框架隐藏 API:
app-common-io的存储包装类(StorageManager2、StorageVolumeX等)通过反射访问StorageManager.getVolumes()等,Robolectric 没有 hidden-API 强制执行面,只有真实运行时才能回答成员是否仍可达——见 app-common-io/src/androidTest 下的StorageVolumeXTest、VolumeInfoXTest; - 真实 App 的全流程:Robolectric 屏幕测试只驱动内部
Screencomposable,不会启动真实 Hilt 图、不会从MainActivity导航、看不到真实权限状态;app的androidTest用ActivityScenario启动MainActivity覆盖这一点。工作示例:OnboardingFlowTest.kt(走完 onboarding 进入 setup 卡片)与SetupDetectionTest; - R8 对发布构建的影响:
app-e2e是com.android.test自插桩模块,其 APK 在独立进程里用 UI Automator 驱动 R8 混淆后的betaAPK——测试的字节码就是将要发布的字节码。规则特别强调:删除 proguard-rules.pro 中的-keep class eu.darken.sdmse.BuildConfig必须让测试变红,这是该模块仍有效测试混淆包的必要检查; - 旧版本遗留数据:
app-e2e的UpgradeTest安装最新 release、通过 UI 制造状态、原地升级到当前 beta、逐项校验非默认值是否存活,由 tools/e2e/upgrade-test.sh 驱动。
6.5 两个容易踩的"假绿"陷阱
- Robolectric 使用桩字体度量渲染:字符宽度恒为约 1.0dp/字符、文本永不换行、行高错误——依赖文本测量的
assertIsDisplayed()断言在此不可靠; - 绿色运行可能测的是过期字节码:尤其是使用
built_in_kotlinc编译器的 worktree 中,曾出现新增@Test未进编译产物仍报成功的情况。核验方法:对比源码grep -c '@Test'与<module>/build/test-results/<task>/TEST-<fqcn>.xml中的tests="N";读取 XML 时注意自闭合 testcase 的/跟在time="…"之后,应交叉核对<testsuite>上的failures="0"。
七、架构约定:模块体系、路径系统与 ViewModel 层级
.claude/rules/architecture.md 总结了 AI 助手理解代码所需的核心架构事实:
- 模块命名:
app-common-*是共享基础设施,app-tool-*是清理工具(一工具一模块),权威清单在 settings.gradle。其中app是入口与变体实现、app-common-test是供其它模块测试消费的 JVM 测试工具库、app-common-data承载 Room 数据库、app-common-adb经 Shizuku 集成 ADB、app-common-shell经响应式FlowShell执行 shell 操作; - 路径系统:抽象路径体系
APath/RawPath,配合 Gateway 模式支持普通/root/ADB(Shizuku) 三种文件访问方式。规则强调访问路径段必须用APath.segments,不要手工 split 路径字符串; - MVVM 层级:自定义 ViewModel 继承链位于 app-common-ui/src/main/java/eu/darken/sdmse/common/uix:
ViewModel1(调试日志与 tag)→ViewModel2(DispatcherProvider、vmScope、launch()、launchInViewModel())→ViewModel4(错误事件errorEvents、导航navTo()/navUp()、safeStateIn())。注意没有ViewModel3,新 ViewModel 一律继承ViewModel4并使用@HiltViewModel; - UI 模式:所有界面均为 Compose(旧 Fragment/XML 已彻底移除),每个屏幕拆成
<Feature>ScreenHost(唯一接触hiltViewModel()、副作用与事件收集,必须调用ErrorEventHandler(vm)与NavigationEventHandler(vm))和纯展示的<Feature>Screen(internal、可用 mockflowOf()数据做 Preview)两部分。modifier: Modifier = Modifier必须是@Composable函数第一个参数;所有 composable 需要@Preview2+PreviewWrapper预览; - 陷阱:
android.nonTransitiveRClass=true已启用,app模块的R.attr只含自身定义的 attrs,主题 attr 必须用声明模块的 R 类(如com.google.android.material.R.attr.colorSecondary)。
八、DataStore 设置约定:createValue 的两种重载
.claude/rules/datastore-settings.md 解释了所有*Settings.kt文件的底层设施——DataStore<Preferences>上存在两个createValue()重载(实现见 DataStoreValue.kt):
- 基本类型(Boolean/String/Int/Long/Float)不需要额外参数,底层由
basicKey/basicReader/basicWriter按类型映射到 DataStore 的booleanPreferencesKey等:val usePreviews = dataStore.createValue("core.ui.previews.enabled", true) - 复杂
@Serializable类型需要传入 kotlinx.serialization 的Json:val themeMode = dataStore.createValue("core.ui.theme.mode", ThemeMode.SYSTEM, json)
只有存储的 JSON 可能损坏或来自旧 schema、且希望静默回退到默认值时,才使用fallbackToDefault = true。读写统一走.value()(挂起)与.flow(响应式)——注意.value()读/写是扩展函数,分别基于flow.first()与update { value }(源码第 117-119 行),这正是测试章节需要 stub 底层方法的原因。
九、代码风格、自动化与发布规则的要点
9.1 代码风格(.claude/rules/code-style.md)
- 按功能分包而非按层分包;优先流式(flow-based)与响应式方案;可取消的操作要保证取消;
- Kotlin 约定:多行参数与集合加尾逗号、非单行
if必须加括号、用FlowCombineExtensions替代嵌套 combine、@Suppress尽量贴近受影响代码; - 日志用
logTag()生成 tag、log()+ lambda 消息,多段 tag 会拼成SDMSE:Review:Settings:Gplay形式,避免在参数中做字符串拼接(会产生无用开销); - 共享 Compose 组件如
SdmMascot(Lottie 动画吉祥物,按日期切换万圣节/圣诞节/新年装扮)位于app-common-ui/.../compose/。
9.2 自动化系统(.claude/rules/automation.md)
AppCleaner 的自动化基于无障碍服务:AutomationManager管理服务生命周期与权限,AutomationService继承AccessibilityService。对调试最有价值的事实是——扫描路径完全没有自动化参与,AppCleaner.performScan不触碰无障碍路径;自动化只发生在performProcessing阶段,由includeInaccessible开关门控并构建ClearCacheTask。因此仅录制扫描过程的调试日志不会包含任何自动化活动,复现自动化错误必须实际执行清理步骤。调试时还要注意:uiautomator dump看到的是与应用自身服务不同的节点树,要了解服务真实所见需在调试录制开启时抓取应用自己的 ACS-DEBUG dump。
9.3 发布流程(.claude/rules/release.md)
- tools/release/bump.sh 是版本逻辑的唯一事实来源,其 versionCode 公式必须与 ProjectConfigPlugin.kt 保持同步;
- CI 在每次 PR 上通过
bash tools/release/bump.sh --mode=check校验(code-checks.yml的check-release-toolingjob);手工改动version.properties或VERSION前必须先本地重跑该命令; release-prepare.yml(从 Actions UI 派发)是 bump 版本与创建 release tag 的唯一合规路径,AI 助手不应建议直接编辑version.properties或手工建 tag。
十、给 AI 助手的实操清单
综合CLAUDE.md与规则目录,参与本仓库开发时应遵循的实操要点:
- 本地开发用 FOSS debug flavor,避免 Google Play 依赖与密钥配置;
- 绝不硬编码用户可见字符串,新文案写入所属模块
strings.xml,后端类经toCaString()访问; - 写测试前先读相似既有测试,普通单测继承
BaseTest(JUnit 5),Compose 单测继承BaseComposeRobolectricTest(JUnit 4),只有反射隐藏 API、真实 App 全流程、R8 混淆包、旧版升级数据四类场景才写androidTest/app-e2e; - 优先 JVM 测试:
MigrationTestHelper在 Robolectric 下即可运行(参见 SwiperDatabaseMigrationTest.kt),Room 迁移测试放androidTest在本仓库是错误做法; - 新增设置项走
createValue(),基本类型直接传默认值、复杂类型传Json,仅在需要兼容损坏/旧数据时启用fallbackToDefault; - 不要建议直接编辑版本文件,版本变更一律走 CI 的
release-prepare.yml; - 警惕两个假绿源:Robolectric 桩字体度量下的布局断言、以及可能存在的过期编译字节码,变更测试后务必核对测试结果 XML。
通过这份指南,AI 编码助手可以在触碰strings.xml、*Settings.kt、测试源码、版本文件等敏感区域时自动加载对应规则,从而在不了解全部代码的情况下也能产出符合仓库约定、可被 CI 与既有测试验证的改动。
- 移动开发
- 文件管理
【免费下载链接】sdmaid-se
SD Maid 2/SE is Android's most thorough cleaning tool.
相关推荐
FoundationDB 仓库开发指南深度解析:面向 AI 编码代理的构建、测试与架构手册
FoundationDB 仓库开发指南深度解析:面向 AI 编码代理的构建、测试与架构手册 本文以 FoundationDB 仓库根目录下的 AGENTS.md
分布式数据库KV存储数据库后端3 步用 GHelper 替代奥创中心:华硕笔记本轻量控制完整指南
3 步用 GHelper 替代奥创中心:华硕笔记本轻量控制完整指南 如果你发现华硕笔记本上预装的奥创中心(Armoury Crate)光是常驻后台就吃掉一大块内
桌面应用系统编程Netcatty 仓库开发指南:面向 AI 编码助手的命令、架构与 IPC 规范
Netcatty 仓库开发指南:面向 AI 编码助手的命令、架构与 IPC 规范 本文以仓库根目录的 CLAUDE.md https://link.gitcod
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考