v2.2.1 更新说明: - 新增 xposed-module(Telegram/微信/SQLite Hook),双 APK + LSPosed 作用域 - HookMessageReceiver 后台直接 DebugForwarder + goAsync,修复 notiMessage 退后台丢消息 - MessageLogStore 日志持久化;AppConfig 调试/上传开关;PC 调试台 debug-server - 健康检查去掉联网限制;通知/Hook 通道增加诊断日志 - 安装脚本 install-full/configure-lsposed/start-debug-server;文档 CHANGELOG + HOOK_GUIDE
11 KiB
消息抓取架构与 Hook 扩展指南
本文说明 notiMessage 的双通道抓取机制、Telegram 的实现方式,以及日后接入其他 App 的步骤。
1. 总体架构
notiMessage 使用 两条独立通道 抓取消息,互为补充:
┌─────────────────────────────────────────────────────────────────┐
│ 目标 App(如 Telegram) │
└───────────────┬─────────────────────────────┬───────────────────┘
│ │
后台/锁屏弹系统通知 前台打开 App、消息入库/解码
│ │
▼ ▼
NotificationListenerService Xposed Hook 模块
(NotificationService) (xposed-module)
│ │
│ │ Broadcast
│ ▼
│ HookMessageReceiver
│ │ goAsync()
│ ├─ MessageLogStore(立即写入)
│ ├─ DebugForwarder(直接转发 PC)
│ └─ submitFromHook(仅正式上传模式)
│ │
└──────────────┬──────────────┘
▼
NotificationService.submitNotification()(通知通道)
│
┌────────────────┼────────────────┐
▼ ▼ ▼
MessageLogStore DebugForwarder (可选)正式后端上传
(App 本地日志) (PC 调试台) AppConfig.ENABLE_SERVER_UPLOAD
| 通道 | 触发条件 | 代码入口 | 日志前缀 |
|---|---|---|---|
| 通知监听 | 目标 App 在后台且弹出系统通知 | NotificationService.onNotificationPosted() |
无前缀 |
| Hook | 目标 App 进程收到新消息(通常在前台) | HookMessageReceiver → 直接 DebugForwarder |
[Hook/xposed_telegram] 等 |
重要:仅开通知监听时,目标 App 在前台通常 不会 产生系统通知,因此抓不到消息。要覆盖前台场景,必须启用 Xposed Hook。
2. Telegram 实现方式
2.1 包名与作用域
Pixel 6 等设备上 Telegram 常见包名:
| 安装来源 | 包名 |
|---|---|
| Play / 官网 APK | org.telegram.messenger |
| 部分渠道 / Web 版 | org.telegram.messenger.web |
LSPosed 作用域需勾选 目标 Telegram 包名 + notiMessage 主 App。
默认作用域见 xposed-module/src/main/res/values/arrays.xml。
2.2 为何不 Hook SQLite?
Telegram 消息存在 SQLite 的 data 字段(加密二进制 TL 序列化),不是明文 content/text。
通用 SqliteMessageHook 只能读明文列,对 Telegram 无效。因此单独实现 TelegramMessageHook。
2.3 Hook 点
主路径:Hook NotificationCenter.postNotificationName(int, Object[])
- 读取静态字段
NotificationCenter.didReceiveNewMessages作为事件 ID - 当
id == didReceiveNewMessages时,从参数里的List<MessageObject>取新消息 - 只处理 非 outgoing(
messageOwner.out == false)的消息
兜底路径:Hook MessageObject 全部构造函数(主路径失败时启用)
源码位置:xposed-module/.../hook/TelegramMessageHook.java
2.4 字段提取逻辑
| 字段 | 提取方式 | 用途 |
|---|---|---|
| 群名/会话名 | MessagesController.getPeerTitle(dialogId) → MessageObject.getName() → getChat().title |
分组标题、转发 group |
| 发送者 | MessageObject.getFromName() |
群内消息前缀 发送者: 内容 |
| 正文 | messageText → messageOwner.message → caption |
文本内容 |
| 图片+说明 | 识别 [图片] 等媒体标签 + 合并 caption |
避免只显示「图片」丢失说明 |
| dialogId | MessageObject.getDialogId() |
去重键 dialogId:messageId |
注意:不要把 messageOwner.from_id(TLRPC$TL_peerUser 对象)直接 toString() 当标题,会出现 TLRPC$TL_peerUser@xxxx。
2.5 转发到主 App
Hook 模块通过 HookForwarder 发送广播:
Action: com.miraclegarden.smsmessage.action.HOOK_MESSAGE
Package: com.miraclegarden.smsmessage
Extras: packageName, title, content, timestamp, source=xposed_telegram
主 App 的 HookMessageReceiver 接收后:
- 检查该
packageName是否在监听列表(App.getMessageByNotiList) - 写本地日志(
MessageLogStore,立即持久化) - 直接转发 PC(
DebugForwarder,不依赖startService,后台可用) - 若开启正式上传(
ENABLE_SERVER_UPLOAD),再经submitFromHook交给NotificationService
3. 主 App 相关模块
| 文件 | 职责 |
|---|---|
NotificationService.java |
通知监听 + 统一提交入口 |
HookMessageReceiver.java |
接收 Xposed 广播 |
MessageLogStore.java |
日志持久化(后台也能保留历史) |
DebugForwarder.java |
POST 到 PC 调试服务 |
AppConfig.java |
ENABLE_DEBUG_FORWARD、ENABLE_SERVER_UPLOAD 开关 |
PC 调试台
# 启动本地服务(含 adb reverse)
powershell -ExecutionPolicy Bypass -File scripts\start-debug-server.ps1
# 浏览器打开 http://127.0.0.1:8765 ,按群/会话分组展示
4. 日后接入其他 App 的步骤
4.1 先判断用哪种 Hook 策略
目标 App 前台消息是否需要抓取?
├─ 否 → 仅通知监听即可,在 App「监听设置」里添加包名
└─ 是 → 需要 Xposed Hook
│
├─ 消息以明文写入 SQLite(content/text/body 等)?
│ └─ 是 → 可复用 SqliteMessageHook(默认兜底)
│
├─ 微信?
│ └─ 是 → 已有 WeChatMessageHook(Hook WCDB message 表)
│
└─ 其他(Telegram、WhatsApp、银行 App 等)
└─ 需编写 **专用 Hook 类**
4.2 新增专用 Hook 的标准流程
以新 App com.example.chat 为例:
步骤 1:分析目标 App
- 用
adb shell pm path <包名>拉出 APK - jadx / dexdump 找消息解码后的类(类似 Telegram 的
MessageObject) - 确认:消息明文出现在哪个字段、哪个时机(构造函数 / 事件总线 / 通知回调)
步骤 2:编写 Hook 类
在 xposed-module/src/main/java/.../hook/ 新建 ExampleChatMessageHook.java:
public final class ExampleChatMessageHook {
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
// 1. findClass + findAndHookMethod / hookAllConstructors
// 2. 提取 title(群名)、content(正文)、过滤 outgoing/系统消息
// 3. 去重(messageId + dialogId)
// 4. HookForwarder.forward(context, lpparam.packageName, title, content, "xposed_example");
}
}
在 HookBridge.java 增加 source 常量,例如 SOURCE_XPOSED_EXAMPLE = "xposed_example"。
步骤 3:注册到 MainHook
编辑 MainHook.java:
if ("com.example.chat".equals(lpparam.packageName)) {
ExampleChatMessageHook.install(lpparam);
return;
}
步骤 4:更新 LSPosed 作用域
编辑 xposed-module/src/main/res/values/arrays.xml:
<item>com.example.chat</item>
步骤 5:主 App 添加监听
在 notiMessage「监听设置」中添加该 App(或通过 shared_prefs/server.xml 的 saveNotiList)。
bankInfoId 本地调试可用 local:<包名>。
步骤 6:构建、安装、验证
# 完整安装(主 App + Xposed + LSPosed 作用域)
powershell -ExecutionPolicy Bypass -File scripts\install-full.ps1
验证清单:
- LSPosed 中模块已启用,作用域勾选 目标 App + notiMessage
- 强制停止并重启目标 App(让 Hook 重新加载)
- notiMessage 监听控制台「开始监听」
- 后台场景:收消息 → 通知通道有日志
- 前台场景:收消息 →
[Hook/xxx]日志 + PC 调试台有数据 adb logcat | grep notiMessageHook可见installed for <包名>
4.3 复用通用 SqliteMessageHook 的条件
SqliteMessageHook 会在 MainHook 中作为 默认兜底 安装(非微信、非 Telegram 时)。
适用 App 特征:
- 使用标准
SQLiteDatabase/FrameworkSQLiteDatabase - 表名含
message/messages ContentValues中有明文列:content、text、body、msg等
不适用:Telegram、WhatsApp、Signal 等端到端加密或二进制 data 列的 App。
4.4 已有专用 Hook 参考
| App | 类 | Hook 目标 |
|---|---|---|
| 微信 | WeChatMessageHook |
com.tencent.wcdb.database.SQLiteDatabase insert,message 表 |
| Telegram | TelegramMessageHook |
NotificationCenter.didReceiveNewMessages + MessageObject |
| 其他 | SqliteMessageHook |
通用 SQLite insert 兜底 |
5. 构建与安装
| 脚本 | 用途 |
|---|---|
scripts/build-debug.ps1 |
构建主 App + Xposed 双 APK |
scripts/install-debug.ps1 |
仅安装双 APK |
scripts/install-full.ps1 |
推荐:构建 + 双 APK + LSPosed 作用域 + adb reverse + 电池白名单 |
scripts/start-debug-server.ps1 |
启动 PC 调试台 |
注意:Android Studio 点 Run 只安装 :app 主模块,不会安装 Xposed 模块。完整功能必须用 install-full.ps1 或手动安装两个 APK。
6. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 前台有消息,App 无日志 | 仅通知通道,Hook 未生效 | 检查 LSPosed 作用域、重启目标 App |
标题显示 TLRPC$... |
误把对象当字符串 | 用 getPeerTitle 等 API,见 TelegramMessageHook |
| 图片消息只有「图片」 | 说明在 caption 字段 | 合并 caption + 媒体标签 |
| PC 有数据,App 无历史 | 旧版日志未持久化 | 已用 MessageLogStore 修复,更新主 App |
Hook 日志前缀 [Hook/...] 无 |
Xposed 未注入 | 确认 Magisk + LSPosed + 模块启用 |
7. 相关文件索引
xposed-module/
├── src/main/assets/xposed_init # 入口 MainHook
├── src/main/java/.../MainHook.java # 包名路由
├── src/main/java/.../HookForwarder.java # 广播转发
├── src/main/java/.../hook/
│ ├── TelegramMessageHook.java
│ ├── WeChatMessageHook.java
│ └── SqliteMessageHook.java
└── src/main/res/values/arrays.xml # xposed_scope 默认作用域
app/
├── src/main/java/.../service/
│ ├── NotificationService.java
│ └── HookMessageReceiver.java
├── src/main/java/.../MessageLogStore.java
├── src/main/java/.../network/DebugForwarder.java
└── src/main/java/.../AppConfig.java
debug-server/server.py # PC 调试台
scripts/install-full.ps1 # 一键完整部署