uni-app离线推送实战:uniPush厂商通道配置与避坑指南
1. 离线推送到底在解决什么问题做过移动端的人都有一个共识消息推送这件事看起来简单做起来处处是坑。尤其是离线推送也就是App进程已经被系统杀掉、用户压根没打开应用的情况下还能把一条通知送到用户手机的通知栏里——这个能力在即时通讯、订单状态变更、告警提醒这类场景里几乎是刚需。我最早接触这块是在一个跨平台的即时通讯项目里当时用的是uni-app做多端适配服务端消息下发之后在线用户能实时收到但一旦App退到后台被系统回收消息就石沉大海了。用户投诉说消息延迟几个小时才收到实际上根本不是延迟是压根没收到等用户主动打开App才一次性拉取到历史消息。这个体验在IM场景里是致命的。后来接入了uniPush的离线推送能力才算把这条链路打通。简单说uniPush是uni-app生态里统一封装的一套推送服务它把各家手机厂商的推送通道比如华为、小米、OPPO、vivo、荣耀等以及个推的通道做了聚合开发者不需要分别对接每一家厂商的SDK通过一套API就能实现在线走长连接、离线走厂商通道的推送逻辑。这篇文章适合几类人看一是正在用uni-app做App、需要接入推送的开发者二是已经接了推送但离线收不到、踩了坑在排查的同学三是对移动端推送整体链路感兴趣、想搞清楚为什么离线还能收到通知这个原理的人。我会从整体设计思路讲起把厂商通道、离线判定、参数配置、真机调试这些环节拆开揉碎尽量让你看完能直接抄作业。需要提前说明的是离线推送的很多坑不在代码层面而在厂商配置和系统权限层面。代码写对了配置没弄好一样收不到。这也是为什么很多人觉得离线推送玄学——其实一点都不玄只是链路太长任何一环断了都表现为收不到。2. 推送整体架构与方案选型思路2.1 在线推送和离线推送的本质区别要理解离线推送先得搞清楚在线推送是怎么工作的。App启动之后客户端会和推送服务端建立一条长连接通常是TCP长连接靠心跳包维持。服务端有消息要下发时直接通过这条长连接把数据推给客户端客户端收到后自己决定是弹通知还是更新UI。这条链路的特点是实时、可控、不依赖系统但前提是App进程活着、长连接没断。问题就出在进程活着这个前提上。移动端系统为了省电和内存会在App退到后台一段时间后把它冻结甚至杀掉。进程一死长连接自然就断了服务端再怎么推也没人接。这时候就需要离线推送登场服务端发现长连接不可达就把消息转交给手机厂商的推送服务比如华为推送、小米推送由厂商的系统级通道把消息投递到设备上。因为厂商推送是系统内置的不受App进程死活影响所以能做到App没开也能收到。用一句话概括两者的分工在线靠自己的长连接离线靠厂商的系统通道。uniPush的价值就在于它把这两条链路统一了开发者调用一次接口它内部自动判断走哪条路。2.2 为什么选uniPush而不是自己对接厂商理论上你完全可以自己一家一家对接厂商推送SDK。但实际做下来成本高得离谱。国内主流厂商少说七八家每家的SDK接入方式、参数格式、回调机制都不一样光是维护这套代码就够呛。而且厂商通道还有各种限制比如华为有自分类权益、小米有通道限额、OPPO有私信通道申请每一家的规则都要单独研究。uniPush的思路是做一层聚合。你在uni-app项目里配置好各厂商的参数uniPush帮你把消息分发到对应通道。对开发者来说接口是统一的省去了大量重复对接工作。当然代价是你得接受它的抽象层某些厂商特有的高级能力可能没法直接用但对绝大多数业务场景来说够用了。这里有个选型上的经验如果你的App只在单一厂商的应用市场分发比如只上华为应用市场那直接对接华为推送可能更省事但如果是全渠道分发、用户机型五花八门那uniPush这种聚合方案的优势就非常明显了。2.3 离线推送的完整链路拆解一条离线消息从服务端发出到用户看到通知中间要经过这么几个环节你的业务服务端调用uniPush的服务端API提交推送请求带上目标用户标识cid和消息内容。uniPush服务端判断该用户当前是否在线。在线就直接走长连接下发离线就进入下一步。根据设备品牌把消息路由到对应的厂商推送通道华为设备走华为、小米设备走小米。厂商推送服务把消息投递到目标设备的系统层。设备系统收到消息在通知栏展示或者唤醒App的推送接收逻辑。这条链路里第3步和第4步是最容易出问题的。因为消息一旦交给厂商就脱离了你的掌控厂商那边可能因为各种原因通道未开通、消息被限流、设备未注册投递失败而失败原因往往不会实时反馈给你。所以排查离线推送问题本质上是在排查这条链路的每一环。3. 核心配置与实操要点详解3.1 厂商通道开通离线推送的地基离线推送能不能成七分靠配置三分靠代码。配置里最关键的就是厂商通道的开通。以华为为例你需要去华为开发者平台创建应用开通推送服务拿到AppID和AppSecret然后在uniPush后台把这些参数填进去。小米、OPPO、vivo、荣耀各家流程类似但细节差异不小。这里有个特别容易踩的坑厂商推送服务和应用包名是强绑定的。你在华为后台填的包名必须和App实际打包的包名完全一致差一个字符都不行。我见过有同学调试时用测试包名上线换成正式包名后忘了改厂商配置结果线上离线推送全挂。另一个坑是签名证书。部分厂商比如小米要求你上传应用的签名证书指纹SHA256用于校验请求来源。如果你用调试证书打包测试上传的也是调试证书指纹那正式包用正式证书签名后就对不上了。建议一开始就用正式证书的指纹去配置避免后期返工。厂商关键配置项常见坑点华为AppID、AppSecret、包名需开通自分类权益才能推通知小米AppID、AppKey、AppSecret、签名指纹签名指纹必须与打包证书一致OPPOAppKey、MasterSecret、包名需申请私信通道权限vivoAppID、AppKey、AppSecret测试阶段有设备白名单限制荣耀AppID、AppKey、AppSecret独立于华为需单独开通提示厂商后台的配置改完之后通常需要重新打包App才生效因为部分参数是编译期写进App里的。改完配置记得重新出包别只重启应用。3.2 uni-app端的初始化与cid获取客户端这边核心是拿到cidClient ID这是uniPush用来标识一台设备的唯一ID。服务端推送时就是靠cid来指定目标设备的。获取cid的时机很关键——它需要等推送服务初始化完成之后才能拿到所以不能一进App就去取得监听事件。在uni-app里通常是在App.vue的onLaunch里做初始化然后通过监听pushClientId事件来获取cid。拿到之后要把cid上报给你的业务服务端和当前登录用户绑定。这样服务端才知道要给用户A推送应该发到哪个cid。这里有个细节cid可能会变。用户卸载重装、清除数据、换设备cid都会变。所以每次App启动拿到cid后都应该重新上报一次让服务端更新绑定关系。如果只在首次登录时上报用户重装后就收不到推送了。// App.vue 中监听cid onLaunch() { uni.onPushMessage((res) { console.log(收到推送消息, res) }) // 获取cid uni.getPushClientId({ success: (res) { const cid res.cid // 上报给业务服务端与当前用户绑定 reportCidToServer(cid) }, fail: (err) { console.error(获取cid失败, err) } }) }3.3 服务端下发参数怎么填才不丢消息服务端调用uniPush的下发接口时参数配置直接决定了消息能不能走离线通道。最核心的几个参数是cid、title、content以及一个容易被忽略的force_notification。force_notification这个参数很关键。它决定消息是通知消息还是透传消息。通知消息由系统直接展示在通知栏App不需要运行透传消息则是把数据交给App自己处理如果App没运行透传消息就丢了。所以做离线推送一定要用通知消息也就是把force_notification设为true。另外消息体里可以带自定义数据payload用于用户点击通知后跳转到指定页面。这个payload在离线场景下也能带过去用户点击通知启动App时App能拿到这份数据做页面跳转。{ cid: 目标设备的cid, title: 您有一条新消息, content: 点击查看详情, force_notification: true, payload: { page: /pages/chat/detail, id: 12345 } }注意不同厂商对通知标题和内容的长度有限制华为一般标题不超过40字符、内容不超过100字符超长会被截断。重要信息尽量放前面。4. 完整实操流程与真机验证4.1 从零到一的接入步骤我把整个接入流程梳理成一条可复现的路径按顺序做基本不会漏开通uniPush服务在项目对应的开发者后台开通uniPush拿到AppID等信息。配置厂商通道逐个去各厂商开放平台创建应用、开通推送、拿到密钥回填到uniPush后台。配置应用包名和证书确保厂商后台的包名、签名指纹与实际打包一致。客户端集成在uni-app项目里引入推送模块处理cid获取和消息监听。服务端对接接入uniPush服务端SDK实现消息下发逻辑。真机测试用真机验证在线和离线两种场景。每一步都不能跳尤其是第2、3步是离线推送的命门。4.2 离线场景的真机测试方法测试离线推送有个标准动作把App进程彻底杀掉。注意是杀进程不是退到后台。退到后台只是进程被冻结长连接可能还在测不出离线效果。正确的做法是在最近任务列表里把App划掉或者用系统设置里的强行停止。杀进程之后从服务端发一条推送观察通知栏是否弹出。如果弹了说明离线通道通了如果没弹就要开始排查。这里建议分厂商测试因为不同厂商的通道是独立的华为能收到不代表小米能收到。测试时还要注意一个细节部分厂商对测试设备有白名单限制。比如vivo在测试阶段只有加入白名单的设备才能收到推送。如果你测vivo收不到先确认设备是否在白名单里别一上来就怀疑代码。4.3 参数计算与通道选择逻辑服务端下发时uniPush内部是怎么选择通道的大致逻辑是先看设备品牌华为设备优先走华为通道小米设备走小米通道以此类推如果设备品牌没有对应的厂商通道或者厂商通道下发失败就回退到个推通道。这里有个通道优先级的概念。有些场景下你可能希望强制走某个通道但大多数时候交给uniPush自动判断就行。需要注意的是厂商通道有配额限制。比如某些厂商对每天的推送条数有上限超过之后会被限流。如果你的业务推送量很大要提前评估配额够不够必要时申请提升。关于消息体大小厂商通道一般限制在4KB以内超过可能被截断或拒收。payload里的数据要精简别塞大对象。5. 常见问题排查与避坑经验5.1 离线收不到消息的排查顺序离线推送收不到是最常见也最让人头疼的问题。我总结了一套排查顺序按这个顺序走基本能定位到问题确认cid是否正确上报服务端拿到的cid是不是当前设备的用户重装后有没有更新确认厂商通道是否开通对应品牌的通道配置填了没密钥对不对确认包名和签名是否一致厂商后台的包名、签名指纹和实际打包的是否一致确认是否杀进程测试退到后台不算离线要真杀进程。确认厂商配额是否用完当天推送量是否超限查看厂商后台的下发记录厂商后台一般能看到消息的下发状态和失败原因这是最直接的线索。现象可能原因排查方向在线能收离线收不到厂商通道未配置或配置错误检查厂商后台参数部分机型收不到该品牌通道未开通补开对应厂商通道重装后收不到cid变更未重新上报每次启动重新上报cid测试能收线上收不到包名或签名不一致核对正式包配置偶尔收不到厂商限流或配额用尽查看厂商后台配额5.2 那些文档里不会写的坑坑一通知栏权限被用户关了。这个听起来很基础但真的很多人忽略。用户如果手动关闭了App的通知权限厂商通道推过来的消息也不会展示。排查时先确认系统设置里通知权限是开着的。坑二后台自启动被限制。部分系统对App的后台行为管得很严如果App被系统判定为不活跃推送到达后可能无法唤醒App做后续处理。这个在国产定制系统上尤其明显需要在系统设置里给App开自启动权限。坑三payload解析失败导致点击无反应。用户收到通知了但点击之后没跳转到正确页面。这通常是payload格式问题或者App冷启动时没正确处理推送点击事件。建议在App启动逻辑里统一处理推送点击兼容冷启动和热启动两种情况。坑四测试环境与生产环境混淆。有些开发者测试时用的是测试环境的推送配置上线后忘了切生产环境导致线上推送全挂。建议在代码里用环境变量区分避免手动切换出错。提示排查离线推送问题时善用厂商后台的消息轨迹功能。它能告诉你消息在哪个环节失败了比盲目猜测高效得多。5.3 提升到达率的几个实用技巧离线推送的到达率不可能做到100%但可以通过一些手段尽量提升。第一合理设置推送时间避开用户休息时段减少被投诉和关闭通知的概率。第二消息内容要有价值别发垃圾推送用户一旦关闭通知权限你就彻底失去这个触达渠道了。第三多通道备份重要消息可以同时走厂商通道和个推通道提高到达概率。第四监控到达率建立推送到达率的监控指标一旦某天到达率异常下降能及时发现通道故障。我在实际项目里做过一个统计配置齐全的情况下主流机型的离线到达率能到85%以上剩下的主要是用户主动关闭通知、设备离线时间过长等原因。这个数字已经能满足绝大多数业务需求了。6. 消息点击跳转与数据回传处理6.1 冷启动与热启动的差异化处理用户点击通知栏消息后App会被唤起。这里分两种情况热启动是App还在后台点击通知直接切到前台冷启动是App进程已经死了点击通知会重新启动App。这两种情况下获取推送数据的方式不一样处理逻辑也得区分开。热启动时App的推送监听还在能直接通过onPushMessage回调拿到payload。冷启动时App是重新初始化的需要在启动参数里读取推送数据。uni-app里可以通过plus.push.getClientInfo()或者启动参数来获取。如果不做区分处理冷启动点击通知经常会出现跳转不到目标页面的问题。// 统一处理推送点击 function handlePushClick(payload) { if (!payload || !payload.page) return // 延迟跳转确保页面栈已就绪 setTimeout(() { uni.navigateTo({ url: ${payload.page}?id${payload.id} }) }, 300) }6.2 payload数据的安全与校验payload里带的数据服务端下发前最好做一层校验和签名防止被篡改。虽然厂商通道本身有一定的安全性但业务层面加一道校验更稳妥。比如payload里带一个时间戳和签名App收到后校验签名是否合法不合法就丢弃。另外payload里的跳转路径要做白名单校验。如果直接拿payload里的page去navigateTo恶意构造的payload可能跳转到非预期页面。建议维护一个允许跳转的页面白名单只放行白名单内的路径。6.3 消息去重与幂等处理离线推送有个特性同一条消息可能被投递多次。比如厂商通道下发成功但回执丢失服务端重试用户就可能收到两条一样的通知。所以客户端和服务端都要做去重。服务端给每条消息生成唯一ID客户端收到后记录已处理的ID重复的直接忽略。这个去重逻辑在IM场景里尤其重要否则用户会看到重复的消息提醒体验很差。我的做法是在payload里带一个messageId客户端维护一个最近处理过的ID集合可以用LRU缓存收到消息先查重再处理。7. 多厂商适配的差异化策略7.1 各厂商通道的特性对比虽然uniPush做了聚合但各厂商通道的特性差异还是存在的了解这些差异有助于你针对性地优化。华为通道相对稳定但需要开通自分类权益小米通道对签名校验严格OPPO和vivo对推送频率有较严格的限制荣耀作为独立通道需要单独配置。厂商稳定性主要限制适配建议华为高需自分类权益提前申请权益小米高签名校验严格用正式证书配置OPPO中频率限制严控制推送频率vivo中测试白名单测试机加白名单荣耀中独立配置单独开通通道7.2 通道降级与兜底方案再完善的配置也可能遇到某个厂商通道临时故障。这时候需要有兜底方案。常见的做法是厂商通道下发失败后自动回退到个推通道。uniPush默认有这个降级逻辑但你也可以在服务端做更精细的控制比如记录每个通道的下发成功率动态调整优先级。另一个兜底思路是引导用户开启通知权限。如果检测到用户关闭了通知权限可以在App内用弹窗引导用户去系统设置里打开。虽然不能强制但至少给用户一个选择。8. 推送效果监控与持续优化8.1 关键监控指标推送上线之后不能就不管了得建立监控。核心指标包括下发量、到达量、到达率、点击量、点击率。到达率反映通道健康度点击率反映内容质量。这两个指标要分开看到达率高但点击率低说明内容不行到达率低说明通道有问题。监控数据最好按厂商维度拆分这样能快速定位是哪个厂商通道出了问题。比如某天华为设备的到达率突然从90%掉到50%那大概率是华为通道出了状况可以第一时间去厂商后台查。8.2 基于数据的优化方向拿到监控数据后可以做几件事。第一优化推送时机分析用户点击率高的时间段把非紧急推送安排在这些时段。第二优化推送文案对比不同文案的点击率找出效果好的表达方式。第三清理无效cid长期下发失败的cid要及时清理避免浪费配额。第四分群推送根据用户活跃度分层对不活跃用户减少推送频率避免被关闭通知。我在项目里做过一轮优化把推送文案从您有一条新消息改成带具体内容的张三给您发了一条消息点击率提升了将近一倍。这说明用户对具体、有价值的信息更敏感泛泛的提醒容易被忽略。9. 我踩过的几个真实坑说几个我自己踩过的坑都是文档里不会写的。第一个是cid上报时机我一开始在登录成功后上报cid结果用户不登录就收不到推送。后来改成App启动就上报和登录状态解耦问题解决。第二个是厂商配置的缓存改完厂商后台配置后uniPush这边有缓存不是立即生效等了大概十几分钟才同步。所以改配置后别急着测等一会儿。第三个是测试机的系统版本有些老系统版本对厂商推送支持不好测试时尽量用较新的系统版本避免把系统问题误判成代码问题。还有一个印象深刻的有次线上突然大量用户反馈收不到推送排查半天发现是某个厂商通道的配额当天用完了。因为那天有个营销活动推送量激增把配额耗尽了。后来我们加了配额监控和预警快用完时提前告警避免再次发生。10. 后续可扩展的方向离线推送打通之后还有很多可以深挖的方向。比如推送内容的个性化根据用户画像推送不同内容提升点击率。再比如推送的A/B测试同一批用户分两组推不同文案对比效果。还有推送与站内信的联动推送点击后落地到站内信详情页形成完整的消息触达闭环。另外随着各厂商推送能力的演进一些新的特性也值得关注比如华为的实况窗、小米的焦点通知等这些能让推送以更醒目的方式展示。不过这些高级特性通常需要额外申请权限接入前要评估投入产出比。我个人在实际操作中的体会是离线推送这件事配置的功夫远大于编码的功夫。代码可能半天就写完了但把各厂商通道配置齐全、调通往往要花好几天。所以心态上要放平遇到收不到的情况按排查顺序一步步来别慌。把这条链路彻底搞明白之后你会发现它其实没那么神秘无非就是环节多、细节杂而已。