BlockDirs

阻止手机在插入 OTG U 盘 / SD 卡等外置可移动存储时,自动在其根目录创建 Android、Music、Pictures、DCIM、Movies 等标准媒体目录的 LSPosed 模块。

已在 ColorOS 16(Android 16,实测 PJZ110 / ColorOS V16.1.0)上端到端验证通过;理论上基于 AOSP 的系统也可使用。


背景

Android 的 MediaProvider(com.android.providers.media.module,UID 10176)在检测到外置存储卷首次挂载时,会在卷的根目录初始化一批标准媒体目录(Android、Music、Pictures、DCIM、Movies 等)。这些目录的创建并非走 Java 层的 File.mkdir,而是由 MediaProvider 通过 native 层 FUSE 直接调用 libc 的 mkdir / mkdirat 完成,因此常规的 Java 层 Hook 无法拦截。

原理

本模块 Hook 位于 MediaProvider 进程内的 libc mkdir / mkdirat 符号(基于 ShadowHook 的 inline hook),采用位置判定法而非「黑名单目录名」:

  • 仅当目录路径满足以下条件时才拦截:
    • 路径前缀为 /storage/、/mnt/media_rw/ 或 /mnt/secure/;
    • 且排除内部存储 emulated;
    • 且是「卷 UUID 之后仅一层」的目录名;
  • 命中后返回 0(假装成功)但不真正创建目录。

设计要点

  • 不硬编码目录名,零遗漏:任何第一层目录都会被拦,不存在「漏掉某个标准目录名」的风险。
  • 仅拦截外置可移动存储:手机内部存储 /storage/emulated 完全不受影响。
  • 更深层子目录不受影响:只拦卷根下的第一层,XXX/Music/... 这类子路径不拦。
  • 进程级隔离:Hook 只安装在 MediaProvider 进程内,你用自己的文件管理器手动建目录走的是另一个进程,不会被拦。
  • 不会死循环:mkdir 返回 0(成功)是终止信号,调用方收到「成功」不会重试,因此不会出现「反复创建」的现象。

副作用(请知悉)

  • 外置卷根目录下任何第一层目录都会被拦截,包括你手动在卷根下新建的目录(但手动目录由文件管理器进程创建,实际不受影响,此条仅为 hook 语义说明)。

构建

GitHub Actions

仓库已配置 build.yml,push 后会自动构建 arm64-v8a 的 Release APK(release 复用 debug 签名,便于覆盖安装)。

本地构建

./gradlew assembleRelease
# 产物:app/build/outputs/apk/release/app-release.apk

环境:AGP 8.2.0、Gradle 8.6、JDK 17、NDK 27.2.12479018、CMake 4.0.2(native 以 CMake 构建)。


安装与启用

  1. 安装构建出的 APK。
  2. 打开 LSPosed,勾选模块(模块已内置作用域推荐,会自动预选 MediaProvider),作用域选择:
    • 系统框架
    • com.android.providers.media.module(MediaProvider)
  3. 重启手机(或软重启使 zygote 注入生效)。
  4. 插入 OTG U 盘 / SD 卡,观察其根目录不再自动生成媒体目录。

若之前安装过旧模块 io.github.rootuser.no_otg_media_folders,建议先卸载以避免混淆验证。


目录结构

top.yixiangren.blockdirs/
├── app/                 # Android 应用(LSPosed 入口 + 关于页 UI)
│   └── src/main/
│       ├── java/top/yixiangren/blockdirs/
│       │   ├── MainHook.java      # Xposed 入口(zygote 加载 native + 注入 hook)
│       │   └── MainActivity.java  # 关于页 UI
│       └── res/layout/activity_main.xml
├── native/              # native hook 实现(ShadowHook + libc mkdir/mkdirat)
│   └── blockdirs.cpp
└── ...

作者

yixiangdebufengren

Releases

9/18/2026, 9:48:52 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
fix: 进度弹窗变量类型改为 androidx.appcompat.app.AlertDialog(MaterialAlertDialogBuilder.create 返回 AppCompat 类型)

Assets

1

9/18/2026, 9:36:37 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
feat: 检查更新改为手动检测流程(正在检查→已最新/弹更新弹窗),启动静默检测

Assets

1

9/18/2026, 9:32:22 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
feat: 启动时自动检测更新并弹窗(立即更新/忽略本次/稍后再说),移除原理卡片

Assets

1

9/18/2026, 9:26:51 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
feat: 新增「检查更新」卡片,跳转 GitHub Releases 最新版

Assets

1

9/18/2026, 8:06:39 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
refactor: 规范激活检测代码结构,精简日志仅保留异常日志

Assets

1

9/18/2026, 8:01:54 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
fix: 激活检测对齐简书方案,用 lpparam.classLoader hook ModuleActive,并加日志

Assets

1

9/18/2026, 7:31:32 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
refactor: 激活检测改为 hook 自身方法(大佬方案,无持久化残留)

Assets

1

9/18/2026, 7:06:49 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
fix: 激活标记改用文件 IO(XSharedPreferences 只读,写会抛异常)

Assets

1

9/18/2026, 6:48:16 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
fix: colorSurface 使用 material R.attr 而非 android.R.attr

Assets

1

9/18/2026, 4:45:23 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
fix(ci): commit message 多行内容用 heredoc 写入 GITHUB_OUTPUT,避免特殊字符被解析为 workflow command

Assets

1

9/18/2026, 4:27:25 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
feat: 启用 Material You 动态取色(跟随壁纸),移除手写主题色映射

Assets

1

9/18/2026, 4:07:10 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
fix: 激活检测改为免 root 的 XposedBridge 检测(借鉴 WeKit isLegacyXposed)

Assets

1

9/18/2026, 3:56:14 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
feat: 添加模块激活状态卡片(仿 WeKit M3 风格)

Assets

1

9/18/2026, 3:36:16 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
引入 Material3 组件:MaterialSwitch 替换手绘开关,主题升级 Material3,开启 R8 混淆

Assets

1

9/18/2026, 3:04:07 AM

阻止外置存储(OTG U 盘 / SD 卡)自动创建 Android、Music、Pictures、DCIM、Movies 等标准目录的 LSPosed 模块。

本次更新:
版本号改为自动生成:versionCode=提交数,versionName=git+短hash

Assets

1