OPPO Watch X3 Mini 表盘魔改过程记录
我很喜欢 OPPO 表盘市场的「明晰组件」这个表盘,唯一的美中不足是,日期、星期和秒数都是灰色。暗光下还能看清,但在户外强光下对比度太低,几乎要凑近才能辨认。想把这几个元素改成白色。真正动手后才发现,它不是普通的 APK 表盘,而是 OPPO 的 engine 表盘包,而且主屏和抬腕/AOD 是两套完全不同的渲染资源。
本文记录一次在自家设备上的修改过程,不涉及绕过付费或分发修改后的表盘;如果你也想改自己买的表盘,可以参考思路。
一、它不是 APK
设备是 OPPO Watch X3 Mini(OWW263,Android 11)。「明晰组件」在 pm list packages 里找不到,dumpsys package 里也没有对应的 WatchFaceService,说明它不是独立安装的 APK。
它的真实身份是一个 engine 表盘 zip 包:
1 | 包名:com.heytap.wearable.enginewf.O20250310114637290 |
包内大致是:
1 | images/ 主屏用的 WebP 图层和数字精灵 |
关键在于它有两条渲染路径:
| 场景 | 渲染方 | 使用的资源 |
|---|---|---|
| 解锁后的主屏 | 主 SoC 上的 uiengine | manifest.json + images/ |
| 抬腕亮屏 / AOD | 低功耗 MCU | mcu/bin/style0.bin、mcu/bin/aod/style0.bin 等 |
所以只改 manifest.json 会出现一个很典型的现象:解锁后是白色,抬腕还是灰色。两套资源都要改。
二、把原始包弄出来
手表上的资源在 Launcher 的私有目录里,普通 adb 读不到:
1 | /data/user_de/0/com.heytap.wearable.launcher/files/enginewf/<包名>/ |
手机 OPPO 健康下发前,会先把 zip 写到自己的临时目录:
1 | /data/data/com.heytap.health/files/WatchFace/Temp/Pkg/<包名>.zip |
这个文件同步完成后立即删除,所以要实时轮询复制。手机需要 root:
1 | adb shell su -c ' |
然后在手机 OPPO 健康里删除并重新下载表盘。同步完成后从 /data/local/tmp/wfcap/ 里把 <包名>.zip 拷出来:
1 | adb shell su -c 'cp /data/local/tmp/wfcap/<包名>.zip /sdcard/Download/' |
同目录下还会出现一个 .wtheme 文件,是商店的加密主题包,改色用不到,忽略即可。
三、包结构
| 文件/目录 | 作用 |
|---|---|
images/wf_pic_no_*.webp | 背景、时分秒数字、天气/电量等图层,按编号被 manifest.json 引用 |
images/preview.png / preview_small.png | 表盘选择器里的预览图 |
manifest.json | 主屏显示列表:每个图层的类型、坐标、颜色、字体、数据源 |
infos.json | 表盘元数据:packageName、versionCode、可编辑组件槽位 |
project.json | 设计工具生成的工程数据,运行时基本不用 |
mcu/bin/style0.bin | MCU 正常模式的显示列表(编译后) |
mcu/bin/aod/style0.bin | MCU 的 AOD 版本显示列表 |
mcu/bin/preview.bin | 12 字节头(宽、高、类型、长度)+ PNG 预览图 |
mcu/bin/font_*.bin | MCU 位图字体,魔数 UFL$ |
mcu/bin/setting.bin / widget.bin | MCU 配置头和组件定义 |
style0.bin 不是文本配置,是编译后的二进制显示列表,魔数 78 56 34 12;颜色字段可以直接找出来改,但版式(坐标、字号、文本拆分)没有公开的编译器可用,这一点后面再说。
四、改颜色
4.1 主屏
日期和星期在 manifest.json 里是普通的 Text 节点,颜色为灰色 #FF7D7D7D,共 4 处(主样式 + AOD 样式各一套)。直接替换成白色:
1 | p = 'orig/manifest.json' |
秒数不是文字,而是 WebP 数字精灵,像素本身是灰度 (125,125,125)。把这些图的非透明像素重绘成白色,保留 alpha:
1 | from PIL import Image |
背景图 wf_pic_no_00000000.webp 叠到黑底后已经是 #000000,不用动。截图里日期上方那块 #262626 深灰圆角底是引擎绘制的组件底板,包内没有对应配置,保持原样。
4.2 MCU / AOD
MCU 资源里同样有两处日期/星期的灰色 RGBA 值,分别在 mcu/bin/style0.bin 和 mcu/bin/aod/style0.bin:
1 | for f in ['orig/mcu/bin/style0.bin', 'orig/mcu/bin/aod/style0.bin']: |
mcu/bin/preview.bin 是「12 字节头 + PNG」,把 PNG 里的灰色像素重绘成白色后,还要更新头里的长度字段:
1 | import io, struct |
选择器里的 images/preview.png 和 preview_small.png 也按同样方式重绘一下,保持预览一致。
五、装回手表
5.1 为什么不能直接 adb 安装
手表的 Launcher 有一个 EngineWfProvider(authority com.heytap.wearable.provider.watchfaces),安装表盘时需要向它写入 zip。但它的写入口做了校验:调用方必须是 OPPO 表盘商店(包名 com.heytap.wearable.themestore),并且证书 SHA-256 匹配。
adb shell 能通过 content call --method install_watchface 拿到安装 URI,但 content write 会被拒绝;普通第三方 App 连调用都返回 null。这条路走不通,正确做法是在手机同步给手表之前把 zip 换掉。
5.2 在同步瞬间替换
用 strace 观察手机的同步过程,顺序是:
- 下载进程用
O_WRONLY|O_CREAT|O_TRUNC打开Temp/Pkg/<包名>.zip并写入 - 其他进程随后按路径重新
open(O_RDONLY)读取 - 最后
unlink删除
所以只要在文件刚被创建时,把我们的改色包原子 rename 覆盖到该路径即可。写进程手里的 fd 仍指向被 unlink 的旧 inode,它继续写它的;后面按路径打开的进程拿到的就是我们的文件。
有两个细节:
- 等长:手机/手表会 stat 文件大小,最好让改色包和原包字节数完全一致。用最高压缩级别重打,再用 zip comment 补齐到原大小(本例 341427 字节)。
- 同文件系统:临时文件必须放在目标目录内再
ATOMIC_MOVE。放在/data/local/tmp会报Cross-device link。
等长重打包:
1 | import os, zipfile |
5.3 一个 1ms 轮询的 watcher
用一个很小的 Java 程序跑在手机 root 下(dalvikvm),每 1ms 检查目标文件是否存在;出现时,把源包复制到目标目录内的 .wfrepl.tmp,再 Files.move(..., ATOMIC_MOVE) 覆盖:
1 | Path dir = Paths.get("/data/data/com.heytap.health/files/WatchFace/Temp/Pkg"); |
同步完成后,App 会把包用掉并删除,watcher 看到文件消失(日志 cleared)就自动清理源包、dex、日志并退出,不留后台进程。
部署大致是:
1 | adb push wf_patch.zip /data/local/tmp/wf_mod_same.zip |
然后在 OPPO 健康里点一次「同步/应用到手表」。watcher 日志出现:
1 | replaced size=341427 |
就说明替换发生在读取之前,手表拿到的是改色包。
六、结果
改色前后对比:


主屏和抬腕/AOD 的日期、星期、秒数都变成了白色,背景保持纯黑,版式和原来一致。
七、限制
主屏的版式(比如去掉 24H、把星期移到秒上方、日期改成上下排列、字号加大)在 manifest.json 里改起来很简单。但抬腕/AOD 用的是 MCU 的 style0.bin,这是编译后的显示列表:
- 没有公开的编译器/反编译器
- 不能从
manifest.json重新生成 - 手工改颜色可以,改坐标/字号/文本结构成本很高
如果只改主屏,就会出现主屏和 AOD 版式不一致。所以这次只做了颜色,版式保持原样,两边看起来一致。对「只想看清时间」来说,已经够了。
OPPO Watch X3 Mini 表盘魔改过程记录
https://www.imaegoo.com/2026/oppo-watch-x3-mini-watchface-patch/


