ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

UniPush 2.0推送原理与实战:从在线长连接到厂商离线通道

UniPush 2.0推送原理与实战:从在线长连接到厂商离线通道 1. 先对齐一个核心认知在线推送和离线推送根本不是一回事做App开发这几年推送功能我至少接过七八次每次排期表上都写着简单半天搞定最后没有一次不加班的。尤其是UniPush 2.0这类把在线推送和离线推送打包在一起的方案表面看就是个SDK接入真正跑起来全是细节证书、包名、厂商密钥、通知渠道、离线消息时效哪个环节断链消息就静悄悄丢了用户那边毫无感知你这边还在纠结为什么送达率这么低。开始动工之前我觉得有必要先把一个概念掰扯清楚在线推送和离线推送本质上走的是完全不同的两条路。1.1 App活着的时候推送反而最简单当App进程活着、用户在前台或者处于后台但没被杀掉的时候UniPush 2.0走的是个推自建的长连接通道。App启动时会和推送服务器建立一条TCP长连接服务器有消息就直接往这条连接上怼客户端收到后回调到业务层由你决定是弹通知栏还是走自己的逻辑。这条链路的特点是快、可控、不受手机厂商限制。只要是前台活着的App消息基本都是秒达延迟在几百毫秒级别体验非常好。而且这时候你可以发透传消息——不弹通知栏纯粹把数据丢给App内部处理比如刷新页面、同步状态、触发某个业务动作。这是在线推送独有的能力。但问题就在于App活着这个前提。移动端生态发展到今天Android系统对后台进程的限制已经收得非常紧iOS更是从系统层面就掐死了App在后台长时间运行的可能。用户上滑划掉应用、手机厂商的省电策略杀掉进程、系统内存回收……任何一个动作都会让你的长连接断开。长连接一断在线推送就失效了这时候必须有人接盘。1.2 离线推送的真正难点厂商不让你活着离线推送的本质是App进程已经死了你的长连接已经断开了但消息还是得让用户看到。怎么做到答案只有一个借系统的通道。Android这边小米有小米推送、华为有华为推送、OPPO有OPPO推送、vivo有vivo推送、荣耀有荣耀推送这些统称厂商通道。它们由手机厂商自己维护属于系统级服务优先级极高即使你的App进程被杀掉厂商的推送服务也能把消息送到系统通知栏。iOS那边就更简单粗暴了没有厂商之分只有苹果自家的APNs走的是苹果推送服务。所以离线推送的实际情况是你每台手机都要对接对应的厂商通道小米的用小米SDK华为的用华为SDK……如果全部自己接一个App接五个厂商通道光适配和联调就得掉一层皮。而且各大厂商对推送的审核要求还不一样有的要求你申请自分类权益有的要求App在应用市场达到一定等级不然不给开通推送权限。1.3 UniPush 2.0 把三条通道拧成一条APIUniPush 2.0解决的正是这个多通道地狱问题。它是DCloud联合个推推出的基于uni-app生态的推送服务在底层把个推长连接、Android各大厂商通道、iOS的APNs全部封装好对外只暴露一套API。你在服务端调用一个接口发消息UniPush会自动判断用户当前在线状态在线走长连接直接到达App离线且是Android设备自动路由到对应厂商通道通过系统通知栏展示离线且是iOS设备走APNs厂商通道没配置或不可用消息进入离线存储在有效期内用户联网后补偿下发。这个设计对于uni-app项目来说几乎是量身定做的。因为你不需要再单独集成个推SDK也不需要关心各厂商SDK的版本冲突问题只需要在manifest.json里勾选Push模块把厂商参数填进开发者中心剩下的交给封装层处理。用一句话概括UniPush 2.0的定位**它是把在线推送的实时性和离线推送的触达性缝在一起的一层胶水让你不用关心消息到底走哪条路。2. UniPush 2.0 的消息流转架构谁在发、走哪条路、怎么落地2.1 三条通道的分工与降级逻辑理解了在线和离线的本质区别后我们再来看UniPush 2.0的整体消息流转架构这样后续排查问题的时候你能迅速定位是哪条通道掉链子了。第一条通道是个推长连接。这条通道覆盖的是App在线场景。它的优势是支持透传消息可以做静默更新、业务数据同步等操作而且不受厂商通知栏权限的影响。缺点是只要App进程死了这条通道就断了。第二条通道是Android厂商通道。这条通道覆盖的是Android App离线场景。它的特点是即使App被杀系统级推送服务依然活着消息能稳定到达通知栏。缺点是只能发通知消息不支持纯静默透传而且各厂商对推送类型有限制比如小米要求新闻资讯类App申请自分类权益否则只能发通知消息不能发应用内消息。第三条通道是iOS APNs。苹果生态只认这一条消息从你的服务端发到UniPushUniPush转投到APNs最后落到用户手机通知栏。它的限制是要配置推送证书开发证书和发布证书而且用户可以在系统设置里单独关闭某个App的通知权限。UniPush 2.0的降级逻辑大概是这样的发消息时带上目标用户的cidUniPush客户端标识服务器先查一下这个cid对应的长连接是否在线。在线就走长连接不在线就判断设备类型Android设备查这个cid有没有配置厂商通道配置了就走厂商通道没配置或配置失效就走离线存储Android也支持走自有服务离线存iOS设备直接走APNs。2.2 通知消息和透传消息选择决定命运在UniPush 2.0里消息分为两大类门道很深。通知消息就是用户能在通知栏看到的那个有标题、有内容、带个图标点击后可以唤起App或跳转到指定页面。这类消息不管是长连接通道还是厂商通道都能发是所有离线推送的基本盘。透传消息是只把数据传给App、不经过通知栏的消息App在onPushMessage回调里自己处理。这类消息的价值在于灵活可以做静默操作。但注意透传消息在离线场景下基本是发不出去的。厂商通道的侧重点就是通知栏展示OPPO和vivo这些厂商对透传消息的限制非常严格很多情况下根本不给下发。所以设计消息策略的时候必须有一个清醒的认知如果你想让离线用户看到内容请用通知消息如果你只需要在线用户进行数据同步再用透传消息。不要指望一条透传消息能搞定离线触达那是不现实的。2.3 离线消息的有效期是你必须算清楚的一笔账UniPush 2.0的离线存储不是无限期的。你在服务端发消息时可以指定expireTime意思是这条消息在多少秒内有效。用户在这段时间内联网消息会补偿下发超过这个时间消息就丢弃了。实际项目里怎么设这个值我见过不少团队随便填个值结果要么消息太晚到达没有意义要么过期太短导致用户下午才打开App上午的推送已经丢了。我的经验是分场景时效性强的运营消息比如限时活动、验证码类通知建议设30分钟到2小时普通的交易类通知发货、到账、退款建议设24到72小时。这类消息即使晚了一点用户看到仍然有业务价值。这个参数直接影响你的真实触达率做数据统计的时候要把它算进去不然你统计出来的送达率会低得离谱——不是通道问题是你自己把消息设过期了。3. 接入实操开发者中心参数、证书包名和客户端API一次讲透3.1 开通服务与应用信息配置第一步是在DCloud开发者中心操作。登录dev.dcloud.net.cn找到你的应用在uni-push 2.0下面点击开通。这里会获取到三个核心凭证appId、appKey、masterSecret。三者分工如下appId应用唯一标识服务端发消息时用appKey接口调用身份标识等同于你的应用在UniPush体系里的用户名masterSecret签名密钥等同于密码绝不能暴露到客户端代码里只保存在服务端。开通之后不要急着写代码先把Android厂商推送设置这块配好。表格里逐个填小米的appId/appKey/appSecret华为的appId/appSecretOPPO的appKey/appSecret/masterSecretvivo的appId/appKey/appSecret荣耀的appId/appSecret。去各厂商开放平台申请这些参数的时候有一个特别容易踩的坑应用包名必须和你App的Android包名完全一致包括大小写和点号。厂商平台注册的是com.example.app你本地打包的包名是com.example.app2那离线推送百分百收不到。签名证书SHA1/SHA256指纹也要和正式发布包一致。这些平台审核一般一两天所以建议在项目排期早期就去申请不要等开发完了再弄。iOS端则是到Apple Developer后台创建推送证书。开发环境用.p12证书生产环境用.p8密钥证书推荐p8因为支持多个应用复用且不会过期。把证书内容传到开发者中心的iOS推送配置里然后把Bundle Identifier核对清楚。3.2 manifest.json里的Push模块配置在uni-app项目中打开manifest.json进入App模块配置勾选Push消息推送选择uniPush 2.0版本。同时记得配置推送图标这个图标会显示在通知栏消息的左侧不配置的话有可能显示成默认的白色方块非常影响观感。实际配置类似这样{ app: { distribute: { sdkConfigs: { push: { unipush: { version: 2.0.0, icons: { push: { url: /static/push_icon.png } }, description: 消息推送 } } } } } }填写url时用绝对路径放在static目录下最稳妥。配置完后用HBuilderX重新生成App资源然后打自定义调试基座或正式包测试。注意厂商通道离线推送能力只在正式打包或使用自定义基座时生效标准HBuilderX基座没法验证厂商通道这是很多新手第一次自测就翻车的地方。3.3 客户端API获取cid、监听消息、处理点击客户端的核心逻辑就三个拿cid、监听消息、处理点击。先看获取ciduni.getPushClientId({ success(res) { const cid res.cid // cid是这台设备在UniPush体系里的唯一身份标识 // 拿它去请求你自己的服务端绑定到当前登录用户 uni.request({ url: https://api.yourserver.com/user/bind_cid, method: POST, data: { cid }, }) }, fail(err) { console.error(获取cid失败, err) } })cid的获取时机建议放在App启动后、用户登录成功后各做一次。用户未登录状态也建议获取cid这样可以用cid做游客维度的触达登录后重新绑定把cid关联到用户ID上。服务端保存这个映射关系后续发消息就是给用户ID发消息而不是给设备发消息逻辑清晰很多。监听消息和点击的回调如下uni.onPushMessage((res) { // res.type: click 表示用户点击了通知栏消息message 表示App收到了透传/通知数据 if (res.type click) { // 点击通知栏消息触发 const payload res.payload if (payload) { try { const data JSON.parse(payload) // 根据data里的字段跳转对应页面 uni.navigateTo({ url: data.page }) } catch (e) { console.error(payload解析失败, e) } } } else if (res.type message) { // 透传消息或通知消息的数据到达此时通知栏可能还没展示 // 可以做业务层的静默处理 } })一个非常重要的经验onPushMessage回调要在App启动最早的时机注册最好放在App.vue的onLaunch里。否则用户冷启动App、点击通知栏的时候如果回调还没注册type click的事件你就收不到跳转逻辑就丢了。这个坑我踩过一次线上用户反馈点击推送没反应排查半天发现是注册时机晚了。4. 服务端推送逻辑单推群推、过期策略和送达回执4.1 cid绑定与用户体系的映射服务端的第一个任务是维护cid和用户ID的映射关系。推荐表结构里至少要包含用户ID、cid、平台android/ios、最后活跃时间、绑定状态。用户换手机、卸载重装、登录其他账号都会生成新的cid所以绑定接口要做成一个用户对应多条cid的模型发消息时遍历该用户所有有效cid。服务端收到客户端上报的cid后还需要做一次合法性校验防止恶意刷接口。简单做法是让客户端带着登录态token服务端校验通过后再绑定。4.2 单推和群推的API调用细节UniPush 2.0的服务端HTTP API调用方式以单推为例大致是这样一个流程组装请求头包含appKey、timestamp、signsign的计算规则是用appKey timestamp masterSecret做MD5加密具体以官方最新文档为准然后把消息体以JSON格式POST到对应接口。单推请求的核心参数如下参数说明appId应用标识pushToken目标设备的cidtitle通知栏标题content通知栏内容payload自定义JSON字符串用于客户端跳转forceNotificationtrue表示强通知消息false表示静默透传options.channelAndroid通知渠道IDoptions.expireTime离线消息有效期秒一条完整的单推请求大概长这样curl -X POST https://api.unipush.dcloud.net.cn/rest/v3/unicast \ -H Content-Type: application/json \ -H appKey: your-app-key \ -H timestamp: 1700000000000 \ -H sign: your-md5-sign \ -d { appId: your-app-id, pushToken: 目标cid, title: 订单通知, content: 您的订单已发货请留意查收, payload: {\page\:\/pages/order/detail\,\orderId\:\20250201\}, forceNotification: true, options: { channel: default, expireTime: 86400 } }群推接口把pushToken换成tag或调用广播接口核心逻辑一致。我习惯在服务端封装一层公共方法把sign生成、请求头拼接、错误码处理统一收敛起来业务方只需要传{ userId, title, content, payload, expireTime }可读性和维护性都好很多。4.3 通知渠道channel是Android 8.0以后的必修课options.channel这个参数很多第一次做Android推送的人会忽略。Android 8.0开始所有通知必须归属于某个通知渠道Notification Channel。渠道由App在代码里创建比如默认通知订单消息活动促销每个渠道有独立的优先级、震动、声音设置用户也可以在系统设置里单独关闭某一个渠道。UniPush 2.0在集成时会自动创建默认渠道但如果你想精细控制比如把活动消息和交易消息分成两个渠道让用户可以只关促销不关交易提醒就需要在客户端调用uni.createPushMessage或原生插件提前创建渠道。服务端发消息时指定对应的options.channel消息就会归到该渠道下展示。如果指定的渠道不存在部分厂商会直接丢弃消息所以在发消息前要确认渠道ID已经在客户端创建过。4.4 回执、统计和是否真的送达的验证方式推送发出去了不代表用户看到了。UniPush 2.0提供了送达回执数据在开发者中心的推送统计里能看到发送量、在线送达量、厂商通道送达量、点击量。这里有几个指标要分清楚发送量服务端成功接受的消息数到达量消息到达设备长连接或厂商通道的数量展示量真正展示到通知栏的数量点击量用户点击通知栏的数量。从发送到点击每一步都有损耗。正常运营级App的点击率一般在2%到5%之间算是健康。如果到达量正常但展示量低大概率是Android通知渠道被用户关了如果发送量就异常低说明cid绑定环节出了问题如果厂商通道到达量为0去查对应厂商平台的密钥和包名签名是否配置正确。5. 我在生产环境踩过的坑从收不到通知到点击无响应5.1 离线消息收不到锁定厂商通道配置三重关这是最高频的故障现象是App在线能收到一杀掉就收不到。排查链路基本固定按顺序检查三个点。第一包名和签名。厂商平台填的包名、签名指纹必须和正式包完全一致。用keytool -list -v -keystore your.keystore查看正式签名的SHA256和厂商平台后台比对。记住HBuilderX云打包用公共测试证书跑出来的包和正式证书包的指纹不一样测厂商通道一定要用正式证书包或自己的自定义基座。第二厂商密钥状态。有些厂商平台密钥申请后不是立即生效的小米需要等应用通过审核OPPO需要创建消息服务并绑定应用。这些状态在厂商后台都能看到如果显示审核中或未启用离线推送必然失败。第三设备端通知权限和电池策略。Android 13及以上通知权限需要在运行时动态申请用户拒绝后所有通知都收不到。小米、华为、OPPO的系统还有各自的电池优化策略会限制App的自启动和后台运行。厂商通道是系统级服务理论上不影响但用户在系统设置里把App的通知彻底关掉那谁都救不了。App里需要做一个权限引导页面检测到通知权限被关就提示用户开启。5.2 通知栏不弹通知多半是渠道或分类的锅明明消息到设备了通知栏就是不显示。出现这个情况先看手机设置里App的通知是否允许再往下排查通知渠道。如果你用forceNotification: false发的是透传消息那消息到达后App进程已死根本没有人处理它自然也不会有通知栏展示。想离线也弹通知必须发forceNotification: true的通知消息。另一个隐蔽问题是厂商的消息分类。小米推行消息分类制度把通知分为即时通讯类资讯类营销类等营销类消息在部分MIUI版本上默认不弹横幅、不响铃、直接收进通知栏折叠区。华为也有类似机制新闻资讯类App如果没申请到高优先级分类通知展示会被降级。如果你的App属于内容资讯类务必提前去厂商平台申请相应的消息分类权益。5.3 点击通知无响应payload和路由是重灾区点击通知栏消息App倒是被唤醒了但没有跳转到目标页面。这个问题九成出在payload上。UniPush 2.0的payload是字符串类型不是对象。服务端传的时候必须JSON.stringify成一个字符串客户端收到后再JSON.parse解析。很多团队服务端直接传了个JSON对象到了客户端拿到的payload就变成了[object Object]解析必然失败。跳转用uni.navigateTo时目标页面必须是已经在pages.json里注册过的页面且不能是tabBar页面。tabBar页面要用uni.switchTab跳这个区分一定要做否则点击那个瞬间控制台会报错用户看到的就是点了没反应。冷启动的场景也要单独测。用户杀掉App后点击通知App冷启动此时uni.onPushMessage如果注册晚了type click的事件会丢失。稳妥的做法是同时在App.vue的onLaunch里注册监听并且把cid获取和消息监听放在同一个时机确保冷启动时监听先于业务页面执行。5.4 iOS收不到推送证书环境和token的对齐iOS用户反馈收不到推送优先检查两件事。第一证书环境。开发证书对应开发环境发布证书对应生产环境二者不能混用。用HBuilderX真机运行调试时走的是开发环境这时候需要在开发者中心配好开发证书线上发布包走的是生产环境必须配发布证书。最稳的方式是使用.p8密钥一个密钥同时支持开发和发布环境省去来回切换的麻烦。第二APNs token。iOS每次安装App系统都会生成一个deviceTokenUniPush把它映射到cid。如果用户在系统设置里关闭了通知权限APNs会认为该设备不可推送。还有一种情况用户在App内不同意通知权限弹窗系统直接不给deviceToken那么cid都拿不到。所以在iOS端首次启动就要引导用户允许通知权限。5.5 我维护的一套自测清单踩了这些坑之后我给自己定了一套发版前的推送自测清单每次上线前跑一遍基本能挡住九成问题前台在线通知消息和透传消息各发一条确认秒达后台运行不杀进程确认通知正常展示杀掉进程Android用正式签名包测试华为、小米、OPPO、vivo四个主流厂商的离线推送确认通知栏展示和点击跳转杀掉进程iOS确认APNs到达点击跳转正常关闭通知权限确认App有权限引导提示而不是用户完全不知道去哪开弱网环境确认消息不重复、不丢失过期时间生效多设备同一账号绑定了手机和平板时确认所有设备都能收到或按业务需求做单端限定。这套清单看起来繁琐但每次上线前跑一遍也就半小时比线上出问题再紧急热修划算多了。最后分享一个小技巧开发期间我会在服务端保留一个测试推送的调试接口界面上可以填写cid、标题、内容和payload一键发送不用每次都拼curl。配合真机日志排查消息问题的时候效率至少翻一倍。UniPush 2.0这套东西原理其实不复杂复杂的是它横跨了服务端、客户端、厂商平台三端任何一端的信息不对称都会让你多熬几个通宵。把通道原理吃透把参数当成契约来管理这套推送体系就能稳稳地运转下去。
返回列表