別再天真想「直接熱更」了,這裡有一堆坑在等你
坦白說,剛開始玩 Hybrid App 的時候,很多人都以為 CodePush 就像更新一個網頁檔案那麼簡單,按個鈕新內容就上線。真相?完全不是這回事。一旦動手做了,才發現根本是挖了一個自己都跳不出去的洞:平台版本、快取殘留、原生行為、Silent Push、還有那些鬼打牆的相容性。
我遇過直接把 App 弄成「磚塊」的情況——就是用戶打開之後只會卡在啟動畫面,完全進不去,唯一的解法竟然只有刪掉重裝。
原本以為熱更就 plug & play,結果先被 Cordova 教做人
最早期玩的是 Cordova + 熱更 plugin,一切看起來簡單:App 啟動 → 比對版本 → 下載 zip → 解壓應用。
但實際上這流程有夠鈍,下載跟解壓又慢又常失敗,遇到問題只能請用戶「重安裝」。Web bundle 爆掉的話,plugin 還直接報廢,根本沒救援空間。
所以老實講,那種只靠第三方 plugin 想交差的心態,最後都會被各種例外狀況打臉。
一個版本升級,整批 App 全磚:這種教訓真的是記一輩子
Capacitor v3 版更新的災難:移到 Capacitor 之後,想說用微軟的 CodePush plugin 來做,結果一次平台升級就直接爆掉——只要有升級的用戶,全部卡在啟動畫面動不了。
後來才發現根源很「單純」:新版 Native Binary 上線時,沒清掉舊的快取網頁檔。結果就是新舊混在一起大打架,用戶就只能等著看 spinner 永無止境地轉。
臨時救火:Android 快點修掉、iOS 也盡量趕,還是有用戶得自己清資料。這時候支援信箱、應用商店留言就像爆水管一樣湧進來。
後來都怎麼做?只要 App 有新版本裝上去,第一步一定是砍掉所有快取,保證用的就是內建資產,這才徹底防堵「新 Native + 舊 Web」衝突。
進階一點:自己動手做完整流程,什麼「Rx」、什麼「主流程」全都來
原生 CodePush 自己寫一輪:學乖了之後,決定直接自己做。流程其實蠻有儀式感的:
- 啟動時向後端要 config,確認有無新版本
- 做一輪版本跟相容性檢查
- 過了才下載 zip,解壓到一個新資料夾
- 必要時(強制更新)直接 reload,不然就等 App 進背景才動作
本來想偷懶用「App 進入背景延遲一會再 reload」這種做法,實際上卻踩到大地雷:有的 plugin 會直接開 Native 畫面(像是 Android 會額外啟動 Activity),WebView reload 時用戶可能在拍照或選圖,結果一回來什麼資料都沒了。
只好把判斷條件更嚴謹,只在「真的離開主畫面」才更新,不然會害人想砸手機。
偷偷更新這件事,做得好超爽,做壞就會自爆
Silent Push 更新:如果你以為用 Firebase 的靜默推播能優雅更新一切,實際操作後會發現坑還是一堆。流程大概是:推播送來資料,App 收到後直接開始下載和更新,整個過程用戶其實一點都感覺不到。
但問題來了:iOS 上一不小心會自殘。我看過同一時間有一大票裝置在背景自動開啟 App,然後後端伺服器直接被自己人 DDoS,監控圖瞬間拉到天花板。
解法很「反直覺」:更新的時候根本不要載入 Dashboard,直接給一張空白頁,等真的用戶開 App 再啟動所有請求。不然就變成自己玩自己。
瘦身這件事,不是口號:從 20MB 爆量減到 1–6MB 才是關鍵
你會發現每次都上傳整包 ZIP(20MB 上下)其實很沒效率。後來的做法就是用 manifest 做差異包,只有變動的檔案會被推送下去,下載速度立刻感覺變快,也比較不會因為網路抖動就失敗。
實際壓到每次更新只要 1-6MB,手機用戶在 4G 甚至 3G 的情境都撐得住,沒有人愛等下載一大包才看到新內容啦。
這種看起來小小的「更新設計」,其實是架構中的地基
現在我們怎麼做:已經不再用那種「每一版都開一個新資料夾」的方式。只保留一個工作目錄+一份備份。更新時先備份,再直接把差異檔覆蓋過去,失敗再復原。再怎麼爛,也還有 binary 內建的預設資產可以 fallback。
簡單比喻:像是裝修老公寓,備份一份舊牆皮,萬一新漆掉漆就直接恢復,絕對不會讓房子直接倒掉。
用戶層感覺是順的,開發維護邏輯簡單很多,心情也平穩不少。
再講深一點,原生跟 Web 之間的「同步守則」很重要
不是有 plugin 就萬事 OK:這整套機制,其實早就不只是單純的「熱更」功能,完全變成了一個獨立的子系統。
CodePush 不只是「外掛」,而是整個架構的骨幹。你如果不當一回事,出事時真的會叫不敢大聲。
幾個重點守則:
- 一定要設最小版本,不然老 binary 裝上去直接 crash。
- 強制更新的版本一到,直接套用,避免 API 相容問題。
- 只要 binary 版號換了,快取要徹底清除,讓新舊環境乾乾淨淨。
- 更新失敗要能回滾,最慘也得 fallback 預設資產,這樣至少能保證 App 能正常啟動。
- Silent Update 要避免造成後端流量爆衝,不然自爆給你看。
- 減少傳輸量、只推送 diff,對用戶跟後台都輕鬆很多。
說明白一點:這些原則才是讓 App 可以長期穩定運作的關鍵,不只是架構師在意,PM、QA、甚至客服都會感謝你。
還有很多細節沒講,這裡只抓大重點
其實還沒說完:上面這些只是比較「顯眼」的大坑。還有像 CSP 安全策略、WebView 跟 WebViewClient 的那些眉角、原生錯誤攔截、自定義 bridge 出錯時的處理等等,每一項都可能搞到整套 CodePush 不能用。
如果你有遇到一些很怪的 bug,通常都是這種枝微末節的小東西在鬼混。
小結一下——不是什麼開箱即用的 Plug-in,背後一堆眉眉角角,要自己慢慢踩坑修正
| 做法/規則 | 踩過的坑 | 為什麼這樣設計 |
| 新 Binary 上線時快取全清 | 「磚塊」App,舊快取跟新版衝突 | 保證新版 Native 配新版 Web,乾淨啟動 |
| 只保留一份工作資料夾+備份 | 資料夾爆炸,版本切換易出錯 | 簡化邏輯、方便 rollback、不怕亂流 |
| Silent Push 更新預設空白頁 | 後台伺服器流量飆高被自己人 DDoS | 不讓 Dashboard 背景發請求,穩定更新 |
| 只推 Diff 包,不推全包 | 行動網路慢、更新失敗多 | 用戶等得少,錯誤機率降低 |
說真的,我自己覺得…這些血淚規則你最好筆記下來
台灣市場的小提醒:PTT 上有不少人會問:「我的 Hybrid App 怎麼一直重開還是舊的?」其實多半都是快取沒清乾淨,或是 silent update 把 server 弄掛。
像 Dcard、Momo 這種高流量服務,通常都會有自己的一套快取控制跟 rollback 機制,不會只靠預設的 CodePush 或 plugin。
「你以為只是一個熱更,實際上是要把整個 App 當作一個會呼吸、有成長曲線的系統去對待。」
還有啊,我覺得最重要的是要有「落跑通道」:只要任何流程有失敗機會,Backup 機制永遠都不能少。千萬別以為不會發生,搞砸一次你就知道了。
有卡關嗎?先挑一個地方動手,感覺有動起來就好了
這麼多眉角,先挑一個最讓你困擾的來處理,不用想一次做到完美。比如先把「新 Binary 上線時快取全清」這一件事做起來,其他問題後面慢慢解決也來得及。
如果你遇過更奇葩的 Bug,真的想聽聽大家踩過什麼雷,來對一下到底還有哪種死法沒遇過!
