
1. 推送不是玄学先搞懂它到底是什么1.1 推送的本质一条消息如何走进用户的手机做App开发这些年我经常遇到产品经理跑过来问“就给用户发个通知怎么安卓和iOS那边的对接人说法还不一样”这个问题背后其实是很多人对推送的底层机制有误解。你以为推送是App自己跑在后台然后主动弹个通知不是的。推送的本质是一次“服务端到客户端”的消息投递但它走的路跟普通HTTP请求完全不同。普通请求是App主动来找服务器要数据比如你打开淘宝刷新首页那是“拉”的过程。而推送是服务器主动往用户手机上塞一条消息就算App当时没打开、甚至被用户从后台划掉了只要手机系统还允许通知权限消息就能显示出来。这里的关键在于App本身在绝大多数情况下“不参与”消息的展示过程。真正把消息弹到屏幕上的是手机系统。所以在iOS上Apple有一套自己的推送中转站叫APNsApple Push Notification service在Android上虽然有一个Google官方的FCMFirebase Cloud Messaging但国内因为各种原因基本用不上取而代之的是华为、小米、OPPO、vivo这些厂商各自的推送通道。你发的每条消息本质上都是先跑到这些系统通道那里再由系统决定“什么时候、以什么方式”呈现给用户。理解了这一点你再看“推送到达率为什么突然变低”“为什么iOS收不到但Android能收到”这些问题思路就会清晰很多。它不是App代码的问题而是你走的通道、配置、权限、甚至用户习惯共同作用的结果。1.2 为什么国内Android一定要聊“厂商通道”按照Google的设想Android开发者只需要集成FCM就能“一次接入、全球推送”。但在国内的实际环境里FCM时好时坏很多手机根本没有Google服务框架所以FCM这条路基本是堵死的。于是国内主流的方案变成了这样你的App集成极光推送、个推、友盟推送或者腾讯信鸽这类第三方推送SDK然后由这些SDK在内部“智能选择”一条可用的通道。如果App进程还活着就用App自己建立的常连接来收发消息如果App进程已经被系统杀掉、或者处于纯后台状态就必须借助厂商通道——也就是华为Push Kit、小米MiPush、OPPO Push、vivo Push、荣耀Push这些系统级服务。为什么要借厂商通道因为手机厂商把这些通道的定义为“系统级优先级最高”就算App被杀掉了系统依然可以接收服务端发来的消息然后以系统通知栏的形式展示出来。你的App进没进程、死不死亡都不影响这条消息到达。所以做安卓推送的第一课就是只要能接入厂商通道就不要只靠长连接裸奔。对于国内App来说长连接在用户切后台、锁屏、清理内存之后会变得非常脆弱。但这也不意味着直接套一个第三方SDK就完了你还需要去各个厂商的开放平台注册应用、申请消息服务、配置SHA256指纹、上线审核。不同厂商、不同机型、不同系统版本规则还不太一样这部分我后面会专门拆开讲。1.3 iOS的APNs和Android完全不同的“游戏规则”iOS这边相对“省心”因为苹果把通道收得很紧。每个iOS设备在激活时会跟APNs建立一个持续的TLS连接你的App需要从APNs拿到一个device token然后把token传给自己的服务端。之后服务端想推消息就打给APNsAPNs再找到对应的设备弹通知。很多第一次做iOS推送的开发者会困惑一个问题“为什么我服务端推送成功了手机上就是不显示”这种问题多半出在三个地方证书或密钥配置错误、payload格式不正确、或者是App在前台导致通知被吞掉了。尤其是第三条很多人不知道iOSApp在前台运行时默认是不会弹系统通知条幅的你必须在AppDelegate里面实现willPresent notification这个方法手动把内容展示出来。Android这边的代表差异是通知的展示方式、角标数字、通知渠道优先级、厂商通道的送达回执都需要你自己处理。简单说iOS把“消息能到手机”这件事做得很固定你只需要在固定的框架里做对配置Android则是“条条大路能通罗马”但路况随时在变你得自己判断哪条路最稳。2. 从零搭一条推送链路客户端、服务端、消息层2.1 客户端注册Token的完整流程先把最底层的“地基层”讲清楚。无论你用哪种推送服务整个链路的第一步都是让设备获得一个唯一的token标识。以iOS为例流程是这样的App启动后向APNs发起注册APNs会返回一个由设备硬件和应用BundleID共同生成的device token。App再把这个token上报给你自己的后端服务器后端存起来等老师要推送时拿着这个token去调用APNs接口。必须注意这个token不是永久的用户重装系统、从备份恢复、甚至系统更新后都可能变化而且它在APNs沙箱环境和生产环境也是两个不同的体系。所以很多推送系统会要求服务端每次收到token都做“覆盖更新”而不是简单去重。Android走厂商通道时的流程也类似但更繁琐。比如华为要求先通过HmsMessageService.onNewToken拿到token再把token同步给推送服务端小米那套也差不多不同厂商还会要求不同的包名、AppID、AppSecret、证书指纹。第三方SDK一般会帮你做“聚合”把你的token同步给所有厂商通道你只要在后台能管理就好。我遇到过一个特别典型的错误测试阶段把iOS的development token传到了线上生产环境的推送接口结果开发环境设备能收到消息、线上用户全都不行。排查了大半天才发现是服务端把apns-topic、环境字段这些搞混了。token这个东西听着简单真到了多平台多环境混合操作的时候坑还不小。2.2 服务端下发消息的两种姿势服务端怎么把消息发出去呢两种主流方式。第一种直接调用各平台或第三方SDK提供的HTTP接口。这种方式最直接适合触发型场景。比如用户发了一条评论另一端就要立刻收到通知这时候你在业务代码里同步或者异步地调用一下推送接口把参数传过去就行。第二种走消息队列异步推送。这个适合高并发或需要限流控制的场景。比如一场促销活动要同时给100万用户发push你不可能在用户请求线程里直接一个循环调第三方接口那会把上游服务和推送服务都打挂。正确做法是把“要推给谁、推什么内容”封装成一个任务丢进RabbitMQ或者Kafka然后由推送消费者一批一批地拉取、批量调用上游接口并对失败任务做重试。对于热词里提到的“sse消息推送”它属于另一条路线服务端主动往浏览器或App端的EventSource连接里写入事件流常用于IM、客服对话、实时通知这类场景。它跟原生推送有一个本质区别SSE要求App或浏览器必须保持着一个活跃连接断开了就收不到。所以在App里面SSE通常只作为“App在前台时”的实时补充真正的挂后台能力还是要靠APNs或厂商通道。2.3 消息合并、时效性与展示策略先问自己一个问题用户3分钟没看手机你这期间发了5条活动通知是让他一次看到5条好还是合并成1条“你有5条新消息”更好在讲体验时我一般主张“能合并就合并”。连续推送多条相同类型的内容非常容易引起用户反感直接关通知权限。有些推送服务提供了collapse_id或unique_id参数你可以在后台把同一类型消息的ID设为同一个值。这样当系统发现手机里已经有同ID的消息待显示时新消息就会替代旧消息而不是叠在下面。时效性也很关键。APNs的payload里有个apns-expiration字段代表消息的有效期超过这个时间还没送达就不再投递。这个字段特别适合“验证码、限时秒杀提醒、直播开播提醒”这类场景。比如一个直播开播提醒设定有效期5分钟用户正好这5分钟没网、没开机等他恢复时消息已经失效那就别再弹了再弹只会显得烦人。展示策略上还要考虑App处于什么状态。Android做得比较丰富你可以定义通知渠道的重要性等级比如分“紧急”“默认”“低优先级”。WebPush那边类似也有urgency字段可以控制消息优先级。这些细节直接影响用户在通知栏看到的顺序和样式但很多人忽略了它们。3. 选型实测自建推送还是第三方推送服务3.1 第三方推送的优缺点对多数中小团队来说用市面上的第三方推送服务是性价比最高的选择。像极光推送、个推、友盟、腾讯移动推送这些服务基本都把“聚合厂商通道”“统计报表”“标签分群”“定时推送”这些功能做成熟了接入有现成的SDK后台管理有可视化的界面。最早期的开发阶段你只需要注册账号、上传证书或者配置密钥、集成SDK一天就能让一条消息从控制台到用户手机上。但第三方服务并不是免费的。免费版普遍限制并发、劝你展示广告或者限制标签数量。等你用户量上来需要更高的并发、更细的数据维度时付费套餐不便宜。另一个问题是数据你的用户设备信息、推送行为数据都沉淀在第三方的服务器上这在国内隐私合规越来越严格的背景下需要做充分的数据合规评估。我见过一些初创团队因为早期图方便直接用了第三方等做到百万日活之后才发现每条消息的费用和被绑定的数据出口都很难受。所以选型时一定不要只看“接入快不快”要连未来两年的规模发展一起估进去。3.2 自建推送的最佳适用场景自建推送听起来很硬核但实际不是所有团队都需要。我建议至少在下面几种情况考虑自建一是App有极高的安全合规要求比如金融、政企类应用你不想让第三方拿到终端设备信息。 二是对推送链路和数据自主可控要求极高像一些大厂在IM、社交领域推送是产品的核心体验自建可以针对长连接做深度调优。 三是使用量特别大比如日均推送量超过百万甚至千万级用第三方按量计费成本会变得异常夸张。自建之后运维成本会被摊薄长期看更省钱。自建方案的核心是自建长连接服务和消息管理后台。你自己维护一个MQTT或者自定义TCP长连接服务端在App里维持心跳同时配合厂商通道兜底。这套方案的难点不在“消息能发出去”而在稳定性海量连接下的心跳保活、弱网重连、离线消息存储、消息幂等去重、用户在线状态一致性。没做过高并发网络服务的团队很容易在这里翻车。3.3 我整理过的选型参照我从成本、接入难度、到达率、可定制性四个维度做过一次对比大致是这样方案接入成本加权到达率定制能力长期成本第三方聚合推送低中高全国产机混合弱随量线性上涨第三方FCM/APNs自管中中中中纯自建长连接高中需配厂商通道兜底强前期高、后期低厂商通道直连中高高较强免费但人力成本高如果团队规模不大、又像很多互联网金融、工具类App那样特别看重送达率我推荐“第三方推送服务为主同时自己接好厂商通道”的混合方案。因为第三方已经帮你处理了跟多家厂商的适配省下的时间可以拿去优化业务而不是天天盯着安卓厂商的新规则发愁。3.4 我踩过的选型坑别被“到达率99%”忽悠选型时最容易被市场宣传里的“到达率99%”忽悠。真实情况是按通知栏显示统计的到达率和你业务真正关心的“有效消息”到达率完全不是一回事。有些服务商把“推送服务器收到了消息”也算作到达那数字当然漂亮。但用户手机到底有没有显示厂商通道在不在线用户是不是已经关掉了通知栏权限这些都是影响“真实到达”的因素。我现在的习惯是不只盯控制台的送达数还定期抽样对比“推送消息数”和“用户点击打开数”。如果送达率高但点击率低得离谱那大概率是消息内容或推送时机出了问题而不是通道出了问题如果送达率本身就低又集中出现在某个机型或某个系统版本那就要去查厂商通道的配置了。另外接入任何第三方SDK前一定要去官网翻最新的权限文档。有些SDK默认申请了一堆跟推送无关的权限审核的时候很容易被应用商店打回用户看了也害怕。这块虽然不算“选型”但算是接入的隐性成本之一。4. 推送落地Android项目打包、发布与更新分发4.1 Android Studio生成APK后凭什么还要通过Git发布到服务器热词里有一条特别实在的诉求“Android Studio生成的APK如何通过Git推送发布到服务器以便后续更新”。这条问得很具体也戳中了很多独立开发者和中小团队的痛点。很多新手开发者的做法是用Android Studio打包生成APK然后通过微信、QQ把文件传给同事或者自己传到网盘再让用户扫码下载。这种方案在开发初期确实简单但缺点是完全没有版本管理、没有灰度策略、也没有日志排查依据。你根本不知道线上用户拿着的是哪个包、哪个版本。等用户量再大一点你一定会需要一套“服务端存放版本信息、App启动时主动检查更新”的自动化流程。Git在这里不是用来存大文件的而是用来做“版本发布的触发器和审计”的。具体做法是在你打好了新版本APK之后打一个Git tag比如v1.2.0然后由一个自动化脚本检测到这个新tag就触发打包服务器去拉源码、构建APK、并把产物同步到你自己的服务器或对象存储上。这样你的APK发布过程就跟代码版本严格绑定起来了哪个版本对应哪段代码一查便知。4.2 用Git Tag触发APK发布到服务器的可落地步骤我以一套最常用的实战流程为例用的工具是GitLab CI、Linux服务器以及Nginx做静态文件服务。开发和测试通过了先在本地或者CI上打一个发布taggit tag -a v1.2.0 -m release: 新增活动页 修复登录bug git push origin v1.2.0在GitLab CI的配置文件中加一条发布任务当tag以v开头时触发布构建release-apk: stage: build only: - /^v\d\.\d\.\d$/ script: - ./gradlew assembleRelease - scp app/build/outputs/apk/release/app-release.apk useryour-server:/data/apk/app-v1.2.0.apk - ssh useryour-server echo v1.2.0 /data/apk/latest-version.txt这里的关键点是你把latest-version.txt和APK文件放到了服务器的同一个目录Nginx把它们都暴露在同一个URL前缀下。App后续做版本更新时只需要请求这个txt文件就能拿到“当前服务端最新版本号”再跟自己本地的versionCode比较决定要不要提示升级。在App里写一个简单的更新检查接口把服务器上的版本号和本地比对并把下载地址返回给客户端。升级弹窗一律走App内逻辑而不是依赖应用市场。这套流程的好处是“发布历史拉得清”每次正式发包都有tag、有CI日志、有服务器文件记录。最重要的是回滚的时候你在服务器上保留历史版本出问题把txt改回上一个版本用户端马上就能回退不用重新打包。4.3 版本强制升级与灰度发布的设计细节版本更新不只是“有新版本就弹窗”那么简单。我见过不少团队因为写死了“只要服务器版本号比本地高就弹升级窗”结果被用户骂了几个月。原因其实很简单要么新版本还藏着严重的崩溃bug要么产品经理压根不想让某些用户升级。所以更新接口最好设计成三个字段latest_version、minimum_version、download_url。如果本地的versionCode比minimum_version低就走强制升级逻辑弹窗不可关闭必须更新才能继续用App如果只比latest_version旧但高于minimum_version就弹一个“可选升级”用户能点“以后再说”。灰度发布的思路是在服务器上维护一个“允许升级的设备列表”或“按比例放量”的规则比如先让5%的设备收到升级提示测试没问题再慢慢把比例调成50%、100%。很多第三方更新服务已经内置了这个能力如果你用的是自己搭建的接口就自己实现一个简单的白名单或按手机号尾号取模的规则。4.4 为什么我建议把更新信息做成JSON而不是纯文本上面写了latest-version.txt是个纯文本文件。实际上到了正式环境我更推荐把它换成latest-version.json因为JSON带的信息可以更多、更结构化{ versionCode: 12, versionName: 1.2.0, downloadUrl: https://cdn.example.com/app-v1.2.0.apk, releaseNote: 新增活动页修复登录偶现白屏问题, isForce: false }App端拿到这个JSON以后只要解析一次就能知道要不要弹窗、弹什么文案、点了之后去哪个地址下载、下载完了用不用强制重启。整个逻辑走起来非常顺畅也不会出现“版本号变了但下载地址还是旧包”这种低级问题。5. 推送运营与消息设计发得出去只是第一步5.1 推送文案的“一眼被看到”与“一眼被关掉”很多开发同学以为推送的难点全在技术其实真正决定推送价值的往往是文案和策略。技术只能保证消息“到得了”文案决定用户“点不点”时机决定用户“想不想看”。比如你给用户推送一条“老用户回归礼包”文案如果写“你的优惠券即将过期”配合一个明确的截止时间“今日24点前可用”点击率往往会比“欢迎回来快去看看吧”高出一截。不是说后者不行而是推送文案在通知栏就那么一行必须第一时间制造明确的信息增量这件事跟我有什么关系我为什么要现在点。另外一个很容易踩的坑是推送里的落地页打不开。用户点了一条“限时秒杀”通知结果跳到一个空白页或者一个有明显bug的活动页不但这单转化没了用户还极有可能直接顺手把App通知权限关了。上线前一定要把推送中的每个deeplink、每个落地页URL都自动化测试一遍。5.2 标签、分群与个性化推送推送对象越精准用户的负面反馈就越少。你可以给用户打标签比如新用户、活跃用户、沉睡用户、未注册用户、某个城市用户、某个商品类目偏好用户。合理的标签化推送可以让同一个活动文案在不同用户面前显示不同内容而不是全员群发一锅端。标签怎么来比较常见的渠道是App埋点上报比如用户看了哪个商品、搜索了哪个关键词、是否绑卡、是否开启会员也可以根据用户行为自动计算比如7天没登录自动打上“流失预警”标签。这些数据落到用户画像系统里再由推送服务按标签圈选人群就能实现“千人多面”的运营。做分群推送的时候有一个我强烈建议的原则控制频率。单个用户一天最多收到3条左右的业务推送超过这个阈值次日留存大概率会跌。哪怕是再重要的活动也要学会把不同渠道的push攒一攒再发。5.3 推送效果的三层数据送达、展示、点击运营和开发经常因为“推送到底有没有效”吵架根源是大家都只盯着一个指标看。我习惯把推送效果拆成三层来看第一层是“送达”推送服务是否成功把消息交给了系统通道。这个指标主要衡量链路通不通。 第二层是“展示”消息是否真的出现在了用户通知栏。Android上拿厂商通道的送达回执能看到但iOS上这个数据往往不完整。 第三层才是“点击”用户是否点进了App。这一层是最能说明内容质量的指标。在Android的厂商通道里消息送达和展示是可以区别统计的在iOS这边你就得依赖自己的埋点App在收到推送并且用户点击之后记录一次launchOptions或userInfo的回调。第三层点击数据如果偏低我通常先不看文案而是先去排查用户是否长期直接划掉了通知。如果连通知都不看那说明用户对这个品类的消息已经不感兴趣了你需要做的是减少频次而不是优化文案。5.4 推送合规与用户授权别等被下架才学现在基本所有应用市场都要求App在获取“通知推送”权限之前必须先弹出自己写的引导弹窗向用户解释为什么要推送。这里指的是“预先授权”不能上来就直愣愣地系统弹窗那样大多数用户会顺手选“不允许”。正确的做法是在合适的时机做一个自定义引导页说明推送的用途——比如“开启通知不会错过订单久未支付提醒和优惠动态”用户点了同意再调系统授权弹窗。这样把解释权提前拿到自己手里授权率会明显提高。另外如果你做的是涉及金融、医疗、政务类的App推送内容也需要注意敏感信息保护。个人隐私相关内容不要直接塞在通知里展示。比如银行交易提醒合适的做法是通知主体只说“你的账户发生一笔交易”具体金额跟明细放进App里需要用户解锁后才看得见。这既是安全要求也是很多应用市场的审核硬性标准。6. 常见问题与排查技巧实录6.1 用户反馈收不到推送第一步先查什么这是推送里最好用也最该养成的排查习惯收不到推送别第一时间怀疑服务端代码。先让用户去确认两件事第一系统设置里App的通知权限是不是还开着第二用户是不是用了手机管家一类的工具把App的自启动和锁屏权限关掉了。国内Android手机在这块的“品牌差异”特别明显。小米、华为、vivo、OPPO这些系统默认会对不常用App做后台冻结就算你接了厂商通道如果自己的App进程被清理得比较狠某些机型上照样可能延迟或收不到。遇到用户抱怨收不到推送你可以在后台查一下这台设备对应的厂商通道token是否还在有效期内有没有被调成“已失效”。如果只是某个版本更新之后突然收不到那就要重点对比“新版本是否改了包名、签名或者重新申请了推送服务”。在Android上包名和签名证书同时决定了厂商通道是否能正常使用改动任何一项推送token都会失效。6.2 iOS推送不弹窗的5个常见原因我在这里列一下给iOS排查时最常命中的原因App正处于前台系统默认不展示横幅必须在代理方法里手动处理。token上报错误开发环境token当成生产环境token用。payload里缺少alert字段或者Sound字段格式不对。证书或者使用p8密钥时的key_id、team_id配置有误。用户在系统设置里关闭了横幅样式现在只接收声音但不显示。每次排查iOS推送问题我建议你在服务端先用一段最简payload做单设备测试{ aps: { alert: { title: 测试标题, body: 测试内容 }, sound: default } }把它发给一台已知token正确的设备如果这台能收到那就基本确定是业务层或者payload的问题如果连这台都收不到那就往证书、环境、token上查。6.3 Android厂商通道配置的典型翻车点厂商通道的配置文件五花八门签名、指纹、回执、AppID、AppSecret每一项都有反例。先说最吓人的一个坑消息数量限制。有些厂商对通知消息有每日推送上限比如默认消息量级较大时会被限流。你在自测时感觉一切正常一到正式群发就频频失败要去后台看限流记录。还有回执问题。厂商通道把消息送达之后会通过你配置的回执URL来通知你的服务器。有些团队把回执地址配错了或者服务器没做验签处理导致统计到的送达率一直偏低。这一块要仔细看厂商文档很多情况下不是你SDK的问题而是回执服务没接好。最后提醒一下Android多个厂商通道的优先级问题。如果某个用户手机上同时装了华为移动服务和APK但你的SDK配置了多个通道系统可能会按照自己的调度策略优先走某一条。万一某条通道因为包名或证书不匹配而静默失败就会出现“手机上显示已推送但用户没收到”的情况。排查方式是把第三方控制台的“各通道送达明细”拉出来看每一条通道的成功率。6.4 App内嵌H5、Web推送与deeplink的联动坑现在很多App会用WebView内嵌H5页面来做活动运营。H5里也能通过浏览器推送或者消息事件触发App内容更新。但这里有个容易绕晕的问题H5页面的推送权限跟App原生的通知权限并不是一回事。H5里的Push API需要用户授权浏览器通知权限Firebase Web Push和App原生推送是两条完全独立的消息链路。如果你在H5页面里已经接入了Web推送那么用户打开H5页面时也要判断一下他是否曾经在系统层级禁用过通知否则你的Web推送内容和原生推送内容会“叠加轰炸”体验会很差。deeplink的坑更多。比如推送文案写的落地页是https://m.example.com/coupon但在App里你想唤起自己的原生页面就得借助Universal Link或者Scheme。不少测试漏掉了iOS上“第一次点击推送时Universal Link还没注册成功”的情况导致用户第一次点击通知后打开的却是Safari。解决方式就是上架前把“冷启动点推送”“热启动点推送”“杀掉App再点推送”这三个场景都测一遍。6.5 我压箱底的几个小经验最后分享几个我这些年实际攒下来的经验不一定写在文档里但非常管用。第一推送服务端的iOS环境切换要做得足够清晰。开发、生产两套环境最好分开配置别在同一套代码里靠一个全局布尔值硬切。我见过线上环境用了开发环境的token配置结果整批整批推送失败复盘时发现就是一个环境配置写错了。第二Android推送的“送达回调”别当作完全可靠的数据源。部分厂商在极端省电模式下并不会回传准确的送达回执数据口径自己心里要有数。第三做推送服务端API调用时设置好超时和降级。如果第三方推送接口超时了不要让主业务流程卡住等待而是把推送任务异步化失败任务写进重试表等会再补发。否则一次推送抖动把整个用户下单流程都拖崩那就得不偿失了。第四也是我自己吃过亏的地方线上推送前一定要检查文案里的特殊字符。\u2028、emoji等字符在某些系统通知渲染时会有兼容性问题轻则显示乱码重则导致消息被系统直接丢弃。你可以在测试机上用不同系统版本做一轮文案渲染测试就能提前发现这些细节。写在最后我在实际操盘推送系统的过程中最大的体会是推送从来不是“把消息发出去”这么一件简单的事它横跨客户端、服务端、系统通道、产品运营、数据统计还牵扯用户授权和隐私合规。想在各种机型、各种系统版本上都做到稳定送达没有捷径只能一层一层把细节抠清楚。刚开始做的时候也许你会觉得厂商通道、token、证书遥遥无期但只要你愿意按我上面梳理的链路逐层去排查和落地大多数问题是能稳稳解决的。最后再分享一个小技巧推送链路一旦跑通建议你建立一套自动化的“烟雾测试”。每天晚上自动往一批测试设备上发送一条带特殊标记的空消息第二天早上拉一次送达数据。只要哪天这项数据掉下来了你就知道是某个厂商通道出了问题趁用户还没发现就提前修掉。这套机制帮我提前挡掉了好几次线上事故是很值得投入的。