资讯动态

Android点对点彩信发送源码实战:从PDU构建到MMSC直连

发布时间:2026/9/16 7:40:31 来源:尧图企业网站定制
简介这是一份面向Android中级开发者的点对点彩信发送功能实现源码来自Android高级应用实战场景集中展示MMS协议栈在移动端的落地方法。压缩包共198个文件约602KB以Java源码与编译生成的class文件为主同时包含工程配置文件project/classpath、界面与图标资源png/jpg/xml、打包产物APK以及PduParser、PduComposer、PduPersister等彩信协议处理核心类适合研究系统级彩信发送流程。目前已有213人学习下载具备一定参考热度。借助这份代码读者可以梳理从Pdu构造、Headers解析到发送状态持久化的完整链路并通过可运行APK进行真机或模拟器验证快速掌握Android彩信业务的关键实现细节。1. 点对点彩信源码在 Android 里到底解决什么问题“点对点发送彩信源码”这类包里装的并不是用系统发件箱点一下发送那么简单。它通常是指应用自己拿到 MMSC运营商彩信中心地址把 MMS PDU 按协议拼好通过 HTTP POST 直接发给服务器。这样绕过系统短信应用让应用在后台、无界面、甚至设备被锁屏时都能完成一条点到点的彩信投递。与其说它是一段发送代码不如说是一套完整的 MMS 协议实现PDU 构建、binary headers、multipart 编码、状态报告解析这些才是源码里值钱的部分。这个标题对应的人群主要有两类一类是做物联网或车机设备需要定时上报图片或位置信息另一类是做 IM 类应用想复用运营商彩信通道做兜底通知。你要是不理解 MMS 的 PDU 格式拿到源码也改不动参数。所以这篇文章顺着协议和源码把点对点发送这条路讲透包括参数怎么调、响应码怎么判断、哪些 API 是老的也不能依赖。2. MMS 点对点发送的协议基础与 Android 实现路径2.1 彩信不是“发短信”它是压缩过的 HTTP 载荷MMSMultimedia Messaging Service的底层不是短信的 SMPP而是基于 WAP 的传输。常见做法是把 MMS 消息封装成一个 PDUProtocol Data Unit通过 HTTP 1.1 POST 发送到 MMSC 的地址。真正传图片、音频时PDU 里面包的是 MIME multipart其中包含了 SMIL 演示文件、图片、文本片段还有控制头X-Mms-Message-Type、From、To、Date 等。点对点发送的玩法就是从普通短信网关出发往收发双方之外再引一条数据通道。协议决定了很多表面行为彩信“发送成功”不代表用户“已读”甚至不代表 MMSC 已经投递到对方手机MMS 的大小限制由运营商决定编码时如果不对图片做压缩手机会直接拒收发送方手机号由 MMSC 从 HTTP 请求的来源或 PDU 的 From 字段判断应用层伪造往往会被服务器拦截。理解这一点之后源码里真正需要改的就不是“发送”那一行代码而是 PDU 的头部字段和 multipart 的构造方式。2.2 Android 上的两条路线系统 Intent 与直连 MMSCAndroid 系统从早期就提供了两条发彩信的路子。第一条是 Intent 方式构造一个Intent.ACTION_SENDtype 设为image/*然后让用户选择系统或第三方短信应用去发送。这个方式的缺点很明确——Android 4.4API 19之后非默认短信应用无法直接往系统彩信数据库写记录发送过程会被弹窗打断也不适合无人值守的设备。第二条是绕过系统短信应用直接拿 MMSC 地址发 HTTP 请求。源码包通常走的是这条路。直连 MMSC 需要解决三件事拿到当前 SIM 卡对应的 MMSC 地址、MMS 代理、端口自己构建 MMS PDU并正确处理 PDU 头部的二进制字段处理 HTTP 响应中的 M-Send.conf 状态码以及后续可能的 M-Notify.ind 通知。Android 的TelephonyManager在早期版本里提供了 API 可以取到 MMSC但因为权限收紧高版本上已经不太好用。因此大部分高级源码包的做法是提供一个可配置的 MMSC 地址发送时从配置文件读取而不是依赖系统 API 去枚举。2.3 源码里最常用的几个 MMS 参数下面这些参数是点对点发送源码里必然出现的直接决定发送成败参数示例值说明MMSC 地址http://mmsc.monternet.com运营商彩信中心每张 SIM 卡对应一个MMS 代理空 / 10.0.0.200部分运营商要求走代理才能发彩信端口80 / 8080与代理配套User-AgentAndroid-MMS/1.0很多 MMSC 会校验 UAContent-Typeapplication/vnd.wap.mms-messageMMS PDU 的固定类型X-Mms-Message-Typem-send-req表示这是一条发送请求提示拿到源码后第一步就去看这 6 个参数存在哪里是硬编码在 Java 文件里、写在 XML 配置里还是运行时从CarrierConfig拼出来的。这决定了你的代码换 SIM 卡之后还能不能正常工作。3. 用源码里的核心类在 Android Studio 跑通最小发送链路3.1 最小化的工程配置创建一个干净的 Android 工程不需要引入额外的 MMS 库大多数老源码用的就是纯 Java 的HttpURLConnection加用手工字节数组拼 PDU。build.gradle里需要注意android { compileSdk 33 defaultConfig { minSdk 19 targetSdk 28 } } dependencies { // 不需要额外依赖MMS 协议栈用 JDK 自带类实现 }参数说明minSdk 19是因为 Android 4.4 之后系统短信权限收紧老源码大多从这一版开始适配targetSdk 28规避了高版本对后台服务和隐式广播的限制让应用的发送逻辑能够一直活着。如果你要升级 targetSdk 到 29 以上需要额外处理后台运行时限制否则锁屏后发送进程会被系统冻结。权限只需要两个uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.READ_PHONE_STATE /READ_PHONE_STATE用于读取本机号码实际上很多 MMSC 不校验 From 字段所以这个权限主要用于兼容老代码。3.2 构建 MMS PDU核心是字节级别的二进制头老源码里最值得读的部分是 PDU 构造器。下面是一段剥掉业务逻辑后的最小实现以 M-Send.req 为例public byte[] buildSendReqPdu(String to, String subject, byte[] imageData) throws IOException { ByteArrayOutputStream header new ByteArrayOutputStream(); ByteArrayOutputStream body new ByteArrayOutputStream(); // Begin 标记 header.write(0x80); // application/vnd.wap.mms-message header.write(0x02); // m-send-req // X-Mms-Transaction-Id 随机字符串 String txId tx System.currentTimeMillis(); header.write(0x98); // X-Mms-Transaction-Id field header.write(encodeTextString(txId)); // X-Mms-Version: 1.0 header.write(0x99); header.write(0x10); // Date 时间戳 header.write(0x85); header.write(encodeLongInteger(System.currentTimeMillis() / 1000)); // From: 取配置的发送号码 header.write(0x89); // From field header.write(0x80); // address-present-token header.write(encodeTextString(senderNumber)); // To header.write(0x97); // To field header.write(encodeTextString(to)); // Content-Type: application/vnd.wap.multipart.related header.write(0x84); header.write(0x80); // multipart/related body.write(0x80); // SMIL part start ... return concat(header.toByteArray(), body.toByteArray()); }这段代码的逻辑说明0x80是 MMS 头里常用的“短字节”标记凡是以 0x80 开头的字段都表示一个简短的已知值不需要字符串长度0x98、0x99是字段 ID对应 M-Send.req 的 transaction-id 和 version编码时顺序不能乱MMSC 对字段顺序不是特别严格但 header 里Content-Type必须在To之后否则部分老旧的 MMSC 会解析失败encodeTextString是老源码里都会有的一个工具方法第一个字节是字符串长度后面跟 UTF-8 字节最后以 0x00 结尾。参数怎么改如果你想加附件改Content-Type为application/vnd.wap.multipart.mixed然后把图片和文本按 MIME 格式顺序写入body即可。大多数源码用image/jpeg不要用 PNG体积大且部分 MMSC 对长彩信支持差。3.3 通过 HttpURLConnection 把 PDU POST 到 MMSCPDU 组装完成后网络栈反而是最朴素的。常见写法如下private int sendPduToMmsc(byte[] pdu, String mmscUrl) { HttpURLConnection conn (HttpURLConnection) new URL(mmscUrl).openConnection(); conn.setRequestMethod(POST); conn.setRequestProperty(Content-Type, application/vnd.wap.mms-message); conn.setRequestProperty(User-Agent, Android-MMS/1.0); conn.setRequestProperty(Accept, */*); conn.setConnectTimeout(15000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.getOutputStream().write(pdu); int code conn.getResponseCode(); conn.disconnect(); return code; }这段代码的逻辑说明Content-Type不能写application/json或text/plainMMSC 不认User-Agent 可以改写成你的应用标识但开头建议保留Android-MMS/前缀部分网关会做白名单匹配超时时间连接 15 秒、读取 30 秒是经过实践的经验值。MMSC 处理 PDU 的时间通常很快慢的是移动网络下初始的 TCP 握手返回值 200 或 201只代表 MMSC 承认收到了请求不代表发送完成。真正的成功要解析响应体的 M-Send.conf 里的 Response-Status200 OK 才是稳妥的成功。注意拿到源码后不要直接改用 OkHttp。MMS PDU 是二进制流OkHttp 默认会做 gzip 解压和 charset 处理容易把二进制头搞坏。HttpURLConnection默认不做任何转换恰好是发送 MMS 最安全的客户端。3.4 发送时不能放在主线程源码里一般怎么处理老源码通常直接开一个Thread或者用AsyncTask。现在推荐写法是扔给ExecutorServiceExecutorService executor Executors.newSingleThreadExecutor(); executor.execute(() - { try { byte[] pdu buildSendReqPdu(to, subject, imageData); int code sendPduToMmsc(pdu, mmscUrl); if (code 200 || code 201) { // 写入发件箱标记为“已提交到MMSC” } } catch (IOException e) { // 重试逻辑 } });这样你就有了一个能独立跑通的最小链路。源码包里那些MmsSender.java、PduBuilder.java之类的文件本质上就是上面这些代码的工程化扩展加了联系人解析、缩略图生成、失败重试和数据库记录。4. 点对点发送的状态报告、重试策略与真机坑位4.1 根据响应码判断发送状态而不是只看异常发送链路最容易踩的坑是把网络异常当作发送失败把 HTTP 200 当作发送成功。MMSC 返回的 HTTP 状态码和 PDU 里的Response-Status不是一个东西。下表是源码里必须能识别的状态HTTP 状态M-Send.conf 中的 Response-Status实际含义200/201200 (OK)MMSC 已接收后续按正常流程投递200/201401 (Error permanent)地址格式错误或鉴权失败重试无意义200/201407 (Error networking)发送方无权限可能是号码没开通彩信4xx-HTTP 层错误多半是 UA 或 Content-Type 不对5xx-MMSC 临时故障可等待后重试老源码里常见的做法是取响应体解析出X-Mms-Response-Status字段然后跟一个常量表做比对。你在源码里搜MmsResponseStatus或STATUS_OK这个常量如果存在就说明作者已经处理了这层逻辑。4.2 重试策略要带退避否则会被 MMSC 临时封停点对点发送最常见的场景是信号弱、网络切换、MMSC 繁忙。源码里如果只有while (true) { send(); }这种重试上线后一定会出问题。我一般建议至少做到两点指数退避第一次失败等 30 秒第二次 60 秒第三次 120 秒最多重试 4 次transaction-id 要更新PduBuilder 里每次重试时重新生成 transaction-id否则 MMSC 会认为是重复消息直接丢弃。int maxRetries 4; int delay 30_000; for (int i 0; i maxRetries; i) { byte[] pdu buildSendReqPdu(to, subject, imageData); int responseStatus sendPduToMmsc(pdu, mmscUrl); if (isSuccess(responseStatus)) { break; } Thread.sleep(delay); delay * 2; }这样做的参数意义30 秒的初始间隔足够避开大多数 MMSC 的瞬时拥塞重试 4 次总时长约 7.5 分钟如果这期间网络还没恢复说明是更长时段的故障继续重试只会徒增电池消耗和被封风险。4.3 双卡、漫游和切换网络时 MMSC 会变直连 MMSC 的源码最讨厌的就是搞死 MMSC 地址。你用中国移动的卡写死mmsc.monternet.com插到中国联通卡上就发不出去。这个问题在双卡手机上尤其严重因为你不能简单说“取当前网络”要确认的是“发送用的数据网络是哪张卡”。老源码很少处理这种情况因为早期安卓手机几乎都是单卡。你要做多卡适配常见方案是读取SubscriptionManager拿到当前默认可上网的卡从这张卡的CarrierConfig里动态取 MMSC如果拿不到就回退到配置文件里的默认值。4.4 系统短信数据库与自建发送记录有些源码会把发送记录写进系统content://mms数据库方便用户在短信应用里看到。但 Android 4.4 之后非默认短信应用没有权限写入这个库。如果你看到源码里包含对content://mms的 insert 调用说明这套代码很可能来自老版本系统或者需要 root 权限。替代做法是建立自己的发送记录表把 to、subject、image_path、send_result、response_status、ts 等字段存到本地 SQLite。这样既不影响系统也更方便自查问题。字段类型说明toTEXT接收号码tx_idTEXT每次发送的唯一标识send_timeINTEGER发送时间戳result_codeINTEGER发送结果码retry_countINTEGER已重试次数4.5 锁屏清理与后台限制就算发送逻辑全对Android 9 及以后的系统还会把应用进程在锁屏一段时间后杀死。源码里如果用了前台服务但不显示通知会被系统直接标记为“异常”。那点对点发送怎么保证存活呢常见做法用WorkManager或AlarmManager定时检查“待发送”队列。当队列里有积压消息时再拉起一个前台服务发送完成立即 stop。这比持续保活要健康得多。提示如果发现源码里有START_STICKY、setForground注意拼写满天飞的写法别迷信那是 Android 8 之前的保活方案现在反而容易被系统盯上。5. 点对点发送源码改造为可维护工具的重点拿到一套能发送的点对点彩信源码后最实际的验收方法不是写文档而是造一条真消息看全链路从发件箱记录、到 MMSC 响应、再到对方手机收到用抓包工具确认请求中的X-Mms-Message-Type和User-Agent没有异常。如果想继续增强核心有两个方向第一把 PDU 构建器做成独立的纯 Java Module方便单测断言每个字节的含义第二在上层加一个发送队列把“发送动作”和“业务触发”解耦。设备重启、SIM 卡更换、网络恢复这三个时机都应该自动补发队列里积压的彩信。源码里最容易被忽略又最影响线上效果的是 SMIL 模板。多点对点发送的源码如果不会生成 SMIL只能带单张图片会生成 SMIL 的才能自由组合文字、图片、音频的播放顺序。改一个smil字符串常量往往比改十行发送代码更贴近业务需要。最后养成一个习惯每次发送失败都保留原始响应体不要只存状态码。Response-Status旁边的Status-Text字段往往带着 MMSC 返回的明文错误原因那才是排障的第一手线索。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价