Skip to content

App 啟動

最後更新:2026-04-11


功能說明

App 從冷啟動(完全關閉狀態重新打開)到使用者可操作主畫面的完整流程。啟動分兩大階段:

  1. 系統初始化 — 設定預設域名、探測最快可用域名、初始化第三方服務
  2. 登入判斷 — 根據登入狀態決定自動登入或顯示登入頁

使用者流程

  1. 使用者點擊 App icon → App 冷啟動
  2. 背景自動探測最快可用域名(使用者不感知)
  3. 如果之前登入過且登入狀態還有效 → 自動靜默登入,直接進主畫面
  4. 如果登入狀態過期或從未登入 → 顯示登入/注冊頁
  5. 登入成功 → 切換至主畫面(TabBar)

頁面跳轉

  • App 冷啟動(登入狀態有效)→ 自動登入 → 主畫面
  • App 冷啟動(登入狀態無效)→ 登入/注冊頁 → 登入成功 → 主畫面
  • App 維護中 → 停在維護頁,不進入主畫面

技術視角(開發看這裡)

相關檔案

類型檔案路徑
AppDelegateBBSport/AppDelegate/AppDelegate.swift
SceneDelegateBBSport/AppDelegate/SceneDelegate.swift
域名探測管理器BBSport/Tools/STDomainListManager/STDomainListManager.swift
域名候選清單 JSONBBSport/Tools/Domain/M/domain.json
域名探測配置 JSONBBSport/Tools/STDomainListManager/st_domain_config.json
完整檔案清單
類型檔案路徑
AppDelegate 推送擴展BBSport/AppDelegate/AppDelegate+JPPush.swift
AppDelegate SDK 擴展BBSport/AppDelegate/AppDelegate+SDK.swift
登入服務(OC)BBSport/Tab/我的/Mine/Login_Register/IFLoginService.m
登入服務 HeaderBBSport/Tab/我的/Mine/Login_Register/IFLoginService.h
維護管理BBSport/Tools/MaintenanceManager/GameMaintenanceManager.swift
域名預設值定義BBSport/Tools/Domain/M/AppDomain.swift
環境類型定義BBSport/Tools/Domain/M/EnvironmentType.swift
域名回應 Model(OC)BBSport/Tools/RouterComponent/Classes/Main/BBUrlListModel.h
域名路由開關(OC)BBSport/Tools/RouterComponent/Classes/Main/IFMainSwitch.m
Base URL 取得(OC)BBSport/Tools/UtilityToolComponentOC/Classes/Utils/IFCommonUtil.m
三方 SDK 初始化BBSport/Tools/STThirdPartyManager/STThirdPartyManager.swift
極光推送 HeaderBBSport/Tools/Push/JPPService.h
路由註冊(OC)BBSport/Tab/我的/Mine/Login_Register/ModuleApi.m
通知定義BBSport/Tools/NotificationManager/STNotifyExtension.swift

API

功能說明NamespaceEndpointMethod主要參數
域名探測(找最快域名用)api/forehead/system/domain/list/v2POSTdevice-id, os-type, timestamp, version, sign, pid=bb
投注自訂參數STAPISTAPI.BetListParameterPOST
充值運營圖STAPIapi/forehead/system/config/images/queryPOST
分享螢幕圖STAPIapi/forehead/common/pic/loadPOST
好友域名STAPISTAPI.FriendDomainRequestPOST
邀請碼STAPISTAPI.InviteCodeRequestPOST
產品配置(維護狀態)STAPIapi/forehead/gamebet/product/productConfigPOST

啟動整體時序圖(先看全局)

左邊(AppDelegate)做域名探測,右邊(SceneDelegate)同時做登入判斷,兩件事是並行的。下面逐步展開每個環節。


域名選定:從打開 App 到確定 Base URL 的完整過程

這是啟動流程中最核心的部分。App 要能跟後端溝通,必須先知道「要打哪個 Host」。以下逐步說明。

先搞懂:兩份配置檔的差別

App 裡有兩份域名 JSON,用途完全不同:

檔案位置用途使用場景
domain.jsonTools/Domain/M/定義 AppDomain.shared.root 的預設值(分正式/UAT/測試三個環境),只有 gateway 一種域名開發測試用:DEBUG 模式下直接用這份域名,透過切換 STEnvironment 指向不同環境,不走域名探測
st_domain_config.jsonTools/STDomainListManager/定義域名探測用的候選清單(realese_domains)、探測 API 路徑、OSS 兜底地址正式發布用:Release 模式下的域名探測完全依賴這份配置,線上用戶的域名來源就是這裡

排查線上問題看哪份?

st_domain_config.json。線上(Release)環境的域名探測流程全部走這份檔案,包括:

  • realese_domains 裡的候選域名是否都能連通
  • OSS 兜底地址是否正常
  • 探測 API 路徑 api/forehead/system/domain/list/v2 回應是否正確

domain.json 只影響開發人員本地 DEBUG 模式,與線上用戶無關。

域名選定總覽

第 1 步:讀取本地預設域名(同步,毫秒級)

時機AppDelegate.didFinishLaunchingWithOptions 一開始

做了什麼AppDomain.shared.setDefaultUrls()

domain.json 的完整內容:

json
[{
  "title": "Root域名,用来获取服务端配置的域名",
  "type": 0,
  "domains": [
    { "title": "正式", "url": "https://m.bbtyv16.com" },
    { "title": "UAT",  "url": "https://m.bbuat2021.com" },
    { "title": "测试", "url": "http://m.bbtstxqm7.com" }
  ]
}]

此時的狀態

AppDomain.shared.root 有值了(例如正式環境 = https://m.bbtyv16.com)。 但大部分 API 請求最終是透過 IFMainSwitch(ObjC BBUrlListModel)取域名,不是從 AppDomain 取。

注意:App 裡有兩套域名類型系統

App 有 Swift DomainType(9 種)和 ObjC UrlType(19 種)兩套獨立的域名編號系統,編號不同、用途也不同。線上實際的域名走 ObjC UrlType,Swift DomainType 主要用於開發環境切換和少數 Release 功能。

詳見文末附錄,完整 UrlType 盤點見 域名總覽

domain.json 只配了主域名(root)一種預設值,其他域名首次安裝時是空的,要等 Step 4 域名探測成功後才有值。

第 2 步:載入上次存的域名清單(同步)

做了什麼IFMainSwitch.getURLListFromLocal()

  • UserDefaults 讀取上次成功拿到的域名清單(BBUrlListModel
  • 這份清單包含所有類型的域名(主域名、推送域名、聊天室域名 ... 共 20+ 種 UrlType
  • 目的是讓 App 在還沒聯網之前就有一組可用域名

首次安裝

首次安裝時,UserDefaults 裡沒有任何域名清單。BBUrlListModel 所有欄位都是空陣列,直到 Step 4 域名探測成功後才會被填入。

第 3 步:配置域名探測參數(同步)

做了什麼STDomainListManager.shared.registerDomainList(with: readDomainConfig())

st_domain_config.json 讀取探測所需的配置:

json
{
  "api_domain_path": "/api/forehead/system/domain/list/v2",
  "aliyun_oss_url": [
    "https://ui26ftj02lvf.qodmazcn53.com/bb99/bbpub.json",
    "https://cu02ftj02lvf3.pukyazpv57.com/bb99/bbpub.json",
    "https://ou34ftj02lvf2.qkdoazbi55.com/bb99/bbpub.json"
  ],
  "realese_domains": [
    "https://m-nu42sdu3xfca6.kzpw72mndq19.com",
    "https://m-ayhc45u7p3.iabf52qmhg7.com",
    "https://m-4ytsuxn33i1i0ca3.vafrazwu44.com",
    "https://m-djwnsdfs09a6z3m2.vshwazor54.com",
    "https://m-5xov436trc5b0j.wejaazhp47.com",
    "https://m-uthb76i2c2.zljzazmm59.com"
  ],
  "debug_domains": [
    "https://m.bbmobileproaz.com",
    "http://m.bbtstxqm7.com",
    "https://m.bbuat2021.com",
    "http://m.bbgamedev.com",
    "http://m.bbsama.com",
    "http://www.bbdev.com",
    "http://www.seeknewt.com"
  ]
}
欄位說明
api_domain_path拼在每個候選域名後面的路徑,用來測試該域名是否能通
realese_domainsRelease 版的候選域名列表(6 個)— 首次安裝時,這是唯一的域名來源
debug_domainsDebug 版的候選域名列表(7 個),目前沒有被 startFindFastDomain 使用
aliyun_oss_url阿里雲 OSS 備援地址(3 個),域名全掛時用來取得 IP 清單兜底

第 4 步:併發探測最快域名(異步,核心邏輯)

做了什麼STDomainListManager.shared.startFindFastDomain(completion:)

這是整個域名選定的核心。

4-1. 組合候選域名列表

候選清單的組合邏輯會因為「是否首次安裝」而不同。

BTHostList.plist 是域名探測成功後存到本地的主域名清單(Documents 目錄),下次啟動時直接讀取作為候選,不用從零開始探測。首次安裝時這個檔案不存在。

各場景下的候選清單來源:

場景候選清單內容域名數量
首次安裝(Release)只有 st_domain_config.jsonrealese_domains6 個
非首次安裝(Release)本地快取 + realese_domains(去重)+ 上次最快域名約 6~10 個
DEBUG 模式只有 AppDomain.shared.root(來自 domain.json1 個

4-2. 併發探測 + 處理結果

「最先回來的就贏」:多個候選域名同時打,第一個成功回應的就被選為 Base URL,後面回來的都忽略。

成功回應後做了什麼

回應的 JSON data 陣列包含各類型域名(type 0~36),處理方式:

  1. IFMainSwitch.setUpURLList(data) — 解析回應,存到 BBUrlListModel,所有 20+ 種域名一次更新,同時存入 UserDefaults 供下次啟動使用
  2. 存到本地檔案 BTHostList.plist — 主域名(type 1)清單寫入 Documents 目錄,下次啟動時 Step 4-1 讀取
  3. 更新客服域名 — 如果回應包含 type=15(主線客服),更新 AppDomain.shared.serviceDomain 並重新初始化客服 SDK
  4. STDomainListManager.fastMainDomain = 最快域名 — 記錄在記憶體,後續所有 API 都用這個 Host

域名探測走 IP 兜底時的特殊標記

如果候選域名全掛,改用 OSS IP 探測成功:

  • 會在 UserDefaults 存一個 IPHOST 標記(= 成功的 IP 域名)
  • 不會存入 KEY_MAIN_DOMAIN(因為 IP 不穩定,下次不應優先用)
  • IPHOST 標記會影響活動 URL 拼接邏輯(改用 IFMainSwitch.getActivityUrl 而非正常拼法)

正常域名探測成功時:

  • 存入 KEY_MAIN_DOMAIN
  • 清除 IPHOST 標記

第 5 步:域名就緒,觸發業務請求(異步)

域名選定成功後(在 startFindFastDomain 的 completion 裡),並行發出:

請求說明
STAPI.BetListParameter自訂投注參數
RechargeOperatePicRequest充值運營圖
OperatePicRequest分享螢幕圖
LiveComponent.registerLiveServices()直播服務註冊
SportManager.registerServices()體育服務路由註冊
LotteryConfigManager.registerServices()彩票服務路由註冊
DDConfigManager.registerServices()客服 SDK 初始化

關鍵:上面這些都依賴域名探測完成。如果域名探測失敗,這些請求不會發出。

首次安裝的代理綁定邏輯

域名探測成功的 completion 裡還有一段首次開啟專屬邏輯

首次開啟的 getBaseUrl() 用的是哪個域名?

IFCommonUtil.getBaseUrl() 的取值優先順序:

  1. STDomainListManager.fastMainDomain(記憶體中的最快域名)
  2. UserDefaults KEY_MAIN_DOMAIN(上次存的最快域名)
  3. normalDefaultDomains.firstst_domain_config.jsonrealese_domains 第一個)

首次安裝時 1 和 2 都是空的,所以用的是 realese_domains 第一個域名。 但這段代碼在 startFindFastDomain 的 completion 裡,此時 fastMainDomain 已經有值了,所以實際上用的是剛探測成功的最快域名

第 6 步:SceneDelegate 判斷登入狀態(與域名探測並行)

時機SceneDelegate.scene(willConnectTo:) — 與 AppDelegate 的域名探測幾乎同時執行

首次安裝

首次安裝時 KEY_TOKEN 不存在,一定走「顯示登入頁」分支。

注意

域名探測和登入判斷是並行的。使用者可能已經進入主畫面了,域名探測才完成。但業務 API 請求(投注參數、充值運營位等)會等域名就緒才打。

各場景域名來源總結

場景配置檔AppDomain.shared.root 來源域名探測候選清單來源最終 Base URL 來源
首次安裝(Release)st_domain_config.jsondomain.json(正式環境域名)realese_domains(6 個)探測最快的那個 realese_domains
非首次安裝(Release)st_domain_config.jsondomain.json(但會被之前存的覆蓋)本地快取 + realese_domains + 上次最快域名探測最快的域名
域名全掛 + OSS 兜底成功st_domain_config.json同上OSS 下載的 IP 清單成功的 IP
域名全掛 + OSS 也掛st_domain_config.json同上探測失敗,業務請求無法發出
DEBUG 模式 🔧domain.jsonSTEnvironment 切換直接用 AppDomain.shared.root不探測domain.json 裡的域名

Release 場景全部走 st_domain_config.json,線上問題排查看這份。domain.json 僅供開發人員 DEBUG 模式切換環境用。

IFCommonUtil.getBaseUrl() — 全域 Base URL 取得邏輯

整個 App 取得 Base URL 都透過 IFCommonUtil.getBaseUrl(),它的優先順序:

在 DEBUG 模式下,getFastMainDomain() 會直接回傳 AppDomain.shared.root,跳過上面所有判斷。

注意

getDeviceId()(GuardianHelper)在 startFindFastDomain 之前就呼叫了,它也用 getBaseUrl()。首次安裝時 fastMainDomain 和 KEY_MAIN_DOMAIN 都是空的,所以 getDeviceId 用的是 realese_domains 第一個域名。


實作重點

  1. 域名探測是前置條件startFindFastDomain 成功後才觸發核心業務請求(投注參數、充值運營位等),域名探測失敗則後續請求無法進行
  2. 三層兜底機制:本地快取域名 → 配置檔候選域名(realese_domains)→ 阿里雲 OSS IP 地址
  3. 首次安裝只靠 realese_domains:沒有任何快取可用,6 個寫死在 st_domain_config.json 的域名是唯一來源
  4. DEBUG 模式跳過探測:直接使用 AppDomain.shared.rootdomain.json 裡設定的域名)
  5. Token 自動登入SceneDelegate 判斷 KEY_TOKEN 存在且 TOKENTIME 未過期,直接走 LoginByToken 免登入
  6. 前後台輪詢管理:進入背景 STPauseAllPolling()、回到前台 STResumeAllPolling(),避免背景無效請求
  7. 登出統一收口:無論手動登出、被踢、平台失效、重連失敗,都走 GameStatusManager.logout()resetProviders()STNotify.logout.post()

setUpChangeWindows() 切換到主畫面邏輯

情境 A:從已有頁面 push 出的登入頁(isShowingLoginVC = true

  1. dismiss 登入頁
  2. 已登入 → 延遲 0.1s → STNotify.logged.post()

情境 B:冷啟動的登入頁(isShowingLoginVC = false

  1. 檢查 APP 級別維護 → 維護中直接 return(停在維護頁)
  2. STNotify.changeKeyWindow.post() → SceneDelegate 收到 → window.rootViewController = MainViewController()
  3. 已登入 → 延遲 0.2s → STNotify.logged.post()

👉 所有寫死在本地的域名、IP、金鑰清單已移至獨立頁面:域名總覽


登入成功後的 API 呼叫


附錄:兩套域名類型系統

App 裡有兩套獨立的域名 enum,編號不同,容易搞混:

  • ObjC UrlType(BBUrlListModel,19 種定義)— 線上實際走這套,伺服器回應 → BBUrlListModel → IFMainSwitch getter → API 請求
  • Swift DomainType(AppDomain,9 種)— 主要給開發環境切換用,少數 Release 功能也會直接讀

編號對照表

域名Swift DomainTypeObjC UrlType
推送.push = 3PushUrl = 2
聊天.chat = 2ChatSocketUrl = 6
賽果.result = 5MatchResultUrl = 8
代理.proxy = 7ProxyUrl = 12

AppDomain 管理的 9 種域名

屬性用途DomainType對應的 ObjC UrlType
root主 API 域名.gateway (0)無直接對應
pxdd雷速數據.leisu (1)PXDDUrl (18)
chat聊天室.chat (2)ChatSocketUrl (6)
push推送.push (3)PushUrl (2)
h5廣場/資訊.h5 (4)SportInformationUrl (11)
result賽果.result (5)MatchResultUrl (8)
dataH5詳情頁 H5.dataH5 (6)H5Url (36)
proxy代理.proxy (7)ProxyUrl (12)
serviceDomain客服.serviceDomain (8)ServiceMainLine (15)

AppDomain 在 Release 也有人用

AppDomain.shared.root 不是只給 DEBUG 用。以下 Release code 也直接讀它:

  • CustomerChatManager.swift:210, 819 — 客服 URL 拼接
  • ExponentView.swift:27 — 體育指數

AppDomain.shared.serviceDomain 會被伺服器回應的 UrlType 15 動態更新。

死 code

getUrlByUrlType() 試圖橋接兩套系統,但全專案無任何呼叫,是死 code。

完整的 UrlType 盤點見 域名總覽