WxPicLabe

WxPicLabe

微信 · 发图功能扩展模块(非官方,与腾讯及其关联方无任何隶属、合作或授权关系)

Make WeChat Picture Great Again!

版本 CI 源码仓库 问题反馈 Android LSPosed License


这是什么

扩展 微信 发图能力的 LSPosed 模块。

微信一次选多张图时,默认是「一堆图先落地、字再补」,或者干脆没有字。模块接管相册的批量发送入口,改成按选图顺序逐张发:每张图真正发出去之后紧跟一条文字 —— 默认就是这张图的文件名,也可以用「重命名规则」改写成任意文字 —— 形成 图-文-图-文 交替;哪些图补文字、哪些只发图,由「文字过滤」的黑白名单决定。

功能以模块 App「功能」页为准:

功能 说明
增加 文件名 开关 功能页总开关(出厂关闭);相册底栏原图右边还有同款「文件名」开关,不勾就退回微信原生发送
重命名规则 一行一条 原名 -> 新名,可导入导出、可复制粘贴;导入时按原名就地合并,同名的跳过
重复图片追加次数 同名图片再次发送时在文字后追加 +2、+3…;只累计设定时段内发出的图,默认全天
发送记录 逐条台账可搜索、按时间段分组;能改文件名、改「第几次」、删条目,清空时连 +N 计数一起清零
发送提示 发送期间用系统 Toast 显示「正在发送 3/12」、屏幕保持常亮,整批发完自动解除;进度与常亮各一个开关
发送范围 仅勾选的会话生效(默认关闭 = 所有会话都带文件名),名单外的会话退回微信原生发送
文字过滤 黑白名单:命中与否决定这张图带不带文字,图片照原顺序发;关键字支持子串与 * ? 通配

不联网、不上传、不申请任何 Android 权限,只改变发送时的消息组织方式,不修改聊天记录与账号数据。

环境要求

  • 安卓 8.1+(minSdk 27)
  • 较新的 LSPosed(现代 Xposed API 102,minApiVersion 101)。过旧的 LSPosed、太极 / LSPatch / EdXposed 等实现会静默不加载(已启用但零日志、零生效)
  • 微信不锁版本:模块按类名与字段特征定位,宿主升级后可能需要校准 Hook 点(见 Issues 与 排错手册)

安装

  1. Releases 下载最新 APK 并安装
  2. LSPosed 管理器 → 模块,勾选「WxPicLabel」
  3. 作用域里勾上微信(模块已静态声明默认作用域,一般会自动勾好)
  4. 强制停止微信,再打开一次
  5. 打开模块 App → 功能,打开「增加 文件名 开关」(出厂默认关闭)
  6. 相册选图发送;LSPosed 日志过滤 tag WxPicLabel,看到 takeover: intercepted 说明接管生效

从源码构建

在 Windows 仓根执行 build.cmd,默认出 release 包(开 R8,实机与日常测试都装这一个);debug 变体自 2026-11-13 起同样关 debuggable + 开 R8,实机不再掉帧,要真正可调试的包再显式退回:

build.cmd          :: release(默认),产物就是产品包
build.cmd debug    :: debug,与 release 同口径构建(R8 + 关 debuggable)
gradlew :app:assembleDebug -Pwx.debuggable=true -Pwx.minifyDebug=false   :: 真·可调试包

产物分别是 app\build\outputs\apk\release\app-release.apk 与 app\build\outputs\apk\debug\app-debug.apk;缓存重定向到工程内、SDK 解析顺序与版本钉死见 docs/BUILD.md。

反馈

遇到问题、想让某个页面更顺手,到 Issues 反馈。请带上:

  • 微信版本号(微信 → 我 → 设置 → 关于)
  • 模块版本号、安卓版本、机型
  • 复现步骤,以及预期为什么样、实际什么样
  • 日志:adb shell su -c "grep WxPicLabel /data/adb/lspd/log/modules_*.log | tail -50"

免责声明

本项目仅供学习与个人使用。

  • 不收集、不上传、不存储任何个人信息,不含统计分析、广告或数据回传组件
  • 不修改微信安装包、不绕过任何鉴权,只改变发送时的消息组织方式
  • 使用第三方模块可能违反微信用户协议并带来账号风险,相关后果由使用者自行承担
  • 完全免费开源,不直接或间接获取经济利益

「微信」「WeChat」商标与标识的权利归腾讯及其关联方所有。本项目与之不存在任何隶属、合作、授权、认可或支持关系。

完整声明见 docs/DISCLAIMER.md。

开源鸣谢

本模块建立在这些开源项目之上:

项目 用途
LSPosed · libxposed 模块框架与现代 Xposed API(API 102)
Miuix 设置界面的 UI 组件库(小米 HyperOS 风格 Compose)
AndroidLiquidGlass(io.github.kyant0:backdrop) 液态玻璃顶栏 / 悬浮底栏 / 弹窗的折射与模糊效果
Jetpack Compose · AndroidX 全部界面实现的基础设施
Kotlin 模块与设置界面的实现语言
Material Icons 界面图标的矢量路径数据来源(按 path 自绘,不引入图标库)
HyperIsland 关于页的布局与动效来源(MIT,非代码依赖,实现为本项目自写)

许可证

本项目以 Apache License 2.0 发布。 第三方组件的版权与许可证声明见 NOTICE(随 APK 分发,模块 App「关于 → 第三方组件声明」为同一份内容)。

Releases

v1.0.0

Stable

11/14/2026, 12:40:59 PM

这一版是发送链路的一次重构升级:发图热路径、接管批次上下文与兜底队列按新结构重写,跨进程回传协议收口到单一常量源,连发两批、钩子失败、通配规则写歪都不再丢文字或把微信带崩。

遇到问题、想要新功能,到 Issues 反馈 —— 发不出字、丢文件名请附上模块版本号、日志(设置页 → 日志 → 导出日志),卡顿请附 adb shell dumpsys gfxinfo com.tencent.mm framestats。

本版内容

发送 / 稳定性

  • 重构 发图热路径不再整份拷贝计数台账:RepeatCounter 原来每张图要读 4 次 SharedPreferences.getAll()(每次把 300+ 条台账与 600 条事件全拷成新 map 再取一个键,连发 50 张约 7MB 临时对象),trimEventsIfNeeded 还会把 ≤600 条事件无条件解析成 Event;现在读写直连 getString / getLong / getBoolean(类型写错仍回落原实现),裁剪改成计数短路、每 16 次才真扫一遍。
  • 重构 接管批次改成「每批一份入口上下文 + CAS 独占位」:原来入口 Method / this / 参数存在三个跨批共享的 volatile 字段里,第二张图并发拦进来会覆盖上一批的入口(重放成别人的图);现在每批建 ReplayCtx 挂 ThreadLocal,抢不到独占位就转兜底队列排队补发,不丢文字。
  • 优化 拦截线程不再等跨进程 binder 读配置:3s TTL 到期的重附着改 hop 到 wxpiclabel-poller 线程,本次调用继续用旧快照(拿不到就沿用上一次,仍 fail-closed);作用域名单按「附着时间戳 + key」缓存,labelOf 去掉每张图一次 new File(path).getName(),Uri 解析加 64 项 LruCache。
  • 修复 单张图静默丢文件名:step() 在锁内 peek 到空队列、却在锁外把 draining 置回 false,主干看到 draining == true 不再调度,sendLabel 白等 10 秒后按失败收场;现在「空队列 → 关 draining」在同一临界区。
  • 修复 钩子可能把微信带崩或永久失效:on5.f#a 从不校验返回值,某版本不是 void 就让宿主拆箱 NPE;markHooked 又全部写在 hook() 之前,类没找到也照样标记、之后永不再试(相册底栏开关就此消失)。现在非 void 直接跳过并打日志,标记一律后置到挂载成功之后。
  • 修复 连发两批图时兜底队列串批 / 吞文字:pendingDrafts 只有一份全局状态,三处各自整表替换,补发到第 i 格时被换成新批、新批前 i 格被当成「已发过」跳过;现在消费前校验「手里这份还是不是当前表」,新批追加队尾不重置序号,批末不再清别人的队列。
  • 修复 随手填的通配规则能卡死微信进程:*a*a*a*a*b 这类形状让正则指数回溯,现在改成双指针 + 回溯点匹配(最坏 O(长度 × 星号数)),文本侧大小写折叠每段只做一次。

桥协议 / 诊断

  • 新增 跨进程回传协议收口到 core/BridgeProtocol.kt 单一常量源,并加只读探针 /status(schema / 模块版本 / features / 两条回传的时间与规模共八列);records 与 friends 载荷统一带 v 与 at,读侧遇到更高 v 走宽松解析并标 unsupported 而不是猜结构。好友回传补上退避重试,分片 insert 返回 null 就整份放弃,末片改按前缀清扫残留分片。
  • 修复 日志导出的 StringBuilder 并发写:读线程拼一段、调用线程 toString(),join(1000) 超时后读线程仍在 append,导出内容会被截断;现在读写同一把锁。

界面

  • 优化 发送记录页从 verticalScroll + 双层 for 换成 LazyColumn 扁平行:600 条台账原来首帧就把每一行都组合、测量一遍(每行还各挂一个 remember 锚点 + onGloballyPositioned),现在只组合可见的十几行;组卡横跨多个 item,按「首段圆上两角 / 中段直边 / 末段圆下两角」逐段自绘,与整卡逐像素一致。
  • 优化 UI 帧路径减分配:概览卡光场每帧的两次 map + listOf + forEachIndexed 换成局部 val 与直调,卡片滚出屏幕或窗口不在 STARTED 时不再累加时间、不触发重绘。

文档

  • 文档 docs/VERSIONING.md §1.1 补「一句版本定位」口径:标题仍禁止堆功能清单,但大版本可以写成 v1.0.0 · 功能重构升级。

需要 root + LSPosed(支持 API 102),微信 8.0.x / Android 8.1+。
安装步骤见 README。

Assets

1