
简介一份基于Android平台开发的微信/支付宝收款监控系统源码面向移动端开发者及有个人收款管理需求的用户。项目核心解决个人账户无需单独签约支付接口即可实现即时到账监控的问题通过应用内监听与通知机制辅助收款记录适合自由职业者、小微商户或独立开发者使用。资源共159个文件包含Java源码49个、XML布局与配置37个、PNG图片42个以及JAR依赖库、MP3音频提示和Gradle工程配置压缩包仅2.94MB结构紧凑便于导入Android Studio直接分析。工程涵盖界面展示、支付监听、结果解析等模块并配有属性文件与License声明有助于开发者梳理完整收款监控流程同时图片与音频素材也可直接用于界面和提示音二次定制。目前已有537人学习浏览适合具备一定Android基础、希望快速借鉴实现思路并做功能扩展的工程师参考学习。1. 收款监控系统为什么绕不开 Android 监听端收款监控的核心不是“收钱”而是“在钱到账的瞬间拿到一个可信的事件”。服务端的支付宝/微信回调能精确告诉你交易结果但小微商户、门店播报、多店归集这类场景里收银终端往往不在开发者手里甚至只是店员手机上挂着收款码。这时候Android 端就成了唯一能统一捕获微信和支付宝到账事件的监听点。标题里的“设计源码”其实是在讲一条完整的链路事件源通知栏、无障碍、系统广播→ 中间服务事件解析、金额提取→ 业务出口本地 HTTP 推送、语音播报、上报服务器。这套结构对 5 年以上的工程师来说难点不在某个 API而在怎么在厂商 ROM 的后台限制下保住这条链路的稳定性。2. 广播式监听设计从系统事件到本地 HTTP 推送的链路搭建2.1 为什么先做事件源抽象而不是直接写通知监听微信和支付宝的到账提醒表面上都来自通知栏但两个 App 的推送通道、通知栏展示策略、甚至是否允许读取通知都各不相同。常见做法是先把“事件源”抽象成统一的接口收到通知、收到无障碍节点变化、收到系统广播最终都归一成一个PaymentEvent。这样后续切换监听策略时业务代码不用动只换监听源实现。我一般会在PaymentEvent里保留三个字段type微信/支付宝/未知、amount金额单位分、rawText原始通知文本用于排错。金额单位用分而不是元是因为浮点数在 JSON 序列化和数据库存储里都可能丢精度而且支付宝回调里的total_amount是字符串形式的元换算分的时候要自己处理避免直接Double.parseDouble后乘 100。public class PaymentEvent { public static final int TYPE_WECHAT 1; public static final int TYPE_ALIPAY 2; public int type; public long amount; // 单位分 public String rawText; // 原始通知文本 public long occurredAt; // 事件发生时间戳毫秒 }这段代码本身没有逻辑但它决定了后面所有监听源实现类的返回类型。rawText一定要存因为通知解析的正则规则会频繁调整没有原始文本做回归验证改了规则都没法确认是否误伤。occurredAt用事件发生时间而不是接收时间避免系统通知堆积导致的时间漂移。2.2 用系统广播做兜底触发App 被杀后怎么重新拉起通知监听服务最大的问题是用户手动划掉 App 或厂商清理后台后NotificationListenerService可能被系统解绑但 App 进程还活着业务就进入了“静默失联”状态。兜底方案是动态注册一个BroadcastReceiver监听ACTION_SCREEN_ON、ACTION_USER_PRESENT、ACTION_BOOT_COMPLETED这类高频事件在里面检查监听服务是否仍然连接。下面是几个实践下来值得注册的 actionAction触发时机用途android.intent.action.SCREEN_ON屏幕点亮检查服务存活重建通知通道android.intent.action.USER_PRESENT解锁完成高概率伴随用户查看手机适合做轮询补偿android.intent.action.BOOT_COMPLETED开机完成拉起常驻服务android.net.conn.CONNECTIVITY_CHANGE网络切换补偿上报失败的消息注册方式要用Context.registerReceiver动态注册别写进 Manifest。Android 14targetSdk 34对静态广播接收者做了大量限制SCREEN_ON和USER_PRESENT这类隐式广播在清单注册里根本收不到动态注册是唯一可靠路径。注册时机放在Application.onCreate里和监听服务解绑回调互不干扰。2.3 把解析结果推给业务侧内置 HTTP Server 的端点设计终端设备上的解析结果要送到收银台或后台系统最省事的方式是在 App 里内置一个轻量 HTTP 服务局域网内 POST JSON。避免引入 GRPC 或者自研 TCP 长连接对单机监听场景来说维护成本远大于收益。JDK 自带的com.sun.net.httpserver.HttpServer在 Android 上可用代码量控制在 80 行以内。HttpServer server HttpServer.create(new InetSocketAddress(9527), 0); server.createContext(/payment/notify, exchange - { if (!POST.equals(exchange.getRequestMethod())) { exchange.sendResponseHeaders(405, -1); exchange.close(); return; } String body new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); boolean ok pushService.dispatch(body); // 分发到业务处理链 byte[] resp ok ? {\code\:0}.getBytes() : {\code\:500}.getBytes(); exchange.sendResponseHeaders(200, resp.length); exchange.getResponseBody().write(resp); exchange.close(); }); server.setExecutor(Executors.newFixedThreadPool(4)); server.start();这里的关键参数是端口和线程池。端口要避开微信和支付宝内置浏览器的常见代理端口我一般取 9000 以上的奇数端口。线程池线程数 4 到 8 是经验值监听场景的并发峰值不会超过 10 个请求线程数再多反而浪费内存。注意readAllBytes()在 Java 9 才可用Android 的 desugar 支持有限稳妥写法是手动循环读取。这个端点的dispatch方法内部要做三件事验签、幂等去重、落库。验签用的 token 在 App 和后台各配一份请求头带X-Auth-Token别把 token 放到 URL 参数里因为局域网内网 HTTP 抓包实在太容易了URL 会被各种中间设备记日志。3. NotificationListenerService 实现微信/支付宝到账监听的最小可运行方案3.1 通知栏监听为什么是到账监控的“通用入口”微信和支付宝的收款到账通知最终都会走系统通知栏展示。即使 App 在后台被省电策略限制通知栏消息仍会被系统接管。所以NotificationListenerService是监听渠道里覆盖最广、误报最少的一条路。无障碍服务能看更多东西但需要用户额外开启权限而且被手机厂商杀服务也是常态。需要澄清一个容易误解的点NotificationListenerService拿到的只是通知的内容拿不到 App 内部进程的通信数据。微信把金额显示在通知里我们就能读到如果哪一天微信改成“你有一笔收款到账”不带金额那这条路就废了只能靠无障碍节点逐级遍历界面或者直接放弃金额展示改为语音播报原文。3.2 最小可用的监听服务代码nbsp;class PayNotificationListener : NotificationListenerService() { override fun onNotificationPosted(sbn: StatusBarNotification?) { val extras sbn?.notification?.extras ?: return val title extras.getCharSequence(Notification.EXTRA_TITLE).toString() val text extras.getCharSequence(Notification.EXTRA_TEXT).toString() val app sbn.packageName val amount when { app WECHAT_PACKAGE title.contains(微信支付) - extractAmount(text) app ALIPAY_PACKAGE title.contains(支付宝) - extractAmount(text) else - return } if (amount 0) { val event PaymentEvent(type if (app WECHAT_PACKAGE) TYPE_WECHAT else TYPE_ALIPAY, amount amount, rawText text) dispatcher.enqueue(event) } } private fun extractAmount(text: String): Long { val matcher AMOUNT_REGEX.find(text) ?: return 0 val yuan matcher.groupValues[1].toDoubleOrNull() ?: return 0 return (yuan * 100).roundToLong() } companion object { const val WECHAT_PACKAGE com.tencent.mm const val ALIPAY_PACKAGE com.eg.android.AlipayGphone val AMOUNT_REGEX Regex(收款([0-9]\\.[0-9]{2})元) } }这段代码有四个细节值得讲。第一extras取文本用的是EXTRA_TEXT但部分 ROM 会把通知内容放在EXTRA_TEXT_LINES一个CharSequence[]里只读EXTRA_TEXT会是空所以要加 fallback 逻辑。第二正则收款([0-9]\\.[0-9]{2})元只匹配到分微信的 “收款0.01元” 和 “收款100.00元” 都能命中但如果通知文案改动成 “已收款100.00元”正则就失效了这就是rawText的意义——到时候改正则再跑回归。第三金额乘 100 用roundToLong()避免浮点乘法的精度问题。第四dispatcher.enqueue内部要有一个有界队列队列满时丢弃最早的事件防止内存溢出。3.3 权限配置和通知读取开关要在AndroidManifest.xml里声明服务并配好BIND_NOTIFICATION_LISTENER_SERVICE权限。这个权限是系统签名权限普通 App 必须在系统设置里手动开启“通知使用权”没有代码可以绕过别在这个点上浪费时间。service android:name.PayNotificationListener android:label收款监控服务 android:permissionandroid.permission.BIND_NOTIFICATION_LISTENER_SERVICE intent-filter action android:nameandroid.service.notification.NotificationListenerService / /intent-filter /service配置里android:permission写错了服务会直接无法绑定这是最常见的低级错误。判断用户是否已授权的标准代码是查询NotificationManager.getEnabledListenerPackages()返回的包名列表里存在自己的包名才算开启不要用canUse之类的自定义状态会误导排查。3.4 通知文本特征与金额提取的适配边界微信和支付宝的通知文本结构会随版本调整这里给出一份我整理的适配表标注了稳定场景和失效风险场景场景微信通知特征支付宝通知特征个人收款码到账“微信支付收款到账 12.00 元”“支付宝到账 12.00 元”商家扫码枪“微信支付-xxx店 收款 12.00 元”“向你付款 12.00 元”群收款“群收款- 已收款 12.00 元”无对等场景退款通知“退款到账” 无金额或负数“退款成功” 无金额红包“收到红包” 金额在标题栏无对等场景退款和红包都要单独挡掉。你只监控“收款”但通知里“退款到账”会带正向金额直接匹配就会误报。我一般加一层上下文判断微信的退款通知标题带“退款”支付宝的退款通知标题带“退款成功”命中后直接返回amount 0。红包同理标题有“红包”就不解析。4. 支付宝回调与二维码状态机的集成方式4.1 服务端支付宝回调与 Android 本地监听的职责边界支付宝的异步回调notify_url是服务端的事收到回调说明交易已经真实完成这是账务级凭证。Android 端通知监听只是“播报”级别的事实两者不应混为一谈。实际项目里我会把服务端回调作为最终入账依据Android 监听的结果只做两件事实时语音播报、触发终端界面刷新。这样分工的好处是即使 Android 端因为厂商省电策略漏听了消息服务端回调还能兜底记账。反过来如果服务端宕机Android 端仍然能保证店员先知道钱到账这就是监控系统的核心价值。连接两条链路的介质是订单号out_trade_no扫码支付时终端生成订单号并显示二维码服务端回调带着同一订单号返回Android 端轮询自己的订单接口就能知道“这笔单子到底入账没有”。4.2 关键回调参数与验签字段说明支付宝回调 POST 到开发者服务器的参数中有几个和收款监控直接相关的字段参数名示例用途out_trade_no20250101120001商户订单号关联本地订单trade_no202501012200141234支付宝交易号对账用trade_statusTRADE_SUCCESS交易状态只有这个值才入账total_amount12.00订单金额字符串类型seller_id2088xxxx收款方 PID多门店做归属用signbase64字符串RSA2 签名验签核心trade_status是状态机跳转的核心驱动。WAIT_BUYER_PAY表示等待付款TRADE_SUCCESS表示已付款TRADE_FINISHED表示交易完成且不可退款。收款监控的关注点其实只在WAIT_BUYER_PAY到TRADE_SUCCESS的跳变上TRADE_FINISHED是退款链路才要关心的。4.3 二维码收款的最小状态机代码在终端设备上每张收款码对应一个订单状态变化是“待支付 → 已支付 → 已通知”。这个状态机用枚举加单次流转校验就能写清楚不需要引入 WorkFlow 引擎public enum OrderState { PENDING(待支付), PAID(已支付), NOTIFIED(已通知); private final String desc; OrderState(String desc) { this.desc desc; } public boolean canTransitTo(OrderState target) { return (this PENDING target PAID) || (this PAID target NOTIFIED); } Override public String toString() { return desc; } }状态流转的判断被收敛在canTransitTo里所有入口都必须经过它。这个设计不是为了炫技而是收款链路里的订单状态必须是单向不可逆的——PAID永远不能回到PENDING如果业务上需要撤销应生成逆向退款单而不是改状态。NOTIFIED状态存在的意义是保证“已通知”这个动作的幂等性防止 Android 端重试推送造成重复播报。4.4 轮询二维码状态的实测命令在开发调试阶段不一定要等真钱到账可以用支付宝沙箱环境配合 curl 模拟回调。先在后端接口里加一个 dev-only 的触发端点curl -X POST http://localhost:8080/api/mock/alipay/callback \ -H Content-Type: application/x-www-form-urlencoded \ -d out_trade_no20250101120001trade_statusTRADE_SUCCESStotal_amount12.00seller_id2088123456789012这个请求模拟了支付宝服务器向notify_url发起的回调。注意trade_status参数要严格对应TRADE_SUCCESS和WAIT_BUYER_PAY是两个最常用的测试值。用沙箱调试比用真实账号扫码快得多而且能精确控制金额和状态适合做状态机的单元验证。seller_id在多门店场景下可以多传几组值验证归属逻辑是否正确分流。5. 用 adb 验证监听链路和保活状态的具体技巧5.1 确认 NotificationListenerService 是否被系统解绑命令行直接查当前应用的监听服务绑定状态adb shell dumpsys notification --noredact | grep -A 5 mListeningPackages正常输出里能看到你的包名出现在mListeningPackages如果只有mUserSetPackages有你的包名说明用户在设置里开了开关但服务因异常退出还没重新绑定。接着再查adb shell dumpsys activity services 你的包名输出里的appProcessRecord段会显示进程是否存活intentIntent段会显示flg0x10000000其中0x10000000表示该服务是系统通过BIND_NOTIFICATION_LISTENER_SERVICE权限启动的。两个命令配合使用能快速定位是“没开权限”还是“服务崩了”。5.2 验证通知解析是否生效的快速手段不需要真的收一笔钱用adb直接给微信发一条模拟通知不现实但可以换个思路——把解析逻辑单独抽成纯函数然后写个带 main 的 Java 类在本地跑单元测试。我在项目里会保留一个notify_parser_test.json里面放几十条历史通知原文每次改正则后跑一遍断言。命令如下./gradlew :app:testDebugUnitTest --tests *.PaymentTextParserTest测试通过后再上车真机。真机验证的兜底手段是加一个 debug 页手动粘贴任意通知文本点“解析”输出金额和来源。这样测试时不依赖真实收款事件效率高得多。最后一招直接在onNotificationPosted里加 logcat 输出用adb logcat | grep PaymentListener跟踪实时事件流确认事件是否进入分发队列。5.3 前台服务参数设置的关键点如果项目要求服务更稳可以把监听服务升级成前台服务。startForeground必须传入 channel id 和通知实例Android 12 以后startForeground还要求同时声明FOREGROUND_SERVICE权限并且后台启动前台服务有ForegroundServiceStartNotAllowedException限制。写代码时集中做一层封装避免startForeground被极端场景抛异常try { service.startForeground(NOTIFY_ID, buildNotification(context)); } catch (ForegroundServiceStartNotAllowedException ignored) { // 后台启动受限等待下个系统广播再自启 }不要在这里做复杂的重试逻辑系统广播会再次触发检查过度重试只会增加耗电和崩溃率。保活的最终防线其实是产品层面引导用户把 App 加入厂商的“耗电保护白名单”这一步要放到设置引导页里反复提醒纯靠代码对抗厂商策略是不现实的。本文还有配套的精品资源点击获取