这里是 LSPosed 模块仓库的发布页 —— 只放 release,不放源码。 源码、构建脚本、开发笔记都在 https://github.com/threevits/threekey-nocapsule
装法:下载下面 release 里的 APK → LSPosed 里确认作用域勾了「系统界面」→ 重启一次 SystemUI 生效。
LSPosed 模块。拨动三段式开关时,响应界面不用流体云胶囊,回落成 ColorOS 的经典弹窗;其余场景的流体云完全不受影响。
适用于 ColorOS 系设备(ColorOS / OxygenOS / RealmeUI),实测通过:一加 Ace 3 / ColorOS 16。
OShin(O神)里有个「强制关闭流体云」开关,能让三段式开关退回经典弹窗 —— 但它是全局的,整机所有流体云一起消失。
这个模块只掐三段式那一条路径。
- ColorOS 系设备(ColorOS / OxygenOS / RealmeUI)+ LSPosed
- 需要设备有三段式开关(一加旗舰和部分 OPPO 机型有)。没有的话模块就是空转, 不会出问题,只是什么都不会发生
- 实测通过:一加 Ace 3(PJE110)/ ColorOS 16,SystemUI
16.00.12
- OShin 里的「强制关闭流体云」必须是关的,否则流体云本来就全局停摆,看不出区别 (模块检测到插件不可用时会自动让开,不会打架)
ColorOS 的流体云内部代号是 seedling(幼苗)。SystemUI 里三段式的整个判断只有一处:
com.oplus.systemui.opthreekey.tips.OpThreekeyDialogImpl 的 showH():
if (seedlingPluginManager.isPluginAvailable) {
mOpThreeKeySeedling.updateThreeKeySeeding(ctx, getThreeKeyStateForSeedling());
h.obtainMessage(2, 1, 0).sendToTarget(); // 立刻 dismiss 经典弹窗
return;
}
// ↓ 只有插件不可用时才走这里
rescheduleTimeoutH();
mDialog.show();其中 updateThreeKeySeeding() 是全 dex 里唯一发送 ringModeEvent 的地方,
最终交给 OplusSeedlingService 渲染成胶囊。
模块做法:hook 住 showH,在它执行的那一瞬间把
SeedlingPluginManager.isPluginAvailable 按成 false,方法返回后再还原。
- 三段式 → 掉进 else 分支 → 经典弹窗
- 别处的流体云 → 因为立刻还原了,完全不知情
SeedlingPluginManager 是全局单例,所以有个极短的改写窗口,
但整个 showH 是主线程上的同步调用,期间没有其他主线程代码能观察到。
1. 不能按名字找 showH。
它是 Kotlin 给内部类 H 生成的合成方法,dex 里的真名是 -$$Nest$mshowH ——
带 - 前缀,连合法的 Java 标识符都不是(jadx 会显示成 m248$$Nest$mshowH,
那个序号每次编译都在变)。而且 -$$Nest$m 这个前缀在别的类里同样出现,
VolumeDialogImplSubDisplay 就有一个同名同签名的。
所以按签名特征找:static + 返回 void + 参数恰好是 (OpThreekeyDialogImpl, int)。
该类静态方法总共只有它和 <clinit>,不会撞。
2. 日志会骗人。
OplusSeedlingService: SeedlingDataManager-->setTip: ...RingModeTip... eventCode=ringModeEvent
看着像流体云被触发了,其实那行夹在 BaseService-->onCreate / onBind 中间,
同一毫秒里 RingModeTip/FlashlightTip/DndTip/MediaControlTip 四个一起注册 ——
是服务初始化批量注册事件处理器,不是真触发。
验证要看的是 OpThreekeyDialogImpl-->mDialogView onViewAttachedToWindow
(经典弹窗渲染)和 OplusSeedlingService 什么时候 onCreate。
./build.sh
adb install -r build/module.apk手搓构建(kotlinc + d8 + aapt2),不用 Gradle。只需要自备 Android SDK,其余全自动:
| 依赖 | 怎么来 |
|---|---|
build-tools;34.0.0 + platforms;android-34 |
自备,用 ANDROID_SDK_ROOT 指向 SDK(默认 ~/Android/Sdk) |
| Kotlin 编译器 2.4.20 | 首次构建自动从 Maven Central 下到 ./tools/(约 63 MB,之后走缓存) |
Xposed API(tools/api-82.jar,25 KB) |
随仓库提交,不用管 |
| 签名钥匙 | 缺失时自动生成到项目根的 debug.keystore(已在 .gitignore 里) |
想用自己的钥匙设 KEYSTORE,工具链想放别处设 TOOLS_DIR。
MIT
manifest 里声明了 xposedscope,LSPosed 会自动勾上「系统界面」作用域,
装完重启一次 SystemUI 就生效:
adb shell su -c 'killall com.android.systemui'(在 LSPosed 里也能手动确认:模块 → 作用域 → 系统界面。)
确认模块进来了:
adb logcat | grep "三段式无流体云"应该看到 已挂上 -$$Nest$mshowH(OpThreekeyDialogImpl, int),
第一次真正生效后还会再打一条 已生效。
直接看日志里有没有 已挂上:
- 没有 → hook 没挂上,多半是 ColorOS 升级后类名/签名变了
- 有
已挂上但行为没变 →isPluginAvailable那个判据变了
两种都去 showH 里重新找判据。反编译用 jadx:
# SystemUI 的 dex 在 /system_ext/priv-app/SystemUI/SystemUI.apk
# 三段式相关类在 classes6.dex,包名 com.oplus.systemui.opthreekey