廠商說不支援 URL Scheme:跨 App 綁定從自訂協議遷移到 Universal Link 的完整實錄

廠商一句話,整個回調機制要重寫 我們的健康管理 App 整合了一台第三方氣酮檢測裝置。綁定流程很單純:使用者從我們的 App 跳到廠商的 App 完成綁定,綁定完成後跳回來,顯示成功或失敗。 原本的回調方式是 URL Scheme: myapp://device-bindback?status=success iOS 上一直運作正常,直到廠商的 Android App 更新後回報:他們不支援 URL Scheme 回調,要求改用 Universal Link。 這不是改一行 URL 的事。Universal Link 牽涉到網域驗證、靜態檔案部署、平台設定、安全機制,幾乎是把整個回調架構重新設計。 在深入之前,先釐清幾個關鍵術語: 術語 說明 URL Scheme 自訂的 URL 前綴(如 myapp://),讓其他 App 可以透過這個 URL 開啟你的 App。缺點是任何 App 都可以註冊相同的 scheme,沒有驗證機制。 Universal Link(iOS) Apple 的機制,把 HTTPS URL 和特定 App 綁定。系統透過 AASA 檔案驗證網域和 App 的關聯,確保只有合法的 App 能攔截該 URL。 App Links(Android) Google 對應 Universal Link 的機制,透過 assetlinks.json 驗證網域和 App 的關聯,原理相同。 AASA Apple App Site Association,放在 https://你的網域/.well-known/apple-app-site-association 的 JSON 檔案,告訴 iOS「哪些路徑要交給哪個 App 開啟」。 assetlinks.json Android 版的 AASA,放在 https://你的網域/.well-known/assetlinks.json,功能相同。 Provisioning Profile Apple 的簽名設定檔,記錄 App 被允許使用哪些功能(如 Push Notifications、Associated Domains)。新增功能時必須重新產生。 Deep Link 泛指所有能從外部開啟 App 並導到特定頁面的連結,URL Scheme 和 Universal Link 都是 Deep Link 的實作方式。 SHA-256 指紋 App 簽名憑證的雜湊值(32 組 hex,如 AA:BB:CC:...),用來唯一識別一個 App 的簽名身份。assetlinks.json 靠它比對「宣稱的 App」和「裝置上的 App」是否同一個。 Upload Key 開發者本地 keystore 的私鑰,用來簽名上傳到 Play Console 的 AAB/APK。本地開發、CI、模擬器都用這把。 App Signing Key Google Play 持有的私鑰,用來對上架版 App 做最終簽名。使用者從 Play Store 安裝的 App 是這把 key 簽的,指紋跟 upload key 不同。 改動前後的流程差異,用一張圖看最清楚: ...

April 10, 2026 · 5 分鐘 · Peter

Google Play 警告消不掉:Fastlane 上傳 Native Debug Symbols 的三個陷阱

那個怎麼都消不掉的警告 Google Play Console 上掛著一則警告:「App Bundle 含有原生程式碼,而您尚未上傳偵錯符號檔」。 先釐清幾個名詞,因為 Google Play 的警告訊息把不同的東西混在一起講: 檔案 用途 來源 Native Debug Symbols (.so) 還原 C/C++ native crash 的堆疊追蹤 Flutter 引擎、NDK、第三方 SDK R8 Mapping (mapping.txt) 還原 Java/Kotlin 被 R8 混淆後的類別名稱 Gradle build(minifyEnabled true) 兩者都要上傳,Google Play 才不會抱怨。Flutter 專案兩者都需要:引擎帶了 .so,Gradle 開了 R8。 整體流程:Build → Detect → Upload 在看個別陷阱前,先理解正確的 CI 流程應該長什麼樣: 關鍵原則:上傳邏輯綁定 build 產物,不綁定部署環境。只要 build 裡有 .so 或 mapping.txt,就上傳。不管是 staging 還是 production。 但從這個目標到實際跑通,踩了三個完全不同性質的坑。 陷阱一:File.expand_path 的基準不是你以為的那個 第一版的 Fastfile 寫死了路徑: ...

February 24, 2026 · 2 分鐘 · Peter

15 次 Build Failed:一場 Jenkins + Flutter CI/CD 的史詩級除錯之旅

前言:當 Build Failed 成為日常 在過去的 19 個小時裡,我經歷了 15 次 build failed,產生了 15 個 fix commits。如果你覺得這很誇張,讓我告訴你更誇張的:最後一個 bug 是 git describe 在多個 tag 指向同一 commit 時會隨機返回其中一個。 是的,隨機。在 CI/CD Pipeline 裡。 這篇文章完整記錄這場除錯馬拉松,從最初的 Fastlane 版本問題,到 Discord 通知功能的實作與修復,再到 Ruby 相容性地獄,最後揭開 git 鮮為人知的行為。泡杯咖啡,這會是一段旅程。 第一章:Fastlane 與 Bundler 的糾葛 問題 1:Fastlane 版本不一致 Commit: fix(jenkins): use bundle exec for fastlane to ensure version consistency Jenkins 機器上有全域安裝的 Fastlane,但版本與 Gemfile.lock 指定的不同。這導致某些 action 行為不一致。 // Before: 使用全域 fastlane sh 'fastlane ios build' // After: 透過 Bundler 執行,確保版本一致 sh 'bundle exec fastlane ios build' 學習:在 CI 環境中,永遠使用 bundle exec 執行 Ruby 工具,確保版本與 lockfile 一致。 ...

December 21, 2025 · 5 分鐘 · Peter