踐)
聊一個(gè)我最近一直在折騰的項(xiàng)目Uniapp 框架里面的消息推送與熱更新。這兩個(gè)能力看起來(lái)一個(gè)管“觸達(dá)用戶(hù)”、一個(gè)管“更新代碼”八竿子打不著但實(shí)際做起來(lái)你會(huì)發(fā)現(xiàn)它們就是跨端 App 后臺(tái)運(yùn)營(yíng)的核心兩條腿缺一條都跑不順暢。這篇筆記是我從零梳理 UniPush、廠(chǎng)商通道、離線(xiàn)推送、wgt 資源包、版本升級(jí)鏈路這些知識(shí)點(diǎn)的完整記錄里面有不少我真正踩過(guò)的坑和調(diào)通后的理解應(yīng)該能幫你少走一點(diǎn)彎路。先說(shuō)清楚這篇筆記能解決什么問(wèn)題如果你正在用 Uniapp 做一款 Android 和 iOS 跨平臺(tái) App想實(shí)現(xiàn)“App 關(guān)閉后還能收到通知”“不讓用戶(hù)去商店更新也能修復(fù)線(xiàn)上 bug”這類(lèi)需求這篇文章就是給你寫(xiě)的。我盡可能把推送的原理、廠(chǎng)商配置的流程、熱更新的包制作與版本判斷邏輯講成人話(huà)版本想把這塊搞明白的開(kāi)發(fā)者、獨(dú)立開(kāi)發(fā)者、小團(tuán)隊(duì)前端都可以直接照著做。1. 先說(shuō)結(jié)論Uniapp項(xiàng)目中推送與熱更新的真實(shí)定位1.1 我為什么要把兩者放在一起學(xué)我在做“跨平臺(tái)系統(tǒng)”這個(gè)模擬項(xiàng)目時(shí)最開(kāi)始的理解是推送就是發(fā)一條通知熱更新就是換一下包。但真上手之后才發(fā)現(xiàn)這倆根本不是兩個(gè)孤立的“工具”而是同一套“客戶(hù)端-云端控制鏈路”的兩端。推送要解決的是“用戶(hù)沒(méi)打開(kāi) App 時(shí)怎么觸達(dá)你”熱更新要解決的是“用戶(hù)手上的舊版本代碼怎么被新版本替換”。它們共同依賴(lài)一個(gè)事實(shí)你的 App 沒(méi)有永遠(yuǎn)可靠的在線(xiàn)長(zhǎng)連接也沒(méi)有永遠(yuǎn)不變的版本。你必須在用戶(hù)設(shè)備的系統(tǒng)通道、服務(wù)端的版本接口和客戶(hù)端的資源文件之間維護(hù)好狀態(tài)。所以我把它們放在一起學(xué)習(xí)原因有三個(gè)第一它們都涉及客戶(hù)端與服務(wù)端的雙向溝通推送消息的下發(fā)和升級(jí)包的檢查本質(zhì)上都依賴(lài)一個(gè)穩(wěn)定的服務(wù)端響應(yīng)第二它們都繞不開(kāi)設(shè)備廠(chǎng)商差異Android 生態(tài)下不同廠(chǎng)商對(duì)推送進(jìn)程和資源包安裝的限制完全不一樣這種“碎片化體驗(yàn)”是跨端開(kāi)發(fā)繞不開(kāi)的痛點(diǎn)第三它們?cè)诖a層面的表現(xiàn)都是異步回調(diào)推送離線(xiàn)消息和熱更包的下載都會(huì)在 App 重新啟動(dòng)時(shí)觸發(fā)連鎖邏輯處理不好就會(huì)出現(xiàn)順序問(wèn)題。1.2 這篇筆記適合誰(shuí)來(lái)讀如果你是移動(dòng)端新手你可以從這篇文章里拿到一套完整的推送與熱更新落地方案包括后臺(tái)怎么配、代碼怎么寫(xiě)、測(cè)試怎么跑如果你是有跨端項(xiàng)目經(jīng)驗(yàn)的開(kāi)發(fā)者你可以在我的實(shí)操記錄里找到一些常見(jiàn)的坑比如廠(chǎng)商通道審核為什么一直不通過(guò)、wgt 包為什么手機(jī)上安裝失敗、版本號(hào)為什么遲遲檢測(cè)不到更新如果你是獨(dú)立開(kāi)發(fā)者我更建議你把這份筆記當(dāng)成一份檢查清單每次發(fā)版之前照著過(guò)一遍能省下大量和用戶(hù)解釋“我沒(méi)收到通知”的時(shí)間。先說(shuō)這么多下面直接進(jìn)入推送的技術(shù)細(xì)節(jié)。2. 消息推送從方案選型到真正觸達(dá)用戶(hù)2.1 先理清楚Uniapp推送的幾條路很多人在 Uniapp 項(xiàng)目里做推送第一反應(yīng)是找第三方推送 SDK比如某極光、某友盟。這類(lèi)方案本身沒(méi)問(wèn)題但它們和 Uniapp 的契合度、廠(chǎng)商通道的集成方式、消息透?jìng)鞯慕涌诙家~外處理一段。我當(dāng)時(shí)做項(xiàng)目時(shí)采用了一個(gè)更貼合框架的方案基于 Uniapp 官方推送模塊來(lái)做也就是 UniPush。它和框架天然綁定能在 HBuilderX 里直接拿到推送權(quán)限同時(shí)最終會(huì)走系統(tǒng)的離線(xiàn)通道。這對(duì)跨端項(xiàng)目來(lái)說(shuō)省掉了很多自己封裝接口的麻煩。當(dāng)然也要知道它的邊界UniPush 本質(zhì)上是一個(gè)推送服務(wù)它負(fù)責(zé)把消息從服務(wù)端推給用戶(hù)設(shè)備但真正在 App 離線(xiàn)時(shí)讓消息到達(dá)背后依靠的是廠(chǎng)商系統(tǒng)級(jí)推送通道。所以 UniPush 并不能繞開(kāi)“必須去某個(gè)安卓大廠(chǎng)開(kāi)放平臺(tái)申請(qǐng)推送服務(wù)”這件事。更直接的對(duì)比是這樣的方案優(yōu)點(diǎn)缺點(diǎn)適合場(chǎng)景UniPush官方推送與框架深度整合離線(xiàn)走廠(chǎng)商通道免去自建通道廠(chǎng)商平臺(tái)配置復(fù)雜調(diào)試鏈路長(zhǎng)大部分 Uniapp 跨端項(xiàng)目第三方推送 SDK接口統(tǒng)一后臺(tái)功能多需集成插件個(gè)性化需求受限不想碰廠(chǎng)商配置的團(tuán)隊(duì)自建長(zhǎng)連接完全可控不走系統(tǒng)通知耗電、被殺、維護(hù)成本高對(duì)推送實(shí)時(shí)性要求極高的 App我當(dāng)時(shí)的選擇是 UniPush 為主、第三方為輔真實(shí)原因是 UniPush 的離線(xiàn)消息覆蓋能力比自建長(zhǎng)連接穩(wěn)定太多了。自建長(zhǎng)連接在 App 被系統(tǒng)回收之后基本失效很多國(guó)內(nèi)安卓系統(tǒng)對(duì)后臺(tái)進(jìn)程的清理比你想的激進(jìn)得多沒(méi)有系統(tǒng)通道兜底推送就變成“用戶(hù)打開(kāi) App 才收得到消息”。2.2 UniPush的廠(chǎng)商通道原理與配置要點(diǎn)先補(bǔ)一個(gè)核心概念廠(chǎng)商通道。起因是安卓系統(tǒng)不允許 App 自己常駐后臺(tái)做長(zhǎng)連接推送否則會(huì)被系統(tǒng)清理、耗電增加。但各個(gè)大廠(chǎng)有自己的系統(tǒng)級(jí)推送服務(wù)它們可以在 App 被殺后由系統(tǒng)消息中心替你收消息。UniPush 做的事就是把你的消息同時(shí)發(fā)到這些廠(chǎng)商的推送服務(wù)器再由它們推給用戶(hù)手機(jī)的系統(tǒng)通知欄。你的 App 在線(xiàn)時(shí)消息可以直接通過(guò)長(zhǎng)連接到達(dá)離線(xiàn)時(shí)消息走廠(chǎng)商通道到達(dá)。所以配置的第一步是去各大廠(chǎng)商開(kāi)放平臺(tái)注冊(cè)應(yīng)用并申請(qǐng)推送服務(wù)。這里有幾個(gè)我實(shí)測(cè)中印象很深的差異廠(chǎng)商 A應(yīng)用審核最嚴(yán)格往往要求你上傳應(yīng)用商店的下載鏈接和隱私說(shuō)明很多個(gè)人開(kāi)發(fā)者的應(yīng)用還沒(méi)有上架就被卡住了。它的推送服務(wù)配置里還有一個(gè)“自查域名”的流程必須等審核通過(guò)才能在 UniPush 后臺(tái)拉到對(duì)應(yīng)配置。廠(chǎng)商 B推送權(quán)限和分類(lèi)審核相對(duì)快一些但對(duì)通知消息的條數(shù)有限制如果同一用戶(hù)短時(shí)間收到太多推送部分推送會(huì)直接被系統(tǒng)折疊甚至丟棄。廠(chǎng)商 C對(duì)應(yīng)用的目標(biāo)版本有要求新版本系統(tǒng)要求適配某種后臺(tái)啟動(dòng)限制如果版本太老離線(xiàn)消息到了也顯示不出來(lái)。把這些廠(chǎng)商報(bào)備信息填到 UniPush 后臺(tái)后還需要把每個(gè)廠(chǎng)商提供的 appkey / appid /密鑰信息逐項(xiàng)填進(jìn) manifest 的 UniPush 節(jié)點(diǎn)再重新打包才生效。這里最容易踩的坑是填錯(cuò) appid 或者漏掉某個(gè)廠(chǎng)商的密鑰結(jié)果就是“部分手機(jī)收不到、部分手機(jī)能收到”。排查的時(shí)候一定要先把廠(chǎng)商配置逐項(xiàng)核對(duì)一遍再做真機(jī)測(cè)試。2.3 通知消息與透?jìng)飨⒌恼_打開(kāi)方式UniPush 下發(fā)消息有兩種格式一個(gè)是通知消息一個(gè)是透?jìng)飨?。理解它們的差別特別重要通知消息直接由系統(tǒng)在通知欄展示用戶(hù)點(diǎn)擊后才喚起你的 App。這種消息幾乎不需要寫(xiě)代碼配置完標(biāo)題和內(nèi)容就行問(wèn)題是它的點(diǎn)擊行為和業(yè)務(wù)參數(shù)傳遞需要額外封裝。透?jìng)飨⑼競(jìng)飨⒌竭_(dá)客戶(hù)端時(shí)要走回調(diào)方法由你們自己的代碼決定怎么處理比如彈窗、跳轉(zhuǎn)、靜默更新本地?cái)?shù)據(jù)等。我實(shí)際項(xiàng)目中的經(jīng)驗(yàn)是業(yè)務(wù)消息盡量走透?jìng)饕驗(yàn)槟憧梢宰鲋髁鞒炭刂啤Ee一個(gè)非常常見(jiàn)的場(chǎng)景用戶(hù)下單成功 30 分鐘后未支付想發(fā)一個(gè)“催付通知”。這個(gè)通知要帶上訂單 ID用戶(hù)點(diǎn)擊通知后App 應(yīng)該直接打開(kāi)訂單詳情頁(yè)。如果用透?jìng)飨⒃诨卣{(diào)里拿到訂單 ID 就能直接跳頁(yè)面非常自然如果用通知消息還需要在點(diǎn)擊回調(diào)里解析系統(tǒng)通知里塞的 extra邏輯會(huì)繞一點(diǎn)。我推薦的消息 payload 結(jié)構(gòu)長(zhǎng)這樣{ type: order_payment_remind, title: 你有待支付訂單, content: 距離關(guān)閉還有 15 分鐘請(qǐng)盡快支付, data: { orderId: 202506170001 }, notifyType: transparent }這里的“type”是給客戶(hù)端判斷業(yè)務(wù)用的“notifyType”是讓 UniPush 判斷走通知還是透?jìng)鞯?。?shí)際開(kāi)發(fā)時(shí)不要把大量業(yè)務(wù)字段塞在 title 和 content 里盡量放 data 字段方便后續(xù)擴(kuò)展。2.4 消息推送的調(diào)試與驗(yàn)證方法調(diào)試推送時(shí)最容易出現(xiàn)“后臺(tái)明明發(fā)了消息手機(jī)怎么都不響”的情況。我的經(jīng)驗(yàn)是分三部分排查第一看客戶(hù)端有沒(méi)有在線(xiàn)推送回調(diào)。在 HBuilderX 里把 App 跑到真機(jī)打開(kāi)推送的在線(xiàn)接收監(jiān)聽(tīng)如果在線(xiàn)都收不到說(shuō)明長(zhǎng)連接或 appid 配置有問(wèn)題優(yōu)先檢查 manifest 里的推送參數(shù)和打包權(quán)限第二看后臺(tái)推送記錄。UniPush 后臺(tái)有“推送歷史”可以看到一條消息實(shí)際推給了多少設(shè)備、成功多少、失敗多少如果失敗數(shù)很高很可能設(shè)備已經(jīng)走廠(chǎng)商通道但廠(chǎng)商臨時(shí)拒絕第三看廠(chǎng)商平臺(tái)的推送統(tǒng)計(jì)數(shù)據(jù)它們能看到更細(xì)的設(shè)備級(jí)錯(cuò)誤碼。此外我強(qiáng)烈建議準(zhǔn)備一臺(tái)專(zhuān)門(mén)測(cè)試推送的手機(jī)不要用模擬器模擬器根本驗(yàn)證不了廠(chǎng)商通道。測(cè)試時(shí)把 App 從前臺(tái)切到后臺(tái)、再?gòu)?qiáng)制殺掉進(jìn)程分別觀(guān)察通知能不能送達(dá)。很多問(wèn)題只有殺掉進(jìn)程后才暴露出來(lái)比如廠(chǎng)商權(quán)限沒(méi)配好、長(zhǎng)連接被回收后沒(méi)有走離線(xiàn)通道。3. 熱更新機(jī)制資源包與版本鏈路的完整理解3.1 熱更新到底能更新什么推送聊完進(jìn)入另一個(gè)重頭戲熱更新。在我整理這塊內(nèi)容的時(shí)候最大收獲是搞清了一個(gè)邊界——熱更新并不是萬(wàn)能的它只能替換 App 內(nèi)的“非原生代碼資源”。用 Uniapp 開(kāi)發(fā) App 時(shí)最終運(yùn)行包里面既有原生殼的代碼也有通過(guò) JS 層編譯出來(lái)的頁(yè)面和邏輯資源。熱更新能做的是把原來(lái)的 JS 資源替換成新版本比如修改頁(yè)面樣式、調(diào)整業(yè)務(wù)邏輯、修 bug。它不能更新的是原生插件、原生 SDK、以及任何需要重新編譯 Android/iOS 包才能生效的改動(dòng)。比如你集成了一個(gè)原生支付 SDK想升級(jí) SDK 版本這就不能靠熱更新解決必須走常規(guī)發(fā)版。我遇到過(guò)最經(jīng)典的誤區(qū)以為改成“所有代碼都能熱更”于是把原生插件相關(guān)的修改也塞進(jìn)熱更包結(jié)果用戶(hù)端不生效白折騰。所以第一步就是明確熱更包只包含 JS、頁(yè)面結(jié)構(gòu)、圖片資源、部分配置凡是涉及原生能力的都另走發(fā)版流程。3.2 wgt資源包的制作與發(fā)布完整流程熱更新不是發(fā)一個(gè) apk 或 ipa而是發(fā)一個(gè)關(guān)于 Uniapp 的“資源升級(jí)包”也就是擴(kuò)展名是 wgt 的資源包。它的制作流程非常固定第一步在 manifest.json 的“App 常用其他設(shè)置”里配好“原生App資源版本號(hào)”比如 1.0.0。這個(gè)版本號(hào)是整個(gè)熱更新判斷的關(guān)鍵客戶(hù)端會(huì)拿著它與服務(wù)端返回的版本號(hào)做對(duì)比。第二步在 HBuilderX 菜單欄選擇“發(fā)行 → 原生App-制作wgt資源包”編譯完成后會(huì)生成一個(gè) wgt 文件。需要注意這個(gè)文件和正式打包 apk 不同它只含資源不含原生殼。第三步把這個(gè) wgt 文件上傳到你自己的服務(wù)器或靜態(tài)資源存儲(chǔ)位置生成一個(gè)可以下載的地址同時(shí)在你服務(wù)端維護(hù)一個(gè)升級(jí)配置接口返回最新資源版本號(hào)和下載地址。第四步客戶(hù)端在自己?jiǎn)?dòng)或進(jìn)入指定頁(yè)面時(shí)請(qǐng)求升級(jí)接口如果服務(wù)端返回的版本號(hào)比本地的新就下載 wgt 并執(zhí)行安裝。實(shí)際操作中的幾個(gè)細(xì)節(jié)wgt 文件實(shí)際上就是一個(gè) zip 包你甚至可以把后綴改成 zip 解壓出來(lái)看內(nèi)容它應(yīng)該完整保留 App 的資源目錄結(jié)構(gòu)如果目錄結(jié)構(gòu)不對(duì)安裝就會(huì)失敗。另外要留意某些 Android 系統(tǒng)對(duì)安裝包來(lái)源的限制如果用戶(hù)禁止了“允許安裝未知來(lái)源應(yīng)用”wgt 的覆蓋安裝會(huì)失敗需要在業(yè)務(wù)層做好提示。我用一個(gè)簡(jiǎn)單表格總結(jié)一下制作流程階段操作關(guān)鍵點(diǎn)編譯修改 manifest 資源版本號(hào)版本號(hào)不能比線(xiàn)上低打包HBuilderX → 發(fā)行 → 制作wgt資源包確認(rèn)輸出目錄上傳放到靜態(tài)服務(wù)器/CDN記錄下載地址發(fā)布服務(wù)端升級(jí)配置返回新版本號(hào)控制灰度/強(qiáng)制覆蓋安裝客戶(hù)端下載 wgt 并安裝注意目錄結(jié)構(gòu)與安裝來(lái)源權(quán)限3.3 版本號(hào)管理與更新觸發(fā)條件熱更新的核心其實(shí)在“版本號(hào)”。這里的版本號(hào)有兩個(gè)一個(gè)是 App 整體版本號(hào)也就是用戶(hù)在應(yīng)用商店看到的版本例如 2.3.0另一個(gè)是資源版本號(hào)比如 1.0.0。資源版本號(hào)可以獨(dú)立于 App 版本號(hào)變化。日常發(fā)版邏輯通常是這樣改一行頁(yè)面文案 → 資源版本號(hào)從 1.0.0 變到 1.0.1走熱更更換某個(gè)原生插件 → App 版本號(hào)從 2.3.0 變到 2.4.0走商店發(fā)版兩個(gè)都在改那就兩個(gè)版本號(hào)都更新習(xí)慣是先提交商店版本等上架后再推熱更避免用戶(hù)先收到新資源卻發(fā)現(xiàn) App 殼沒(méi)匹配上。更新觸發(fā)條件也不是“每次啟動(dòng)就強(qiáng)制下載”需要設(shè)計(jì)升級(jí)策略。我習(xí)慣在服務(wù)端升級(jí)配置里放三個(gè)字段新版資源版本號(hào)、wgt 下載地址、是否強(qiáng)制升級(jí)。App 啟動(dòng)后先拿本地版本號(hào)去請(qǐng)求升級(jí)接口如果線(xiàn)上版本大于本地版本再根據(jù)是否強(qiáng)制來(lái)決定彈窗還是靜默下載。如果用戶(hù)正在弱網(wǎng)環(huán)境我會(huì)選擇提示“當(dāng)前網(wǎng)絡(luò)不穩(wěn)定是否立即更新”而不是直接開(kāi)始下載 wgt不然下載失敗概率很高。3.4 熱更后的回退與整體更新邏輯熱更不可能永遠(yuǎn)一次成功所以必須把“回退”邏輯想好。我的做法是升級(jí)包下載完成后先安裝到本地如果安裝成功新資源版本號(hào)寫(xiě)入本地緩存如果安裝失敗不能讓用戶(hù)陷入一個(gè)不可用的狀態(tài)要保留上一版資源作為回退。具體代碼層面的邏輯大致是// 偽代碼式邏輯展示升級(jí)判斷和安裝過(guò)程 function checkAppUpdate() { const localVersion uni.getStorageSync(res_version) || 1.0.0 const res await requestUpdateConfig() // 請(qǐng)求升級(jí)接口 if (res.version localVersion) { return // 已是最新版 } uni.showLoading({ title: 更新資源包中 }) const downloadResult await uni.downloadFile({ url: res.wgtUrl }) const installResult await new Promise((resolve) { plus.runtime.install(downloadResult.tempFilePath, { force: true }, resolve, reject) }) if (installResult) { uni.setStorageSync(res_version, res.version) // 提示用戶(hù)重啟或直接重啟 } else { // 保留舊版本資源下次啟動(dòng)再試 } }這里我特別想說(shuō)一個(gè)容易忽略的點(diǎn)熱更安裝成功后并不代表你當(dāng)前的頁(yè)面就立刻變成新版它需要 App 重啟才能全部生效。所以 install 成功之后最好給用戶(hù)一個(gè)“重啟應(yīng)用以應(yīng)用更新”的按鈕或者直接調(diào)用退出當(dāng)前應(yīng)用的接口重新啟動(dòng)。否則用戶(hù)會(huì)以為熱更沒(méi)成功。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 推送場(chǎng)景的典型問(wèn)題與排查推送配置是從“App 完全收不到通知”到“部分手機(jī)收不到通知”這個(gè)過(guò)程中最容易混淆的。我整理了這幾類(lèi)高頻問(wèn)題第一收不到離線(xiàn)通知。這種情況十有八九是廠(chǎng)商通道沒(méi)生效。先看 UniPush 后臺(tái)該設(shè)備有沒(méi)有綁定廠(chǎng)商推送 token如果綁定不了去檢查在某個(gè)安卓大廠(chǎng)平臺(tái)創(chuàng)建的推送服務(wù)是否通過(guò)了審核包名是否和 App 一致。第二能收到通知但點(diǎn)擊后無(wú)法跳轉(zhuǎn)指定頁(yè)面。問(wèn)題出在點(diǎn)擊事件的處理。透?jìng)黝?lèi)消息可以在回調(diào)里自行 navigateTo通知類(lèi)消息要在點(diǎn)擊通知的回調(diào)里解析 extra 數(shù)據(jù)再做跳轉(zhuǎn)。如果這部分代碼寫(xiě)錯(cuò)用戶(hù)每次點(diǎn)擊通知都會(huì)直接進(jìn) App 首頁(yè)。第三通知要么重復(fù)要么完全消失。很多手機(jī)上UniPush 的長(zhǎng)連接消息和廠(chǎng)商通道消息同時(shí)到達(dá)會(huì)造成重復(fù)通知。解決方式一般是設(shè)置消息唯一 ID在客戶(hù)端根據(jù)唯一 ID 去重。這個(gè)功能在很多推送后臺(tái)默認(rèn)不會(huì)開(kāi)啟需要手動(dòng)配置。下面這張速查表是我自己核對(duì)推送問(wèn)題時(shí)的模板分享出來(lái)現(xiàn)象優(yōu)先檢查點(diǎn)常見(jiàn)解決動(dòng)作所有手機(jī)收不到后臺(tái)配置 appid、廠(chǎng)商密鑰重新編譯打包某品牌手機(jī)收不到該廠(chǎng)商通道審核/白名單補(bǔ)報(bào)備或更換推送服務(wù)在線(xiàn)收不到長(zhǎng)連接是否建立檢查網(wǎng)絡(luò)權(quán)限、推送權(quán)限離線(xiàn)收不到廠(chǎng)商通道 token 是否綁定確認(rèn)系統(tǒng)通知權(quán)限開(kāi)啟點(diǎn)擊不跳轉(zhuǎn)點(diǎn)擊回調(diào)邏輯透?jìng)飨⒏挠脴I(yè)務(wù)類(lèi)型判斷重復(fù)收到消息去重在 payload 加唯一 ID4.2 熱更新場(chǎng)景的典型問(wèn)題與排查熱更新的問(wèn)題往往集中在這幾類(lèi)最常見(jiàn)的是“更新了資源版本號(hào)但客戶(hù)端不更新”。這種大多是版本號(hào)比較或請(qǐng)求失敗導(dǎo)致的。比如客戶(hù)端本地緩存版本號(hào)讀取錯(cuò)了或者服務(wù)端接口被 CDN 緩存服務(wù)端返回的還是舊版本號(hào)。我遇到過(guò)升級(jí)配置接口加了緩存頭導(dǎo)致客戶(hù)端半小時(shí)內(nèi)拿到的都是舊配置清掉 CDN 緩存才恢復(fù)正常。第二是“wgt 下載成功但安裝失敗”。這個(gè)大概率是 Android 系統(tǒng)對(duì)“未知來(lái)源”安裝的限制或者 wgt 包本身不完整。務(wù)必將 wgt 包在本地解壓后檢查目錄是否完整也建議在下載 wgt 時(shí)做一次 MD5 校驗(yàn)防止下載到壞包。這個(gè)習(xí)慣我是在一次正式發(fā)布后學(xué)到的教訓(xùn)當(dāng)時(shí)資源包上傳到服務(wù)器被壓縮工具改了二進(jìn)制導(dǎo)致全量用戶(hù)安裝失敗從那以后我每次發(fā)版都校驗(yàn) hash。第三是“熱更后白屏或部分頁(yè)面報(bào)錯(cuò)”。這個(gè)通常是 JS 資源包和原生殼版本不匹配。新版 JS 用了舊版原生殼不支持的 API一旦出現(xiàn)這類(lèi)問(wèn)題基本只能重新發(fā)布兼容舊殼的資源包或者引導(dǎo)用戶(hù)走應(yīng)用商店升級(jí)。所以從設(shè)計(jì)上就應(yīng)該避免熱更包依賴(lài)最新原生 API。4.3 我建議的日常維護(hù)流程基于這些踩坑經(jīng)驗(yàn)我現(xiàn)在給自己的項(xiàng)目定了一套“發(fā)布檢查清單”每次推送和熱更新都按它過(guò)一遍也算是我后續(xù)一直沿用的日常維護(hù)流程第一步準(zhǔn)備一臺(tái)測(cè)試手機(jī)專(zhuān)門(mén)用來(lái)驗(yàn)證推送和熱更。這臺(tái)手機(jī)不要裝太多后臺(tái)應(yīng)用便于觀(guān)察廠(chǎng)商通道是否被系統(tǒng)清理。第二步推送發(fā)布前先把 App 殺進(jìn)程驗(yàn)證一遍離線(xiàn)推送確認(rèn)通知欄有消息后再全量發(fā)送。線(xiàn)上推送最好用“定時(shí)推送”或“分批推送”避免消息過(guò)多導(dǎo)致廠(chǎng)商通道觸發(fā)限流。第三步熱更發(fā)布前將 wgt 包上傳到測(cè)試環(huán)境模擬一個(gè)新版本資源走一遍“檢測(cè)更新、下載安裝、重啟生效”的完整流程確認(rèn)無(wú)誤后再切線(xiàn)上升級(jí)配置。第四步線(xiàn)上出問(wèn)題時(shí)優(yōu)先確認(rèn)服務(wù)端配置和版本號(hào)是否正常不要先懷疑客戶(hù)端代碼。大部分“沒(méi)收到更新”的問(wèn)題排查到最后都指向 CDN 緩存或接口返回了舊版本號(hào)。寫(xiě)在最后的實(shí)際體會(huì)內(nèi)容說(shuō)到這基本把推送和熱更新的關(guān)鍵鏈路都拆開(kāi)了。最后分享一個(gè)我自己在項(xiàng)目里試過(guò)的小技巧給熱更新接口增加一個(gè)“簽名參數(shù)”比如把版本號(hào)加上固定密鑰做 MD5 后拼到 URL 上服務(wù)端校驗(yàn)簽名才返回配置。這樣能防止某些環(huán)境下升級(jí)接口被人惡意刷取雖然會(huì)加一點(diǎn)開(kāi)發(fā)量但線(xiàn)上跑起來(lái)會(huì)更穩(wěn)。另外每次熱更新之前我習(xí)慣先在真機(jī)上完整走一遍老版本到新版本的升級(jí)過(guò)程確定回退路徑可靠后再切全量。這比任何文檔都管用。希望這份學(xué)習(xí)筆記能讓你在真正做 Uniapp 跨端 App 推送與熱更新時(shí)少踩幾個(gè)坑早點(diǎn)把精力放回業(yè)務(wù)本身。