☰
SD Maid SE 仓库的 AI 编码助手开发指南:项目架构、构建变体与工程规则深度解析
2026/10/15 17:06:42 网站建设 项目流程
  • 移动开发
  • 文件管理

【免费下载链接】sdmaid-se

SD Maid 2/SE is Android's most thorough cleaning tool.

项目地址:https://gitcode.com/gh_mirrors/sd/sdmaid-se
点击查看免费下载

本文以仓库根目录.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.mdapp-common-automation/**、**/automation/**、**/*Automation*.kt
datastore-settings.md**/*Settings.kt、**/datastore/**
release.mdversion.properties、VERSION、tools/release/**、CI 工作流等

CLAUDE.md特别强调有两条规则必须在相关文件被触碰之前就生效(即全局性的硬约束):

  1. 所有用户可见文本必须抽取到所属模块的strings.xml,绝不硬编码 UI 字符串——详见 .claude/rules/localization.md;
  2. 动手写测试前先读一个相似的既有测试——这会顺带把测试规则拉入上下文,详见 .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 什么测试才需要真机

规则列了四类"确实需要设备"的场景,并都给出了仓库内的实证:

  1. 反射访问框架隐藏 API:app-common-io的存储包装类(StorageManager2、StorageVolumeX等)通过反射访问StorageManager.getVolumes()等,Robolectric 没有 hidden-API 强制执行面,只有真实运行时才能回答成员是否仍可达——见 app-common-io/src/androidTest 下的StorageVolumeXTest、VolumeInfoXTest;
  2. 真实 App 的全流程:Robolectric 屏幕测试只驱动内部Screencomposable,不会启动真实 Hilt 图、不会从MainActivity导航、看不到真实权限状态;app的androidTest用ActivityScenario启动MainActivity覆盖这一点。工作示例:OnboardingFlowTest.kt(走完 onboarding 进入 setup 卡片)与SetupDetectionTest;
  3. R8 对发布构建的影响:app-e2e是com.android.test自插桩模块,其 APK 在独立进程里用 UI Automator 驱动 R8 混淆后的betaAPK——测试的字节码就是将要发布的字节码。规则特别强调:删除 proguard-rules.pro 中的-keep class eu.darken.sdmse.BuildConfig必须让测试变红,这是该模块仍有效测试混淆包的必要检查;
  4. 旧版本遗留数据: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与规则目录,参与本仓库开发时应遵循的实操要点:

  1. 本地开发用 FOSS debug flavor,避免 Google Play 依赖与密钥配置;
  2. 绝不硬编码用户可见字符串,新文案写入所属模块strings.xml,后端类经toCaString()访问;
  3. 写测试前先读相似既有测试,普通单测继承BaseTest(JUnit 5),Compose 单测继承BaseComposeRobolectricTest(JUnit 4),只有反射隐藏 API、真实 App 全流程、R8 混淆包、旧版升级数据四类场景才写androidTest/app-e2e;
  4. 优先 JVM 测试:MigrationTestHelper在 Robolectric 下即可运行(参见 SwiperDatabaseMigrationTest.kt),Room 迁移测试放androidTest在本仓库是错误做法;
  5. 新增设置项走createValue(),基本类型直接传默认值、复杂类型传Json,仅在需要兼容损坏/旧数据时启用fallbackToDefault;
  6. 不要建议直接编辑版本文件,版本变更一律走 CI 的release-prepare.yml;
  7. 警惕两个假绿源:Robolectric 桩字体度量下的布局断言、以及可能存在的过期编译字节码,变更测试后务必核对测试结果 XML。

通过这份指南,AI 编码助手可以在触碰strings.xml、*Settings.kt、测试源码、版本文件等敏感区域时自动加载对应规则,从而在不了解全部代码的情况下也能产出符合仓库约定、可被 CI 与既有测试验证的改动。

  • 移动开发
  • 文件管理

【免费下载链接】sdmaid-se

SD Maid 2/SE is Android's most thorough cleaning tool.

项目地址:https://gitcode.com/gh_mirrors/sd/sdmaid-se
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询