本章介紹在行動系統設計 (Mobile System Design, MSD) 面試中經常出現的關鍵建構模組 (building blocks)。對於每個主題,我們會定義概念、說明何時使用它,並探討其他選項與取捨。
本章涵蓋以下主題:
- 架構 (Architecture):常見模式、架構分層、模組化 (modularization) 與測試。
- 資料管理 (Data management):資料儲存、網路 (networking)、身分驗證 (authentication)、分頁 (pagination) 與效能。
- 功能開發 (Feature development):發布策略、強制升級 (force upgrading)、功能旗標 (feature flags)、遠端設定 (remote config)、A/B 測試、可觀測性 (observability) 與分析、在地化 (localization)、隱私、無障礙 (accessibility)、推播通知、App 大小與 CI/CD。
- 支援不同裝置:不同外型規格 (form factors),以及 OS 與 SDK 版本。
架構 (Architecture)#
一個設計良好的架構在行動開發中有兩個重要目的:它為建立穩健且可維護的應用程式提供基礎,同時也建立一種共通語言,讓開發者能有效協作。
行動應用程式可以用多種架構方法來建構。其中最廣泛採用的包含 Clean Architecture 以及 Google 的 App 架構指南,後者強調 單向資料流 (Unidirectional Data Flow, UDF) 以及 儲存庫模式 (repository pattern)。我們也能找到各種以 UI 為核心的架構,包括常見的 MVC、MVVM、MVI 或其他類似 Redux 的模式;以及較為專門的方法,例如 VIPER、Uber 的 RIBs、Airbnb 的 Mavericks 或 Slack 的 Circuit 函式庫。這些 UI 層架構通常會與其他模式搭配,形成完整的解決方案。
讓我們探討面試中可能遇到的關鍵架構概念。我們會先從常見模式開始,檢視它們如何套用到 UI 與資料層(遵循 Google 的架構指南)。接著再延伸到相關主題,示範如何有效地思考與討論架構決策。
常見架構模式#
雖然組織行動應用程式的方法有很多,但某些架構原則對於建立穩健且可擴展 (scalable) 的解決方案是不可或缺的。以下是一些能強化面試回答的重要模式。
關注點分離 (Separation of Concerns)#
良好架構的基本原則之一是把 UI 邏輯與程式碼的其他部分分離。與其把所有東西都塞進 UI 層,每個元件應該各司其職。UI 類別應該只處理它該做的事:顯示內容與回應使用者互動。
- 透過將責任分散到不同元件,程式碼變得更乾淨且更易於維護。
- 使測試更容易,因為我們可以獨立驗證每個元件。
- 有助於避免與 UI 生命週期相關的常見陷阱,因為商業邏輯獨立於 UI 相關的關注點。
單向資料流 (Unidirectional Data Flow)#
單向資料流 (UDF) 模式透過強制單一方向的流動,為資料在應用程式中的移動建立了可預測的方式。其運作方式如下:
資料從一個來源(例如資料庫)開始,流經應用程式直到最終目的地(通常是 UI)。當資料經過不同層時,每一層都可以根據商業邏輯進行轉換。當某處需要變更資料時,它會送出事件 (events) 反向流回來源。來源則暴露出方法讓其他部分請求這些變更。
這種單向流動(如圖 1 所示)帶來幾個關鍵優點:
- 它使資料變更更可預測,因為變更只能透過已定義的路徑發生。
- 透過限制資料可被修改的位置與方式,減少 bug。
- 簡化除錯 (debugging),因為我們可以精確追蹤資料如何在應用程式中流動。
反應式程式設計 (Reactive Programming)#
本書中,我們使用 反應式程式設計 (reactive programming) 來實作單向資料流。其核心是建立一個資料流 (stream),從來源流向「訂閱 (subscribe)」以接收更新的消費者。這種做法在行動開發中廣泛被採用,因為它能優雅地處理非同步資料,並協助建立反應靈敏的應用程式。
現代行動平台為反應式程式設計提供了多種強大的工具:
- Android:可使用 Kotlin Flows 或 RxJava。
- iOS:開發者通常使用 Combine 或 RxSwift。
- 較簡單情境:對於僅需一次性非同步操作的情況,Kotlin 的
suspend函式與 Swift 的async-await提供較輕量的替代方案。
雖然 callback 與 event bus 對特定使用情境也能運作良好,但它們可能會讓狀態管理變得複雜。我們建議改用反應式程式設計的 stream-based 做法,因為它提供了更直接的方式來處理資料流,使程式碼更易於長期維護與推理。
一如既往,每個解決方案都有其取捨。例如 event bus 有助於降低程式碼中元件之間的耦合 (coupling)。
不可變資料結構 (Immutable Data Structures)#
建構現代行動應用程式時(尤其是使用 UDF 的應用程式),不可變資料結構 (immutable data structures) 在狀態管理中扮演關鍵角色。透過讓資料不可變(即資料建立後無法被變更),我們可以減少 bug,並使應用程式的行為更可預測。這在多執行緒 (multi-thread) 常同時存取相同資料的行動應用程式中特別有價值。
雖然偶爾在效能關鍵功能中可能需要可變資料結構,但這些情況很少見。當我們確實使用可變資料時,最好將其與應用程式主要狀態隔離,並仔細控制其修改方式。例如,我們可能在專門的影像處理模組中使用可變結構,但保持應用程式其餘資料不可變。
單一事實來源 (Single Source of Truth, SSOT)#
在現代行動應用程式中,每種資料型別都需要一個明確的擁有者:單一事實來源 (Single Source of Truth, SSOT)。SSOT 擁有 獨佔 (exclusive) 權限來修改其資料,並遵循 UDF 模式:
- 接收請求變更資料的事件。
- 根據商業邏輯處理這些變更。
- 以不可變資料的形式暴露更新後的狀態。
讓我們看看實際運作方式:本機資料庫 (local database) 通常作為離線資料的 SSOT,而 UI 狀態持有者 (state holder) 則擁有該螢幕的顯示狀態。圖 2 說明了這些常見的 SSOT 模式。
資料庫是商業或應用程式資料的 SSOT,而 State Holder 則是 UI 狀態的 SSOT。
透過為每種資料型別指定明確擁有者,我們建立了一個更易於維護的系統,讓資料可預測地流動,狀態變更更可追蹤。這使 bug 更容易被隔離與修復,即使在多執行緒情境下也有助於維持狀態一致性。
依賴注入 (Dependency Injection)#
隨著應用程式日益複雜,不同元件需要協同達成業務目標。這些元件之間的關係會建立出需要仔細管理的相依性 (dependencies)。雖然 依賴注入 (Dependency Injection, DI) 與 Service Locator 模式都能處理這種管理,本書聚焦於依賴注入。
為什麼選擇依賴注入?可以把它想成讓元件被動接受所需資源,而非自己去建立。與其讓元件主動建立依賴,不如由外部提供。這使程式碼更靈活、更易於測試,因為我們可以依需求替換依賴。
例如,假設有一個需要進行網路呼叫的元件。與其讓該元件建立自己的網路客戶端,我們可以透過建構子傳入(或「注入 (inject)」)該客戶端。這種做法稱為 建構子注入 (constructor injection),它清楚地表明每個元件需要什麼才能運作,並讓我們能為測試提供不同實作,或更優雅地處理未來的變更。
我們特別避免使用 Service Locator 模式,因為若未小心實作,它可能會遮蔽依賴關係並使測試複雜化。透過注入明確表達依賴,能讓程式碼更易於理解、維護與驗證。
在閱讀本書中的架構圖時,重要的是理解箭頭代表資料如何在系統中流動,而 不是 元件依賴關係。以圖 3 為例,我們可以看到 UI 狀態從 state holder 流向 UI 元件。雖然 UI 依賴 state holder 來存取並訂閱此資料,但我們不會在圖中用虛線明確畫出這種依賴關係,以保持圖面乾淨並聚焦於資料流。
在本書的高層次架構圖中,我們將依賴注入表示為不同架構層之間的方框。這個方框作為邊界,協助保持元件鬆散耦合 (loosely coupled),如圖 4 所示。
解耦合元件 (Decoupling Components)#
在架構中解耦關鍵元件,可以讓它們各自獨立演進,使應用程式更易於維護與擴展。這種分離在行動應用程式中特別重要,不僅是為了靈活性,也為了實務考量,例如改善可測試性 (testability) 以及在使用模組時縮短建置時間。
解耦的主要策略是讓元件依賴介面 (interfaces) 或抽象類別 (abstract classes),而非具體實作。這在元件間建立了「鬆散耦合」,使某一區域的變更不會漣漪式地影響整個系統。
此方法在以下常見情境中特別有價值:
- 當類別跨越不同架構層互動時。
- 當我們不希望被綁定於特定的第三方解決方案時。
- 當我們需要在相同功能的不同實作間切換時。
例如,在圖 5 中,MapsRepository 依賴 MapsProvider 介面而非任何具體實作。這種彈性讓我們能在測試時使用 MapsTestDouble,而在生產環境中我們可以在 MapsInHouseProvider 與 Maps3rdPartyProvider 之間進行 A/B 測試,以確認哪個能更好地服務使用者。
使用 MapsProvider 於一個假設的應用程式中。
錯誤傳遞 (Error Propagation)#
錯誤傳遞 (error propagation) 是確保應用程式穩健、可靠且對使用者友善的關鍵環節。妥善處理與傳遞錯誤有助於診斷問題、防止應用程式崩潰,並提升整體使用者體驗。
- UI 層:應該在出問題時顯示使用者友善的錯誤訊息,處理簡單驗證(如檢查表單輸入),並顯示適當的 UI 元素以指示錯誤狀態。
- 其他層:應該處理商業邏輯錯誤(如無效資料)、管理來自資料來源的失敗(如網路或資料庫問題),並在使用者需要知道時將相關錯誤向上傳遞到 UI 層。
現代行動框架為錯誤處理提供了內建工具:
- Kotlin coroutines 使用 結構化併發 (structured concurrency) 自然地傳遞例外。
- RxJava 與 RxSwift 可以使用
onError等運算子在資料流中處理錯誤。
當使用不提供這些工具的平台時,我們可以實作一個 Result 型別 來包裝回傳值。這種模式讓我們能明確地建模成功與失敗情況,使錯誤處理更可預測。然而,使用現代框架(如 Kotlin coroutines)時並不需要這額外的一層,因為錯誤處理已經內建。
最小化資料暴露 (Minimal Data Exposure)#
當資料流經應用程式架構時,每個元件只應接收其運作所需的 最小資料量。這個原則同時適用於架構層之間的通訊,以及從資料來源(如本機資料庫或後端 API)擷取資料時。
以聊天應用程式為例:顯示對話列表時,UI 只需要基本資訊,例如聯絡人姓名、最新訊息預覽與時間戳。它不需要為了顯示這些預覽卡而接收每個對話的完整訊息歷史記錄。
這種「less is more」的做法帶來多項關鍵好處:
- 更佳的效能
- 減少記憶體使用與 CPU 負載,因為需要處理的資料較少。
- 最小化網路頻寬消耗。
- 減少不必要的資料處理以改善電池續航。
- 更易於維護
- 系統更簡單,測試與除錯更容易。
- 隨應用程式成長更容易擴展。
- 元件間的資料流更清晰。
資料轉換通常發生在元件之間的邊界。例如,當網路層從後端收到回應時,它可以將資料縮減為儲存庫 (repository) 所需的內容,剝除不必要的欄位。這確保每一層只處理與其職責相關的資料。
架構選擇#
讓我們從剛才介紹的核心架構模式,轉向本書中使用的一些特定實作選擇。那些基礎模式對任何可擴展的行動架構都是不可或缺的,而接下來要討論的選擇較具彈性。你可以根據自身經驗與偏好來調整或替換。
本節聚焦於你需要了解本書所做架構決策的關鍵細節,但我鼓勵你深入探索這些主題,以便在面試中建立自己的觀點。
產業洞察:
- Duolingo 透過採用 MVVM 與使用 Android Jetpack 函式庫(如 Dagger/Hilt 進行依賴注入),改善了 App 的回應性與開發者生產力。
- Uber 透過 plugin system 與 RIBs 強制模組隔離,以擴展其應用程式。每個功能都作為插件開發,並在 App 中有明確定義的整合點,讓 200 多位開發者能平行開發功能而不會互相干擾。
- Notion 的架構強調彈性的資料模型與單向資料流,這為其行動端帶來能力的同時也帶來複雜性。
Google 的 App 架構指南#
讓我們探討本書的核心架構方法,它建立在 Google 的 App 架構指南 之上。雖然原本是為 Android 所設計,但其原則對 iOS 同樣適用,使其成為兩個平台的堅實基礎。
📌 請記住!
如果你有特定架構的經驗,且認為它對行動應用程式有效,請在面試中提出來討論。雖然本書以 Google 的 App 架構指南作為參考(因為它體現了許多核心架構原則),你對其他方法的親身經驗同樣有價值。
這個架構的核心使用兩個主要層:UI 層 與 資料層 (data layer)。把 UI 層想成使用者所看到與互動的部分,而資料層則處理所有幕後的商業邏輯與儲存。某些應用程式也會受益於一個可選的 領域層 (domain layer),它位於 UI 與資料層之間,讓共享與重複使用常見互動變得更容易。圖 6 說明了資料如何在這些層之間流動。
上圖由 Google 的 App 架構指南推薦。
UI 層#
UI 層也稱為 展示層 (presentation layer)。根據文件描述:
UI 的角色是在螢幕上顯示應用程式資料,同時作為使用者互動的主要接觸點。每當應用程式資料變更(由於使用者互動,例如按下按鈕,或外部輸入,例如網路回應),UI 都應更新以反映這些變化。
UI 層由兩個東西組成:
- 在螢幕上渲染資料的 UI 元素 (UI elements)。
- 持有資料、將其暴露給 UI 並處理邏輯的 state holders。
實際上,UI 是從資料層擷取的應用程式狀態的視覺呈現。
UI 層依賴資料層(或若存在則為領域層)作為其資料來源。State holder 作為中介,處理這些資料以建立 UI state,本質上是應該顯示在螢幕上內容的藍圖。文件中簡潔地說明:「如果 UI 是使用者所看到的,那麼 UI 狀態就是應用程式認為使用者應該看到的內容。」
圖 7 說明此在實務中的運作方式。App 中每個畫面都表示為一個元件,並擁有自己的 state holder。這些 state holders 透過依賴注入從其他架構層接收資料。
建立在我們先前討論的架構模式之上,資料流遵循清晰路徑:資料層與領域層暴露商業資料的 streams,state holders 將其轉換為不可變的 UI 狀態。UI 層接著訂閱此狀態以顯示內容,並送回事件以請求變更。這些事件經過 state holder 流動,最終更新應用程式狀態與 UI。
我們可能會想知道這個架構是遵循 MVVM 還是 MVI 模式。答案是它可以是兩者:此架構實作 UDF,依照我們如何構建 UI 狀態 stream 與事件處理,可以同時對應到這兩種模式。與其規定單一方法,我們認為這兩種模式在妥善實作時都能運作良好。雖然這個區別可能在面試的深入討論階段出現,請記住聚焦於整體系統設計,而非陷入任一單一元件的細節,除非面試官主動詢問。
資料層#
資料層實作 repository pattern,為應用程式中所有資料存取提供清晰的抽象。根據文件:
「資料層包含應用程式資料與商業邏輯。商業邏輯是賦予應用程式價值的部分,它由決定應用程式如何建立、儲存與變更資料的規則組成。資料層由 repositories 組成,每個 repository 可包含零到多個資料來源。你應該為應用程式中處理的每種不同資料型別建立一個 repository 類別。
Repository 類別負責以下任務:
- 將資料暴露給應用程式的其餘部分。
- 集中處理資料變更。
- 解決多個資料來源之間的衝突。
- 從應用程式其餘部分抽象化資料來源。
- 包含商業邏輯。
每個資料來源類別應只負責使用單一資料來源,可能是檔案、網路來源或本機資料庫。資料來源類別是應用程式與系統資料操作之間的橋樑。」
圖 8 說明資料層在行動應用程式中的典型運作方式。其核心有兩個主要資料來源:與後端通訊的 遠端資料來源 (remote data source),以及處理裝置上資料儲存的 本機資料來源 (local data source)。如同案例研究章節所見,應用程式常使用資料庫以在裝置上有效管理與持久化資料。
由於 repositories 作為資料層的主要入口點,將它們定義為介面是合理的。這讓我們能透過依賴注入換入不同實作:正常運行時使用真實實作,測試時切換到 test doubles。
導航 (Navigation)#
對於大多數 MSD 面試,我們採用簡化的導航方法。我們不深入複雜的導航模式,而是假設一個全域的 Navigator 或 Coordinator 處理畫面轉換與依賴管理。
此 Navigator 負責:
- 在應用程式的不同畫面之間移動。
- 為每個畫面提供所需的依賴,包括靜態依賴(如 state holders 與平台服務)以及動態資料(如詳情畫面所需的項目 ID)。
Navigator 與依賴注入系統協作以正確建立並提供這些依賴。在處理面試問題時,除非被特別要求,我們通常不需要設計複雜的導航流程。大多數練習聚焦於核心功能而非導航架構。本書的圖表中,我們以圖 9 所示方式表示導航。
在真實應用程式中,導航變得複雜許多。當應用程式成長到包含許多畫面、對話框與使用者流程時,仔細管理導航變得至關重要。當新增 deep linking 支援時,此複雜性倍增,因為這會引入額外的 App 進入點,必須妥善處理。
模組化 (Modularization)#
模組化 (modularization) 是將行動應用程式結構化為獨立建構模組的強大方式。雖然模組化提供多項好處(如更易維護與更佳可擴展性),其最大優勢是 更快的建置時間。然而,模組化需要謹慎規劃。若管理不當,新增更多模組可能增加複雜性與額外開銷。
若妥善實作,模組化能從三個關鍵方式加速建置:
- 更有效率地使用建置資源。
- 減少需要重新編譯的程式碼量。
- 允許不同模組的平行編譯。
雖然我們不會在後續章節中深入討論模組化,但在面試中提及它是值得的。行動業界愈來愈遵循「合約模組 (contract module)」模式,其中每個元件至少有兩個模組:API 模組與實作模組。這種分離意味著當我們變更某個實作時,只依賴其 API 的模組不需要重新建置。
讓我們看圖 10 的具體範例。這裡有幾種類型的模組:
- Feature modules:處理特定畫面的 UI(例如圖中的 “X feature module”)。
- API modules:定義 feature 所需的介面(例如圖中的 “X data layer api module”)。
- Implementation modules:包含那些介面背後實際的程式碼(例如 “X data layer impl module”)。
- Test modules:提供測試實作(例如 “X data layer test module”)。
當實作細節改變時,feature modules 不需要重新建置,因為它們只依賴穩定的 API modules。但我們可能會問:「feature 如何知道要使用哪個實作?」這就是 app module 的用途。它位於最頂層,透過注入正確的實作作為依賴,把一切連結起來。同樣地,執行測試時,test module 提供測試實作以取代真實實作。
如需更深入了解模組化,請參考 Google 的 Android 應用程式模組化指南,該指南擴展了本章討論的架構原則。
產業洞察:
- Airbnb 將其大型 iOS 應用程式重構為近 1,500 個模組,以改善團隊生產力。
- Robinhood 為對抗拖慢建置並糾纏功能的「巨大 monolith」模組,將其 Android 應用程式模組化。他們將應用程式拆成功能特定的模組,改善了建置時間。
測試 (Testing)#
測試應該是面試討論的重點,因為它展示了我們的架構如何支持品質與可維護性。雖然測試常在討論依賴注入或模組化時自然出現,我們也應準備好概述所設計系統的整體測試策略。
行動應用程式通常實作數種自動化測試,雖然術語在團隊與組織間可能有所不同。在面試中討論測試方法時,請明確說明你如何定義每種測試類型以確保共識。以下是你可能要涵蓋的主要類別:
- 單元測試 (Unit tests):測試金字塔的基礎,單元測試獨立驗證個別元件。透過以 test doubles 替換依賴,我們可以驗證特定行為與程式碼路徑。這些測試快速、可靠,有助於及早發現問題。
- Component、Integration 和/或 Navigation 測試:透過使用部分真實實作(而非 test doubles)驗證多個元件如何協同運作。雖然它們比單元測試提供更全面的覆蓋率,但傾向執行較慢且可能需要更多維護。它們對於驗證關鍵使用者流程與導航路徑特別有價值。
- 螢幕截圖測試 (Screenshot tests):這些測試填補了單元測試難以涵蓋的 UI 測試空缺:視覺元素(如排版、字型與樣式)。其運作方式是擷取 UI 元件的參考影像,並與未來的變更進行比較,以捕捉非預期的視覺回歸。雖然概念簡單,它們在維持視覺一致性上很強大。
- 端到端測試 (End-to-end tests):位於測試金字塔頂端,端到端測試驗證應用程式與真實後端服務及依賴是否正確運作。雖然這些測試提供最全面的驗證,它們也是維護成本最高且最可能因網路條件或後端狀態而不穩定的測試。我們應該減少此類測試,聚焦於最關鍵的使用者旅程。
除了這些類別,還有一些可跨測試金字塔不同層級應用的專門測試:無障礙測試 (accessibility tests) 驗證所有使用者的可用性、效能測試 捕捉減速與瓶頸、記憶體管理 測試識別洩漏,以及 安全性測試 驗證資料保護措施。雖然這些測試聚焦於特定品質面向而非功能性,它們對於交付穩健且包容的行動應用程式至關重要。
行動開發的測試有完善的文件,包含許多已建立的模式與最佳實踐。以下是可協助測試策略的關鍵資源:
- Google 的 Android 應用程式測試基礎。
- Robot pattern。
- 使用 OHHTTPStubs (iOS) 與 MockWebServer (Android) 偽造與後端的互動。
產業洞察:
- Slack 的 Android 團隊將 UI 測試作為開發的一級公民,聚焦於「目標明確且密封 (targeted and hermetic)」的 UI 測試。
- Bitrise 分享了他們建議的行動測試策略,你也可以閱讀 Netflix 與 Dropbox 如何測試其行動應用程式。
資料儲存 (Data Storage)#
行動應用程式需要有效的資料儲存以建立良好的使用者體驗。無論是記住使用者偏好、快取網路回應或儲存離線內容,我們管理資料的方式都直接影響 App 效能與可靠性。
在深入儲存解決方案之前,讓我們看看 App 可能需要處理的主要資料類型:
- 使用者資料 (User data):例如個人檔案資訊、App 設定與使用者偏好,用於客製化體驗。包含從主題選擇到通知設定等一切。
- 使用者檔案 (User files):例如使用者在 App 中建立或上傳的相片、影片與文件。這些檔案會留在裝置上,除非被同步到伺服器;若使用者刪除,App 就失去存取權。
- 商業資料 (Business data):App 向使用者顯示的核心應用程式資料,例如新聞文章、產品清單或聊天訊息。若使用者從裝置移除此資料,App 通常可以從後端重新擷取。
- 敏感資訊 (Sensitive information):例如身分驗證權杖 (authentication tokens)、加密金鑰以及其他安全關鍵資料,需要特殊保護。這類資料通常需要安全儲存機制以防止未授權存取。
- 快取資料 (Cache data):暫時儲存以改善 App 效能的資料,例如下載的圖片或預先計算的結果。此資料可依大小與持續時間存在於記憶體或磁碟中。
- 暫時性資料 (Temporary data):例如上傳檔案分塊、表單草稿或 undo history。當不再需要或 App 關閉時可安全地丟棄。
雖然某些資料需要磁碟上的持久儲存,其他資料可以短暫地存在於記憶體中,並在系統需要資源時被釋放。關鍵是根據資料的生命週期與存取模式選擇正確的儲存方式。
資料儲存選項#
當 App 需要將資料持久化到磁碟時,選擇正確的儲存機制至關重要。最佳選擇取決於我們儲存的資料類型以及計畫如何使用它。
鍵值儲存 (Key-value Storage)#
鍵值儲存提供一種簡單的方式儲存基本資料型別,例如字串、整數與布林值。把它想成一個字典,每筆資料(值)透過唯一識別碼(鍵)存取。此類型儲存適合直觀資料,例如使用者偏好、App 設定與 feature toggles。
鍵值儲存的優點在於其 簡單性。API 易於實作與使用,設定需求最少。處理小型資料集時,讀寫操作快速且有效率。
然而,鍵值儲存並非適合所有情境。它在下列情況下表現不佳:
- 大量資料或複雜資料結構。
- 資料項目間的關聯。
- 進階查詢需求。
- 資料完整性強制。
- 交易性操作 (transactional operations)。
此外,除非我們實作額外保護措施,鍵值儲存通常比專門的安全儲存解決方案提供較少的安全性。
🛠️ 平台實作細節
Android 與 iOS 都提供原生鍵值儲存解決方案:
- Android:提供 SharedPreferences 與 DataStore。
- iOS:提供 UserDefaults 與 Property Lists (Plists)。
裝置端安全儲存 (On-device Secure Storage)#
處理敏感資料(例如使用者憑證、付款細節或加密金鑰)時,裝置端安全儲存變得不可或缺。此專門儲存利用行動裝置內建的硬體安全功能,保護敏感資訊免於未授權存取。
安全儲存的主要好處是其強大的 加密資料保護 (encrypted data protection)。這有助於 App 符合嚴格的資料保護要求,同時提供強大的安全保證。現代平台(例如 Android 的 Keystore system 與 iOS 的 Keychain services)讓實作安全儲存變得直截了當。
然而,安全儲存伴隨一些取捨:
- 實作比基本儲存選項更複雜。
- 測試與除錯可能更具挑戰性。
- 加密/解密操作增加效能開銷。
- 資源使用可能影響低階裝置。
- 儲存容量通常有限,不適合大型資料集。
在 iOS 上,Keychain 提供額外好處:與 iCloud Keychain 整合讓我們使用端到端加密 (end-to-end encryption) 跨使用者裝置安全同步憑證。
關聯式資料庫 (Relational Databases)#
當 App 需要有效處理複雜資料時,關聯式資料庫 (relational databases) 提供穩健的儲存解決方案。它們擅長管理大型資料集、處理複雜查詢,並維護不同資料類型間的關聯。這使它們非常適合儲存商業資料與需要持久化到磁碟的快取內容。
關聯式資料庫的主要優勢:
- 最佳化資料擷取與操作,可隨大型資料集良好擴展。
- 強大的索引能力以加速搜尋操作。
- 內建資料實體間關聯的管理。
- 強大的資料完整性保證。
- 支援資料模型演進的 schema migrations。
然而,關聯式資料庫也有一些需要考量的取捨:
- 相較於較簡單的儲存選項,設定與維護更複雜。
- Schema 設計需要仔細規劃。
- 較高的資源消耗可能影響 App 效能。
- 可能有延遲開銷,特別是對複雜查詢。
🛠️ 平台實作細節
對於行動開發,SQLite 作為基礎,Android 與 iOS 都提供原生支援。Google 官方支援 Room persistence library 用於 Android,而 Apple 提供 Core Data 用於 iOS。
非關聯式資料庫 (Non-relational Databases)#
雖然關聯式資料庫因其原生平台支援而在行動開發中常被使用,非關聯式資料庫 (non-relational databases) 可以是依資料需求而定的有力替代方案。
對於行動應用程式,Realm 是一個受歡迎的非關聯式選項。其專注於簡單與速度,結合內建反應式程式設計與即時同步支援,使它特別適合行動用例。Firebase Realtime Database 提供另一種做法,它是一個具離線支援的雲端 NoSQL 解決方案,在連線恢復時自動同步資料。
非關聯式資料庫的主要優勢是其 彈性:它們不需要預先定義的 schema,使它們非常適合儲存非結構化或半結構化資料。它們在高效能讀寫操作上也表現出色。
然而,此彈性伴隨取捨。非關聯式資料庫最適合 App 需要簡單 CRUD 操作而非複雜查詢時。它們通常缺乏關聯式資料庫透過外部鍵 (foreign keys) 與約束 (constraints) 提供的 ACID 交易與參考完整性保證。這可能導致資料一致性挑戰,需要在應用程式邏輯中處理。
檔案與目錄 (Files and Directories)#
有時候我們的資料需求不適合資料庫或鍵值儲存。在這些情況下,直接將資料儲存在檔案中是個好選擇。此方法特別適合二進位資料,或像記事本應用程式這類每個筆記儲存為獨立文字檔案的情境。
檔案儲存的主要優勢是 彈性,我們不被資料庫 schema 所約束,可以建立自訂檔案格式。實作直截了當,因為我們只是處理基本的檔案 I/O 操作。檔案也能跨平台攜帶,並可包含在備份系統中。
取捨是我們失去了資料庫提供的結構與功能。我們需要手動處理一切:確保資料完整性、建構查詢能力、管理併發存取,以及規劃可擴展性。
iOS 與 Android 都提供檔案操作的原生 API:
- iOS:FileManager。
- Android:File APIs。
這些讓 App 直接讀寫內部或外部儲存,特別適合處理大型檔案或不易對應到資料庫表格的複雜資料結構。
儲存選項比較表#
表 1 提供行動應用程式可用主要儲存選項的全面比較。在面試中評估何種儲存機制最符合特定需求時,可用此作為參考。
| 儲存機制 | 優點 | 缺點 | 使用情境 | 範例 |
|---|---|---|---|---|
| 鍵值儲存 (Key-value storage) | 簡單實作與使用,小型資料集的讀寫操作快速。 | 不適合大量資料或複雜結構,缺乏進階查詢,較不安全。 | 儲存非敏感使用者資料,例如偏好、設定、App 設定。 | Android:SharedPreferences, DataStore。iOS:UserDefaults, Property Lists (Plists)。 |
| 裝置端安全儲存 (On-device secure storage) | 敏感資料的安全儲存,靜態與傳輸時加密,符合資料保護標準。 | 實作較複雜,加密/解密帶來效能開銷,儲存容量有限。 | 儲存敏感資訊,例如使用者憑證、付款資訊、加密金鑰。 | Android:Keystore。iOS:Keychain, iCloud Keychain。 |
| 關聯式資料庫 (Relational Databases) | 有效管理大型資料集,支援複雜查詢,確保資料完整性。 | 設定與維護較複雜,資源密集,潛在延遲。 | 處理大型資料集、維護資料實體間關聯、執行複雜查詢。 | Android:SQLite, Room library。iOS:SQLite, Core Data。 |
| 非關聯式資料庫 (Non-relational Databases) | 彈性無 schema 資料模型,高效能讀寫操作。 | 對複雜查詢支援有限,潛在不一致問題。 | 處理非結構化或半結構化資料。 | Android 與 iOS:Realm, Firebase Realtime Database, Couchbase Lite。 |
| 檔案與目錄 (Files and Directories) | 彈性儲存格式,實作簡單,跨平台資料分享容易。 | 缺乏結構,需手動管理資料完整性、併發與可擴展性。 | 處理二進位資料、記事本類應用程式將資料儲存為獨立檔案。 | Android:File APIs。iOS:FileManager。 |
表 1:儲存選項比較
儲存選擇決策樹#
比較表提供每個儲存選項的詳細檢視,而決策樹則提供選擇最適合儲存解決方案的結構化方法。
在面試中,走過此決策樹展示選擇儲存的方法論。與其只是命名一個儲存選項,你可以解釋每個決策點的理由,展示你如何考量安全需求、資料複雜性、關聯需求與存取模式。
📌 請記住!
比較表與決策樹提供簡化的框架引導面試中的儲存決策,但真實應用程式通常需要更細緻的方法。
通常,行動應用程式需要多種儲存解決方案協同運作。例如,社群媒體 App 可能使用:
- 安全儲存 用於身分驗證權杖。
- 關聯式資料庫 用於結構化內容,例如使用者個人檔案與貼文。
- 鍵值儲存 用於 App 設定與偏好。
- 檔案儲存 用於快取圖片與媒體。
產業洞察:
- Netflix 選擇 Realm 行動資料庫儲存裝置上的影片中繼資料以支援離線下載。
- Trello 重新設計其行動資料層以離線工作,將本機資料庫作為單一事實來源。
行動應用程式如何儲存與分享資料#
雖然行動應用程式有各種儲存選項,但它們最終都是在裝置磁碟上以檔案形式儲存資料。這些選項之間的差異在於它們如何透過各自 API 組織、管理並提供資料存取。
預設情況下,行動作業系統使用 沙盒化 (sandboxed) 儲存模型:每個 App 擁有自己的私密儲存空間,其他 App 無法存取。此內部儲存提供基本層級的安全性與資料隔離。
然而,此保護有其限制。使用者若越獄 (jailbreak) 其 iOS 裝置或 root 其 Android 裝置,可以繞過這些安全措施並可能存取或修改任何 App 的內部儲存。對於需要額外保護的敏感資料,由硬體層級安全功能支援的安全儲存解決方案,即使在被妥協的裝置上也能提供更強的保障。
存取裝置範圍儲存#
行動平台提供機制讓 App 與存在於其私有沙盒外的儲存區域互動。這實現了重要功能,例如將相片儲存到裝置圖庫或從其他 App 存取文件。
- Android 上,共享儲存區域允許多個 App 存取共同檔案。這包括裝置內建儲存(有時稱為「外部儲存」,即使實體上在裝置內部)以及可移除儲存(如 SD 卡)。App 常使用這些區域儲存要共享的檔案,例如相片、影片與文件。
- iOS 採取不同的做法,擁有更受控的存取點。iOS 不提供通用共享儲存區域,而是為不同目的提供特定目錄:
- Documents Directory:儲存使用者應能存取的檔案,並會備份到 iCloud。
- Library directory:存放 App 需要共享但不應直接對使用者可見的資料。
使用者可以透過 iOS 的 Files 應用程式存取共享檔案,其他應用程式可以在需要時請求存取。完整的目錄清單可在 FileManager 文件中找到。
處理來自其他 App 的媒體與檔案#
某些行動應用程式需要存取由其他 App 建立的內容,無論是來自相機 App 的相片或來自瀏覽器下載的文件。
兩個平台都提供專為此目的設計的系統 API:
- iOS:
PHAsset與PHImageManagerAPI 讓 App 從使用者的媒體庫載入相片與影片,而UIDocumentPickerViewController讓使用者能瀏覽並選擇裝置上任何位置的檔案。 - Android:MediaStore API 提供對媒體檔案(如相片與影片)的存取,而 Storage Access Framework 建立了讓使用者能從任何相容檔案提供者瀏覽並開啟文件的標準化方式。
這些 API 處理必要的權限與安全檢查,確保使用者持續控制哪些 App 能存取其個人內容。當 App 請求存取這些檔案時,系統會顯示權限對話框,讓使用者核准或拒絕請求。
在自己的 App 間分享資料#
建立多個 App 的開發者常需要一種方式讓這些 App 彼此分享資料。Android 與 iOS 都為此目的提供安全機制:
- Android:使用 Content Providers。透過設定特定權限與簽署要求,開發者可確保只有自己的 App 能存取特定共享資料。這透過限制存取給以相同開發者憑證簽署的 App 來達成。
- iOS:實作 App Groups。當 App 屬於相同開發團隊時,App Groups 建立共享容器,所有成員 App 都可存取。這可在相關應用程式間分享各種資料類型,包括 UserDefaults 設定與檔案。
兩種做法都提供細粒度的資料分享控制,同時維持安全邊界,防止來自其他開發者應用程式的未授權存取。
網路與 API 設計#
無論是從伺服器擷取資料還是跨裝置同步資訊,我們處理網路的方式都可能成就或破壞使用者體驗。讓我們探討 HTTP,即本書中多次使用的主從通訊基礎協定。
HTTP 與 REST API#
建構與後端通訊的行動應用程式時,HTTP (Hypertext Transfer Protocol) 常是首選。其廣泛採用與穩健的功能集使它特別適合客戶端發起的請求。
HTTP 的主要優勢:
- 跨平台、裝置與網路的通用相容性。
- 無狀態設計 (Stateless design):改善可擴展性,因為伺服器不需要在請求間追蹤 session 資訊。
- 支援持久連線:現代版本(HTTP/1.1 與 HTTP/2)允許透過單一連線進行多次交換。
- 彈性:可處理 JSON 以外的各種資料格式,包括 HTML、XML 與二進位資料。
- 內建標頭系統:用於在客戶端與伺服器間交換中繼資料。
- 強大安全性:透過 HTTPS 加密,確保資料保持私密與真實。
當行動應用程式與伺服器通訊時,每個 HTTP 請求都有幾個關鍵元件:
- HTTP verb(例如
GET或POST):指定要採取的動作。 - 資源路徑:識別我們要存取或修改的資料。
- Headers:包含請求的中繼資料,例如身分驗證權杖或預期的回應格式。
- 選擇性請求資料:對於建立新內容等操作放在 body 中,或作為 URL 中的查詢參數。
讓我們看一個典型的 HTTP 請求:
Accept: application/json
Authorization: Bearer <token>
GET https://my-backend-name.com/v1/news?after=timestamp&limit=30拆解:
- 這是一個擷取新聞資料的安全 HTTPS 請求。
Authorizationheader 包含使用者的身分驗證權杖。Acceptheader 告訴伺服器回傳 JSON。- 查詢參數
after與limit協助篩選結果,請求特定時間戳後的 30 則貼文。
查詢參數對 GET 請求特別有用,因為它們不能在 body 中包含資料。透過將這些參數加入 URL,我們可以精確指定想要的資料,同時保持請求安全且 idempotent,意即無論呼叫多少次都不會修改伺服器上的資料。
雖然 HTTP/1.1 定義了這些基本方法及其用途,大多數現代 API 在 HTTP 之上建立額外慣例。這就是 REST 的用武之地,它為設計後端服務提供標準化方法。
設計 REST API#
建構需要擴展的後端服務時,一致性是關鍵,特別是在我們如何定義與結構化 HTTP 端點上。REST (Representational State Transfer) 已成為受歡迎的架構方法,提供進行 HTTP 請求與處理回應的標準慣例。其廣泛採用意味著我們能在開發社群中找到豐富的文件與支援。
設計 REST 介面有許多不同風格,以下是本書使用的核心 REST 實踐:
- 主要 HTTP verbs 自然對應到 CRUD 操作:
GET擷取資料。POST建立新資源。PUT更新現有資源。DELETE移除資源。
- API 中每個資源最好擁有唯一識別它的 URI。例如:
GET /users擷取所有使用者。GET /users/1取得 ID 為 1 的使用者詳情。
- 慎用查詢參數:它們協助篩選、排序與分頁結果。例如:
GET /users?role=admin&sort=lastname_asc&page=2&limit=30
- 選擇清楚的資源名稱:
- 使用名詞表示資源,例如
GET /posts而非GET /query-posts。 - 一致地使用複數形式,例如
GET /posts/{postId}而非GET /post/{postId}。
- 使用名詞表示資源,例如
- 結構化巢狀資源:對於相關資源,使用子資源路徑:
- 好:
GET /v1/posts/{postId}/attachments/{attachmentId} - 避免:
GET /v1/posts/{postId}?attachment=attachmentId
- 好:
- 一致地版本化 API:使用全域版本化以保持 API 結構乾淨:
- 好:
GET /v1/posts/{postId}/attachments - 避免:
GET /posts/{postId}/v1/attachments
- 好:
REST 也定義了標準 HTTP 回應碼協助客戶端了解請求結果。以下是最常遇到的:
200 OK:請求成功。通常用於成功的GET與PUT操作。201 Created:成功建立新資源。POST請求建立項目的常見回應。400 Bad Request:客戶端錯誤。伺服器因語法無效或遺失資料而無法處理請求。403 Forbidden:身分驗證成功但客戶端缺乏該資源的權限。404 Not Found:請求的資源在伺服器上不存在。500 Internal Server Error:伺服器端錯誤。伺服器處理請求時出錯。
遵循這些標準化 REST 慣例,我們建立出直觀、可預測且隨應用程式成長能良好擴展的 API。
資料編碼格式#
透過 HTTP 傳輸資料時,我們對資料格式有彈性。HTTP 本身不在乎資料格式,只需要客戶端與伺服器同意使用哪種格式。它們透過 HTTP headers(例如 Content-Type 與 Accept)建立此共識。
我們可以從幾種常見格式中選擇,例如 JSON (application/json)、XML (application/xml 或 text/xml)、Protocol Buffers (application/x-protobuf) 與 CSV (text/csv)。最佳選擇取決於對可讀性、效率、平台支援以及在程式碼中工作難易度的特定需求。
JSON (JavaScript Object Notation) 已成為現代 REST API 最受歡迎的選擇,而且理由充分。它人類可讀、運作有效率,並能與大多數程式語言順暢整合,因為它緊密符合語言結構資料的方式。JSON 廣泛支援意味著我們可以使用標準函式庫函式輕鬆解析,無需額外依賴。表 2 比較不同格式及其使用情境。
| 格式 | 優點 | 缺點 | 使用情境 |
|---|---|---|---|
| JSON | 人類可讀、易於使用與解析、跨平台與語言廣泛支援、相較於 XML 大小精簡。 | 在大小與解析速度方面較二進位格式(如 Protocol Buffers)效率低,無內建 schema 強制。 | 輕量資料交換。Web API 與行動應用程式常見。 |
| XML | 人類可讀、透過屬性支援複雜資料結構與中繼資料、廣泛支援、透過 XSD 支援強大 schema 驗證。 | 冗長、檔案較大、解析速度比 JSON 與 Protocol Buffers 慢、撰寫與維護較複雜。 | 企業應用程式中的資料交換、以文件為中心的資料、需要大量中繼資料的情境。 |
| Protocol Buffers | 精簡有效率的二進位格式、更快的序列化與反序列化、嚴格 schema 強制、向後與向前相容性。 | 較不易人類閱讀、需要定義 .proto 檔案與使用程式碼產生工具、設定較複雜。 | 效能關鍵應用程式、大規模資料傳輸、需要嚴格 schema 強制與版本化的系統。 |
表 2:網路資料編碼取捨
產業洞察:
- Lyft 從即興的 REST + JSON 演進到使用 Protocol Buffers 的統一 IDL (Interface Definition Language)。Lyft 系統可處理 gRPC 呼叫(用於新的、串流或二進位最佳化 API)與傳統 HTTPS + JSON(為向後相容)。
- Reddit 從單體式後端 API 遷移到 GraphQL 以更好地服務行動客戶端,Airbnb 也是。
- Snapchat 整合 QUIC (Quick UDP Internet Connections) 協定以增強網路效能,特別是在行動環境中。
身分驗證 (Authentication)#
對於管理敏感或個人化資料的行動應用程式,穩健的身分驗證至關重要。我們必須確保使用者在存取受保護資源前,被驗證為合法使用者。
基於權杖的身分驗證 (Token-based Authentication)#
基於權杖的身分驗證因其效率與彈性而在行動應用程式中廣泛採用。與傳統的 session-based 方法不同,它提供:
- 無狀態操作:無伺服器端 session 儲存,增強可擴展性。
- 跨平台相容性:在各種裝置上無縫使用。
- 細粒度存取控制:權杖嵌入權限與到期詳情。
- 減少後端開銷:驗證發生時不需頻繁的資料庫查詢。
最常見的實作使用 JSON Web Tokens (JWT),這是精簡、自包含的權杖,以 JSON 物件安全地在各方之間傳輸資訊。
身分驗證流程#
- 初始登入:客戶端將使用者憑證(例如使用者名稱/密碼)提交給後端驗證服務,該服務會對已註冊的使用者資料進行驗證。
- 權杖產生:成功驗證後,後端發出兩個權杖:
- Access token (JWT):短期存在(例如 15-30 分鐘),包含使用者身分與權限。
- Refresh token:較長期存在,用於取得新的 access token 而無需使用者重新登入。
- 安全權杖儲存:App 使用平台特定的安全儲存機制安全地儲存兩個權杖:
- Android 提供 Android Keystore system 建立並儲存永不離開裝置安全硬體的加密金鑰。App 可在儲存到 SharedPreferences 或 DataStore 之前加密權杖。
- iOS 提供 Keychain Services API,具多個保護類別以保護權杖,確保它們在靜態時加密並只在裝置解鎖時可存取。
📝 提示:
為簡化後端並最小化金鑰管理複雜性,公司可以利用外部 Identity Provider 進行權杖發行與驗證,如圖 12 所示。此提供者提供 JWKS (JSON Web Key Set) 端點,讓系統能擷取用於驗證 JWT 的公鑰,而無需暴露私密簽署金鑰。
已驗證請求與權杖續約#
完成身分驗證後,App 使用權杖進行安全的 API 存取。
進行已驗證請求:App 在每個 API 請求的 Authorization header 中包含 access token:Authorization: Bearer <access_token>。API 閘道接著使用公鑰對傳入的 JWT 進行身分驗證,確保請求在轉發到後端服務前被驗證。
當 access token 過期時,App 使用 refresh token 請求新的:
- App 偵測到 access token 過期(主動檢查其到期或被動收到
401 Unauthorized回應)。 - 它將 refresh token 傳送到權杖續約端點。
- 若 refresh token 有效,伺服器發出新的 access token(有時也發新的 refresh token)。
- App 儲存這些新權杖並使用新 access token 重試原請求。若 refresh token 無效,後端回傳錯誤,使用者應從 App 登出。
強化安全選項#
為進一步強化身分驗證,可考慮實作:
- 多因素身分驗證 (Multi-factor authentication, MFA):需要密碼之外的額外驗證,例如簡訊驗證碼、驗證 App 或生物辨識。
- 生物辨識 (Biometric authentication):利用指紋或臉部辨識提供更友善但安全的體驗。
- 憑證釘選 (Certificate pinning):確保身分驗證請求只送到合法伺服器,防止中間人攻擊。
- 信任裝置登錄:後端為每個使用者維護已知裝置清單。
- 裝置指紋:收集非敏感裝置屬性以建立唯一簽章。
- 新裝置警示:使用者在帳號被新裝置存取時收到通知。
- 可疑活動偵測:系統標記不尋常模式,例如在短時間內從新位置或裝置登入。
透過實作穩健的基於權杖的身分驗證,行動應用程式可以確保只有授權使用者存取受保護資源,同時維持順暢且安全的使用者體驗。
產業洞察:
- Uber 最近在其行動應用程式中推出 passkey 支援,擁抱無密碼登入體驗。
- 許多 App(如 Robinhood)在裝置成功完成多因素身分驗證後標記為「受信任」。之後來自同一裝置的後續登入可能簡化,讓使用者無需提供第二個驗證因素。這透過不在同一手機上每次登入都要求 2FA,來平衡安全與便利。
內容傳遞網路 (Content Delivery Network, CDN)#
內容傳遞網路 (CDN) 將伺服器分布在不同地理位置以有效地向使用者傳遞靜態內容。把它想成全球的內容儲存點網路,每個點為其附近區域的使用者服務。
在行動應用程式中,CDN 擅長傳遞相對穩定且常需要大量頻寬的內容,例如 App 圖示與圖片、影片內容、HTML 頁面,以及 App 素材與資源。
為什麼使用 CDN?透過將常存取的內容儲存得更靠近使用者,CDN 顯著減少我們主要後端伺服器的負載。使用者從最近的 CDN 伺服器接收內容,而不是每個請求都送到後端。此方法提供幾個關鍵好處:
- 因內容來自附近伺服器而載入時間更快。
- 因資料旅行距離較短而延遲降低。
- 因更快的內容傳遞而使用者體驗更好。
- 透過卸載重內容傳遞降低後端成本。
何時應考慮加入 CDN?讓我們檢視表 3 中的取捨。
| CDN 優點 | CDN 缺點 |
|---|---|
| 更佳效能:因內容儲存得更靠近使用者而減少延遲與更快的載入時間。 有效的流量管理:處理大量與突發尖峰,防止瓶頸。 可靠性與可擴展性:提供冗餘與簡易擴展。 降低來源伺服器負載:快取靜態內容,例如圖片、影片與 HTML 頁面。 | 成本:隨使用量增加可能變得昂貴。 增加複雜性:需要設定、快取管理與客戶端整合。 外部依賴:App 效能部分依賴 CDN 提供者的正常運行時間與效能。 內容新鮮度挑戰:確保內容為最新需要仔細的快取管理。 |
表 3:CDN 優缺點
儘管有缺點,CDN 對於擁有全球使用者的 App 是絕佳解決方案。圖 13 說明 CDN 如何融入整體系統架構。
分頁 (Pagination)#
分頁 (Pagination) 是我們在 MSD 面試中經常遇到的基本模式。每當處理資料清單時,分頁應是首要考量之一。無論是顯示貼文的社群媒體動態或處理附件的訊息系統,將大型資料集切成可管理的區塊對可擴展性至關重要。
每當在系統中遇到清單時,我們需要自問兩個問題:
- 一次顯示的項目數量是否有自然限制?
- 在單一回應中傳送整個資料集是否會耗盡網路或裝置資源?
若傳送完整資料集會耗資源,分頁可能是正確解法。透過以較小批次載入資料,你能改善效能與可擴展性,同時減少行動裝置上的記憶體使用。
實作分頁時,我們通常會在兩種主要方法之間選擇:offset 分頁 與 cursor-based 分頁。
Offset 分頁#
讓我們探討 offset 分頁,我們透過指定想要哪一頁以及每頁應有多少項目來請求資料。
以此範例請求為例:GET /v1/feed?page=3&limit=30。
這裡我們要求結果的第三頁,每頁 30 項。後端會略過前 60 項(2 頁 × 30 項)並回傳接下來的 30 項。
使用 offset 分頁時,後端通常包含關於整體資料集的中繼資料。這協助客戶端了解在完整結果清單中的位置。以下是回應可能的樣子:
{
"feed": { … },
"paging": {
"page": 3,
"total_pages": 12,
"total_items": 1000
}
}Offset 分頁在處理穩定、固定大小資料集時表現出色。它易於實作,因為自然對應到資料庫查詢,並讓使用者能直接跳到任意頁面。然而,它有一些重要限制。
隨資料集變大,效能開始受影響。資料庫需要計數並略過越來越多列,變得越來越昂貴。對於頻繁更新的資料也有問題,當使用者到達第 3 頁時,第 1、2 頁的內容可能已完全改變。
你可以嘗試在客戶端處理這些問題,偵測並移除重複,但這會產生新問題:額外的處理開銷、浪費的頻寬,以及需要額外請求填補空缺。
此方法適合具有以下特性的應用程式:
- 中等大小、穩定的資料集。
- 簡單的導航需求。
- 可預測的內容,例如教育素材或電子商務目錄。
Cursor-based 分頁#
我們可以把 cursor-based 分頁 想成在長書中使用書籤。與其跳到特定頁碼,你使用書籤(cursor)追蹤你離開的位置並從那裡繼續閱讀。
以下是實務中的運作方式。請求資料時,客戶端包含指向目前位置的 cursor:GET /v1/feed?after=<cursor>&limit=30。
對於第一個請求,還沒有 cursor,所以該欄位保持為空。後端的回應不僅包括資料,還包括向前後移動資料集的 cursors:
{
"feed": { … },
"paging": {
"size": 20,
"next_cursor": "bmV4dF9jdXJzb3IK",
"prev_cursor": "Y3Vyc29yIF8gcHJldg=="
}
}要載入下一組項目,客戶端只需使用 next_cursor 值:GET /v1/feed?after=bmV4dF9jdXJzb3IK&limit=30。
cursor-based 分頁的關鍵優勢是其頁面大小的彈性。與 offset 分頁不同,我們可以在每次呼叫請求不同資料量。Cursor 本身通常對應到資料中的唯一識別碼,例如主鍵或時間戳。雖然循序 ID 常見,最重要的是有一致的方式來排序記錄。
例如,在新聞動態系統中,我們可能使用 postId 作為 cursor。這些 cursors 在透過網路傳送前通常會被編碼(常用 Base64)。此編碼協助標準化格式並防止 URL 中特殊字元的潛在問題。
此方法特別適合頻繁變更的資料集,例如社群媒體動態、事件日誌與持續更新的系統。Meta 與 Slack 等公司在其 API 中使用此模式。與 offset 分頁相比的主要限制是使用者不能直接跳到特定頁面,他們需要依序瀏覽資料。
選擇正確的 limit 數值#
limit 參數控制每次請求擷取多少項目,其最佳值取決於處理的內容。對於輕量內容,我們可以使用較高的 limit,而重量內容需要較低的 limit 以維持良好效能。
理想的 limit 值因 App 與內容類型而異,值得實驗以找到特定用例的正確平衡。
我們可以廣泛評估不同 limit 選項:
- 10 到 20 項
- 確保快速回應時間與每次請求最少的資料傳輸。適合有頻繁更新的高動態動態消息。
- 可能需要更頻繁的請求,可能導致更多載入指示器與較不順暢的體驗。
- 30 到 50 項
- 在資料傳輸與頻繁請求需求間提供良好平衡。使用者在需要更多資料前可捲動瀏覽貼文較長時間。
- 每次請求的資料負載稍大但對大多數現代行動網路通常可管理。
- 50+ 項
- 減少資料擷取頻率,提供更順暢的捲動體驗。適合內容不頻繁更新的較不動態動態消息。
- 較大的資料負載在較慢網路或舊裝置上載入可能較慢。依 App 規模可能是個考量。
與其使用固定的 limit 數值,我們可以實作 adaptive pagination (適應性分頁) 進一步增強解決方案,它根據網路條件或使用者行為等因素動態調整 limit:
- 對快速捲動的使用者,我們可能增加 limit 以提供更順暢體驗。
- 對花更多時間在內容上的使用者,我們可降低 limit 以減少不必要的資料傳輸。
📌 請記住: 選擇分頁中正確的 limit 值很重要,因為它直接影響系統效能與使用者體驗。最佳值通常取決於內容重量。一般來說,limit 在 20 到 50 項之間在使用者體驗與有效資料載入間取得良好平衡。
分頁比較表#
表 4 比較行動應用程式中使用的主要分頁方法。使用此參考快速評估哪個策略符合特定需求與限制。
| 分頁方法 | 優點 | 缺點 | 使用情境 |
|---|---|---|---|
| Offset 分頁 | 易實作,易於導航至特定頁面,良好對應資料庫查詢。 範例: GET /v1/feed?page=3&limit=30 | 在大型資料集上效能降低,對頻繁更新可能不準確並可能導致重複。 | 具有中等大小資料集需要直接導航的應用程式,例如教育 App、電子商務平台、論壇。 |
| Cursor-based 分頁 | 對大規模資料集有效率,良好處理頻繁更新,每次請求彈性 limit,無狀態後端。 範例: GET /v1/feed?after=bmV4dF9jdXJzb3IK&limit=30 | 無法不穿越前面頁面就跳至特定頁面,可能需要編碼以提高安全性。 | 具高頻率更新的即時與大規模應用程式,例如社群媒體動態、事件日誌。 |
表 4:分頁選項比較
分頁選擇決策樹#
在決定要實作哪種分頁方法時,有數個因素需考量。圖 14 中的決策樹提供結構化路徑來處理這些考量,根據 App 特定需求與資料特性引導我們到適當的分頁策略。
⚠️ 免責聲明!
雖然此決策樹與比較表提供選擇儲存選項的結構化指引,它們刻意簡化了複雜的考量。
真實世界實作通常涉及額外因素,例如後端架構限制、現有 API 模式,以及資料的特定效能特徵。使用這些工具框住面試討論,但準備好承認可能影響最終決策的細微差別。
產業洞察:
- Slack 從 offset-based 轉為 cursor-based 分頁,以有效處理大型資料集並確保一致的資料擷取。
- Discord 對無限清單使用虛擬化以順暢處理分頁。結果是重度使用者啟動時記憶體使用下降 14%,載入時間更快。
- Shopify 遷移到相對 cursor 分頁,使用最後看到的項目 ID 擷取下一頁。
效能 (Performance)#
效能最佳化構成行動 App 品質的關鍵支柱,直接影響使用者滿意度、留存率與商業成功。隨應用程式複雜性增加且使用者期望上升,維持反應靈敏的體驗變得越具挑戰性,同時也越發必要。
🎯 何時提及
效能考量展示我們在建立反應靈敏、有效率應用程式方面的技術深度。我們可以在討論資料密集功能、動畫、背景處理,或需要在低階裝置上運作的系統時提及。此主題普遍相關,但在設計處理大型資料集、複雜即時需求,或需要在具挑戰性網路環境中運作的 App 時變得至關重要。
不良效能會嚴重影響使用者留存率,Google 甚至在 Play Store 中降低表現不佳 App 的排名。使用者常會解除安裝有崩潰、過度資料使用、電池耗損與 UI jank 問題的 App。
整體效能方法#
有效的效能最佳化需要跨越整個 App 架構的系統化方法:
- UI 回應性 (UI responsiveness):確保 App 感覺即時且流暢。主執行緒應保持自由以處理使用者互動,將繁重操作卸載到背景執行緒。
- 網路效率 (Network efficiency):最小化延遲與資料消耗。
- 記憶體管理 (Memory management):防止崩潰與背景崩潰,同時實現順暢的多工體驗。
- 電池最佳化 (Battery optimization):透過最小化耗電操作延長有效使用時間。位置服務、持續網路活動與 wake locks 對電池續航特別昂貴。
以測量為驅動的最佳化#
效能最佳化必須基於實證資料而非假設或理論瓶頸:
- 基線指標 (Baseline metrics):透過全面的效能測試提供客觀資料以衡量改善。
- 持續監控 (Continuous monitoring):透過生產環境分析揭示應用程式在真實世界條件下跨多樣裝置、網路與使用模式的表現。
- 目標儀表化 (Targeted instrumentation):協助識別關鍵路徑中的特定瓶頸。
- 增量驗證 (Incremental validation):在每次最佳化後確認實際改善並協助避免回歸。小型、聚焦的變更搭配可測量結果優於大型、推測性的重構。
在 MSD 面試中,討論背景元件時特別注意效能影響。無論設計預先擷取服務或基於位置的功能,我們都應準備好解釋如何最佳化其資源使用以維持順暢使用者體驗。最佳設計在功能與有效資源消耗間取得平衡。
產業洞察:
- Instagram 透過在背景預先擷取資料改善 UX,即使在不良網路條件下也確保無縫內容傳遞。他們也推出 Data Saver 模式讓受限流量方案的使用者控制資料使用。
- Robinhood 實作 Server-Driven UI 加速功能交付。
- Spotify 實作儲存在裝置磁碟上的批次中繼資料下載,搭配預計算排序以減少啟動與檢視載入時間。
- Lyft 透過最佳化磁碟讀取操作並實作記憶體快取策略,達成 ANR 減少 21%。
- Uber 在其行動網路基礎設施中開發複雜的 failover 處理機制,確保其 App 的可靠與低延遲通訊。
功能開發 (Feature Development)#
雖然穩固的架構為 App 奠定堅實基礎,深思熟慮的功能部署策略確保使用者能順暢地體驗我們的創新。本節探討在規模上發布與管理功能的實務方法。
雖然這些主題可能不是每個面試問題的核心,它們展示了有價值的觀點,可讓候選人在討論如何實作大規模行動解決方案時脫穎而出。面試官常尋找不僅知道如何建構功能,還了解如何安全地交付給使用者的候選人。準備好討論這些營運面向顯示我們思考超越純粹實作,考慮整個產品生命週期。
漸進式發布策略#
部署新 App 版本或重要功能時,漸進式推出方法有助於在影響整個使用者群之前識別並解決潛在問題。可以把它想成安全網,我們可以在問題仍可管理時發現並修復,而不是讓它們一次衝擊所有使用者。
🎯 何時提及
討論漸進式發布策略展示對行動開發風險管理的理解。可在解釋如何推出複雜功能或可能影響使用者體驗的重大架構變更時提及。對於大型使用者群的 App,或設計可能在同時發布給所有人時壓垮後端資源的功能時特別相關。
兩大 App 商店都支援漸進式推出,儘管它們實作方式不同:
- Google Play Store 提供 staged rollouts,提供時間與受眾百分比的精細控制。可從 1% 使用者群開始,並隨信心增加逐步擴大可用性。
- Apple App Store 提供 phased releases,採固定 7 天時程與預定每日使用者百分比。雖較 Google 的方法不靈活,此系統仍提供對廣泛問題的寶貴保護。
兩平台的關鍵差異涉及 回滾能力。若出現嚴重問題,Google Play 允許開發者停止發佈並將受影響使用者還原到先前版本。Apple 平台目前缺乏此安全功能;一旦更新送達使用者,他們會留在該版本直到新更新發布並安裝。
若我們需要在廣泛發布前更受控的測試,兩平台都提供額外選項:
- Beta 程式(iOS 的 TestFlight、Android 的 Play Store Beta testing):允許將建置發佈給組織外的已註冊測試者。
- 內部測試軌道 (Internal testing tracks):讓團隊成員使用與生產建置相同的發佈管道驗證候選版本。
- 員工 dogfooding 程式:正式化團隊成員在日常生活中使用預發布版本以發現可用性問題與邊緣情況的做法。
實作漸進式發布策略時,聚焦於:
- 監控能力:確保配置穩健的分析與崩潰報告,以在分階段推出期間快速識別問題。
- 嚴重性門檻:定義清楚的指標以觸發推出暫停或回滾(例如崩潰率增加超過 1%)。
產業洞察: Notion 的行動團隊在 iOS 與 Android 上採取每週發布的做法。
強制升級 (Force Upgrading)#
有時行動應用程式需要淘汰舊版本以確保安全性、維持效能或交付關鍵功能改善。強制升級 (force upgrading) 是一種策略技術,促使舊版 App 上的使用者在繼續使用 App 前升級。此方法在使用者體驗與技術及商業必要性間取得平衡。
🎯 何時提及
強制升級顯示我們在跨使用者群維持 App 穩定性與安全性的意識。可在討論 API 版本化策略、安全關鍵更新或支援多個客戶端版本將顯著增加後端複雜性的情境時提及。在系統設計包含會使舊版 App 與新後端實作不相容的重大變更時特別相關。
強制升級應在有清楚理由下實作,因為它們在使用者體驗中造成摩擦。最有說服力的理由包括:
- 後端相容性變更:發生重大 API 變更或棄用時,維持與所有先前 App 版本的向後相容變得越發昂貴與複雜。在驗證新後端可靠運作後,可以停用舊 API 並要求受影響使用者升級。
- 安全漏洞:舊版 App 發現關鍵安全缺陷時,升級使用者成為優先以保護他們的資料與系統。在高度受管行業中,安全驅動的強制升級甚至可能是合規要求。
- 法規遵循:新法規(特別是醫療、金融或隱私領域)可能引入舊版 App 無法滿足的要求。這些情況下,強制升級協助維持跨使用者群的法律合規。
- 分析一致性:隨分析策略演進,使用者群碎片化跨多個 App 版本可能損害資料完整性與決策。將使用者整合到新版本可建立更一致的分析基礎。
實作方法#
強制升級存在於執行光譜上,有不同程度的緊迫性:
- 硬升級 (Hard upgrades):完全阻止 App 使用直到使用者更新。適合關鍵安全漏洞或重大後端不相容。UI 通常由解釋需要升級的 modal screen 組成,搭配顯眼的 call-to-action 引導使用者到平台的 App 商店。
- 軟升級 (Soft upgrades):鼓勵更新同時仍允許有限 App 功能。此方法在使用者體驗與技術需求間取得平衡。使用者通常在啟動 App 時看到可忽略的提示,並在 session 期間持續提醒。雖較不具破壞性,此方法將使用者遷移到新版本耗時較長。
- 分層寬限期 (Tiered grace periods):透過從軟提示開始並在定義時間後逐漸轉換為硬要求來提供折衷。這讓使用者有充分警告同時確保最終合規。
技術實作#
標準實作遵循直接流程:
- App 初始化時,客戶端將版本資訊送到專用的版本檢查端點。
- 後端根據目前政策評估此版本。
- 基於此評估,後端回傳是否需要升級的回應以及升級類型(硬或軟)。
- 客戶端根據回應呈現適當 UI,無論是阻止進一步存取或顯示可忽略提示。
實務考量#
實作強制升級時,幾項實務因素需要注意:
- 網路條件:連線不良地區的使用者可能難以下載更新。考慮實作離線寬限期,即使需要更新也允許暫時存取。
- 裝置限制:某些使用者可能擁有不支援最新 App 版本的舊裝置。
- 分階段強制:對於非關鍵更新,考慮如同推出新 App 版本一樣逐步推出強制升級要求。這防止壓垮 App 商店基礎設施與支援管道。
透過深思熟慮地實作強制升級能力,我們維持對 App 生態系統更大的控制,同時確保使用者受益於重要改善與保護。妥善使用時,此方法透過減少碎片化與技術債同時幫助使用者與開發團隊。
產業洞察: Uber 實作強制升級流程以協助管理擴展其行動 App 基礎設施的挑戰。
功能旗標、遠端設定與 A/B 測試#
現代行動應用程式需要快速演進同時維持穩定性,這項挑戰需要動態控制系統。功能旗標 (feature flags)、遠端設定 (remote configuration) 與 A/B 測試 形成強大工具組,實現受控實驗與部署,無需 App 商店更新。
🎯 何時提及
此主題展示我們對現代資料驅動開發實踐的知識。可在討論如何用真實使用者驗證設計決策、實作漸進式功能推出,或管理跨不同使用者區段的複雜功能時提及。在解釋系統如何支援實驗而無需透過 App 商店不斷更新 App 時特別有價值。
其核心是 將功能可用性與程式碼部署分離。當使用者啟動 App 時,它連接到後端服務以根據各種條件決定哪些功能應被啟用:
- 使用者特徵(例如人口統計、訂閱等級或使用模式)。
- 裝置能力(例如硬體規格或作業系統版本)。
- 環境因素(例如地理位置或網路條件)。
- 實驗群組(例如隨機分配或使用者 cohort)。
產品團隊通常透過專用管理介面(例如 CMS)管理這些規則,提供視覺化與控制而無需工程師介入每次更新。
功能旗標透過向不同使用者顯示不同變體實現有效的 A/B 測試。後端控制不僅是功能是否啟用,還控制使用者看到哪個版本。透過追蹤每個變體的分析,團隊可以衡量有效性並做出資料驅動決策。
策略應用#
此工具組在行動應用程式中服務多個基本功能:
- 風險管理:最初將新功能限制在使用者子集,可以抑制潛在問題的影響。
- 分階段推出:功能可以逐步引入給越來越多比例的使用者,讓我們能監控效能影響、伺服器負載與使用者接受度,再決定是否全面部署。
- 效能最佳化:A/B 測試提供哪些實作交付更好效能指標的實證資料,協助團隊做出有證據的決策。
- 基礎設施更新:遷移到新後端系統或第三方服務時,功能旗標允許我們控制新舊實作間的流量,實現無縫轉換。
實作考量#
雖然強大,功能旗標與 A/B 測試需要仔細管理:
- 程式碼複雜性:每個旗標增加分支邏輯與潛在互動。此複雜性隨新增更多旗標而成長。
- 技術債:功能完全發布後仍保留的旗標造成不必要的複雜性。定期清理至關重要。
- 測試挑戰:測試所有可能的旗標組合隨系統成長越來越困難。
- UX 理解:多個活躍旗標讓理解不同使用者確切體驗到什麼變得更困難。
妥善實作時,功能旗標、遠端設定與 A/B 測試改變行動應用程式演進的方式,實現資料驅動開發、受控實驗與對新興問題或機會的快速回應。此方法已成為規模化公司的標準實踐,在那裡快速移動同時維持穩定性的能力創造顯著競爭優勢。
產業洞察:
- Netflix 以資料驅動聞名,幾乎對每個產品變更運行 A/B 測試。他們建立內部實驗平台(代號 “ABlaze”),讓任何團隊能定義實驗、目標使用者子集與追蹤指標。
- Duolingo 將許多產品成功歸因於大量 A/B 測試。在工程端,這意味著將實驗框架建構到 App 中。
可觀測性、分析、監控與報告#
行動應用程式需要對其效能、使用者行為與錯誤狀態的全面可見性。穩健的測量策略協助工程團隊交付穩定體驗,同時為產品團隊提供驅動決策的洞察。
🎯 何時提及
討論這些元素顯示我們理解成功系統需要持續測量與最佳化。可在解釋如何驗證設計符合效能需求、識別潛在瓶頸或追蹤關鍵功能使用者參與時提及。在設計需要顯著擴展的系統或提議受益於穩健監控的複雜架構時特別相關。
完整可見性系統處理 App 效能與使用的多個維度:
- 技術效能監控 (Technical performance monitoring):追蹤系統層級指標,例如回應時間、資源使用與錯誤率。此資料協助識別瓶頸、最佳化程式碼路徑,並確保 App 在不同裝置能力與網路條件下保持反應靈敏。
- 使用者行為分析 (User behavior analytics):捕捉人們如何與應用程式互動,包括導航模式、功能使用與轉換指標。這些洞察透過揭示 App 哪些面向交付價值以及摩擦點在哪裡,來告知產品決策。
- 錯誤追蹤 (Error tracking):識別影響使用者體驗的崩潰、例外與非致命錯誤。
- 商業指標報告 (Business metrics reporting):透過測量符合組織目標的關鍵效能指標(如參與度、留存率與貨幣化指標),將技術效能與商業結果連結。
錯誤分類與管理#
實作錯誤追蹤時,區分不同失敗類型至關重要:
- 致命錯誤 (Fatal errors):導致 App 完全崩潰。
- 非致命錯誤 (Non-fatal errors):允許 App 繼續運行但可能阻止特定功能正確運作。
- 降級狀態 (Degraded states):發生在 App 繼續運作但效能或能力降低時。
每種錯誤類型需要適當的儀表化以確保可見性。雖然崩潰報告框架自動捕捉致命錯誤,非致命錯誤與降級狀態需要在程式碼中明確儀表化。
架構考量#
從架構觀點,分析與監控系統應設計為效能影響最小同時交付最大洞察:
- 取樣策略 (Sampling strategies):透過從代表性使用者或 session 子集收集全面資料(而非詳盡追蹤所有事物),減少資料量同時維持統計有效性。
- 有效資料收集:通常涉及批次事件、壓縮負載與選擇性收集頻率。
- 穩健離線處理:確保分析資料在裝置缺乏連線時不會遺失。
- 上下文資訊:自動為事件新增相關中繼資料,例如裝置資訊、App 版本與使用者區段。此上下文透過實現多維篩選與分段使分析更強大。
在高層次架構圖中,分析元件通常作為與多個子系統整合的交叉關注點 (cross-cutting concerns) 出現。圖 15 顯示 Analytics repository 如何常被加入到 App 高層次架構圖中。
產業洞察:
- Pinterest 為其 iOS 與 Android 客戶端建立端到端 JSON 記錄系統。其理念是讓記錄「無 schema」且簡單。
- Slack 開發客製追蹤系統 SlackTrace,以監控通知在其基礎設施中的流動,目標是標準化資料格式並簡化除錯。
- Signal 對分析採取獨特立場:它幾乎不收集任何分析以保護使用者隱私。Signal App 沒有大多數 App 使用的典型行動分析或崩潰報告 SDK。
在地化 (Localization)#
為全球受眾調整應用程式不僅是翻譯文字。全面的在地化 (localization) 改變 App 的每個方面,讓不同地區、語言與文化背景的使用者感覺自然。深思熟慮地實作時,在地化顯著擴展潛在使用者群,同時改善國際市場的參與度與留存率。
🎯 何時提及
在地化展示我們對為全球受眾開發的意識。可在設計打算用於國際市場的 App 或討論需要翻譯的文字密集介面時提及。在解釋架構如何處理可變內容長度、從右到左語言,或可能影響核心設計決策的區域特定功能時特別相關。
超越文字翻譯#
雖然文字翻譯構成在地化的基礎,完整策略處理文化適應的多個維度:
- 數字與貨幣格式:跨地區差異巨大。
- 日期與時間表示:遵循全球不同慣例。
- 複數規則 (Pluralization rules):跨語言不同,常以複雜方式超越英文加 ’s’ 的簡單方式。
- 文字方向:需特別考量 從右到左 (RTL) 語言,例如阿拉伯文、希伯來文與波斯文。
- 視覺與文化元素:包括顏色、符號與圖像,可能跨文化具有不同含義或聯想。
Android 與 iOS 雖共享類似的在地化概念,但它們的實作方式不同:
- iOS 提供匯出翻譯的內建支援。
- Android 透過其資源系統實作在地化。
在地化挑戰#
實作全面在地化呈現數個技術挑戰:
- 可適應 UI 設計:必須容納跨語言的文字展開與收縮。德文與芬蘭文翻譯通常需要比英文多 30-40% 的空間,而中文常使用顯著較少。
- 動態語言切換:讓使用者能更改偏好語言而無需重啟應用程式。這需要仔細管理資源載入與 UI 重新整理以確保順暢轉換。
- 資源最佳化:隨著在地化素材倍增變得關鍵。每個支援語言增加 App 大小,可能影響下載轉換率與裝置儲存需求。
- 測試複雜性:隨每個支援語言指數成長。
- 後端 API 在地化:確保從後端交付的內容以使用者偏好語言呈現。這通常涉及在 API 請求中包含語言偏好參數,並設計後端系統回傳適當在地化的回應。
透過將在地化視為架構的基本方面而非事後追加,我們的應用程式可以在多樣市場與文化中提供真正全球性的觸及,同時維持卓越使用者體驗。
產業洞察:
- Netflix 在 190 多個國家可用,建立複雜的管道來為所有 App 擷取、翻譯並提供 UI 字串。
- Netflix 甚至使用 偽在地化 (pseudo-localization)(人工延長文字並新增帶重音字元)作為測試步驟,以確保 UI 將容納其他語言與字體系統。
隱私 (Privacy)#
隱私考量已從選擇性功能演進為行動應用程式的基本需求。隨全球法規收緊且使用者意識增強,實作穩健的隱私措施對法律合規與維持使用者信任都至關重要。深思熟慮的隱私策略必須處理法規要求、技術保護措施與透明的使用者體驗。
🎯 何時提及
隱私考量顯示我們理解行動開發的法規與倫理維度。可在討論處理敏感使用者資料的功能、需要跨裝置同步的系統,或針對嚴格隱私法地區(如歐洲 GDPR 或加州 CCPA)的設計時提及。在解釋資料儲存決策、身分驗證機制或使用者同意流程時特別重要。
瀏覽法規環境#
行動應用程式常跨多個司法管轄區運作,每個都有獨特的隱私框架規範如何處理使用者資料:
- 歐盟一般資料保護條例 (GDPR):建立了全面框架,從根本上改變組織對資料隱私的做法。
- 英國資料保護法案 (Data Protection Act, DPA):遵循資料保護標準。
- 健康保險可攜與責任法案 (HIPAA):規範美國受保護健康資訊。
- 澳洲隱私原則 (APPs):澳洲 App 需要遵循。
法規環境持續快速演進,新隱私法規定期在全球湧現。跟上這些發展對維持跨全球市場的合規至關重要。
技術實作策略#
實作隱私保護需要分層方法,保護使用者資料整個生命週期:
- 資料最小化 (Data minimization):構成現代隱私實踐的基礎。只收集 App 核心功能所需的資訊。
- 傳輸中資料安全:保護資訊在客戶端與伺服器間移動時的安全。
- 受保護儲存:保護裝置與伺服器上靜態資料。例如,應使用業界標準演算法(如 AES-256)加密敏感資訊,並搭配適當的金鑰管理實踐以維持安全。
- 存取控制:確保使用者只能存取他們被授權檢視的資料。
- 資料匿名化與假名化:透過將個人識別碼與使用資料分離,降低隱私風險。
- 安全測試:進行定期滲透測試以識別漏洞,並測試客戶端與伺服器端安全措施。
除了這些區域需求,跟上行動安全最佳實踐至關重要。OWASP 行動安全風險清單每年更新,提供新興威脅與建議保護的全面概覽。
產業洞察: Meta 一直在探索保護隱私的機器學習,以在不損害使用者資料的情況下改善產品。一個前沿方法是具備 差分隱私的聯邦學習 (Federated Learning with Differential Privacy, FL-DP)。
無障礙 (Accessibility)#
建立真正包容的行動應用程式意味著確保每個人都能有效使用產品,無論他們的能力或如何與裝置互動。無障礙不僅關於遵循法規,它是關於擴大應用程式觸及並建立對所有使用者運作良好的產品。
🎯 何時提及
討論無障礙展示我們對包容性設計原則的承諾。可在解釋需要為所有使用者運作的介面設計決策、導航模式或互動模型時提及。在設計面向公眾的應用程式時特別相關,特別是那些受法律無障礙要求的應用程式,例如政府、教育或醫療保健 App。
領先平台提供全面的無障礙指南協助建立更包容的 App:Web 內容無障礙指南 (WCAG)、Apple 的 iOS 人機介面指南,以及 Google 的 Android Material Design 無障礙指南。
平台實作技術#
Android 與 iOS 都提供全面的無障礙框架協助開發者建立包容 App:
- 螢幕閱讀器 (iOS 上的 VoiceOver、Android 上的 TalkBack):支援使有視覺障礙的使用者能透過音訊回饋瀏覽應用程式。
- 動態文字大小:允許使用者調整文字顯示以符合需求。
- 顏色與對比:考量協助色盲或低視力使用者。
- 觸控目標:應足夠大以容納有動作控制挑戰的使用者。
測試與驗證#
全面的無障礙測試結合自動化檢查與人工評估:
- 自動化測試工具:例如 Accessibility Scanner (Android) 與 Accessibility Inspector (iOS) 識別常見問題,例如遺失標籤、對比不足與小觸控目標。
- 使用輔助技術的手動測試:提供對真實使用者如何體驗應用程式更深入的洞察。僅使用 VoiceOver 或 TalkBack 瀏覽整個 App 揭示自動化工具可能錯過的可用性問題。
- 與身心障礙者的使用者測試:交付最有價值的回饋。
透過在整個開發流程中整合無障礙考量,而非將它們視為事後追加,你建立運作更好的穩健應用程式,對每個人都更好。
產業洞察: BBC 透過全面的行動無障礙指南以身作則。
推播通知 (Push Notifications)#
推播通知 (push notifications) 讓 App 即使在使用者未主動使用應用程式時也能觸及他們。此能力對於分享重要更新、傳遞時效資訊或重新吸引使用者與 App 互動非常寶貴。雖然強大,推播通知有固有限制,在將其納入行動系統設計時應該了解。
🛠️ 平台實作細節
iOS 與 Android 都透過平台特定服務實作推播通知:
- iOS:使用 Apple Push Notification Service (APNs)。
- Android:依賴 Firebase Cloud Messaging (FCM)。
這些服務作為後端與使用者裝置間的中介。
推播通知的典型流程遵循以下步驟:
- 我們的 App 在安裝期間向平台的通知服務註冊。
- 服務提供唯一裝置權杖以識別該安裝。
- 我們的 App 將此權杖送到後端。
- 當後端需要通知使用者時,它將訊息送到適當平台服務。
- 平台服務嘗試將通知傳遞到使用者裝置。
🎯 何時提及
推播通知展示我們對 App 之外的使用者參與策略的理解。若面試官對此主題感興趣,他們通常會將其作為功能需求加入。若非此情況,你可以在討論傳遞時效資訊的系統、需要使用者重新參與或包含非同步互動的系統時提及推播通知。在設計訊息 App、社群平台或使用者即使未主動使用 App 也受益於被通知事件的任何應用程式時特別相關。
推播通知傳遞挑戰#
數個因素可能阻止成功通知傳遞:
- 裝置限制
- 裝置離線或在飛航模式時通知會排隊。
- 平台伺服器最終會丟棄無法傳遞的排隊通知。
- 省電模式可能延遲或阻止通知傳遞。
- 平台限制
- APNs 與 FCM 都實作節流機制以保護系統資源。
- Android 製造商客製化可能以不同方式影響背景程序處理。
- 平台特定的負載大小限制限制可包含的資料量。
- 使用者控制
- 使用者可以透過系統設定停用通知。
- 他們可能在首次 App 啟動時拒絕通知權限。
- 勿擾設定與 focus modes 可能暫時阻止傳遞。
鑒於這些限制,推播通知應作為 補充通訊管道 而非傳遞關鍵資訊的主要方法。它們對增強參與度極佳,但系統設計應包含重要更新的替代機制。
第 4 章「設計聊天 App」詳細介紹推播通知。圖 16 顯示其高層次架構圖中推播通知相關元件。
產業洞察:
- Slack 擴展其通知系統以傳送更廣泛的通知類型,不僅限於直接訊息與提及。
- Duolingo 建立高規模通知系統以處理其龐大使用者群與參與活動。
App 大小#
App 大小直接影響使用者取得與留存。Google 研究顯示 APK 大小每增加 6MB,安裝轉換率降低約 1%。在資料方案有限的市場或儲存受限裝置上,此效應變得更顯著。
🎯 何時提及
App 大小考量顯示我們對實務下載與安裝限制的意識。可在討論目標為頻寬有限地區的應用程式、包含大量離線內容的 App,或需要眾多第三方函式庫的設計時提及。在目標受眾包含儲存受限裝置或有限資料方案的使用者時特別相關。
兩個主要貢獻者通常影響 App 大小:
- 第三方函式庫:新增功能但常帶來顯著大小開銷。每個函式庫應該透過提供無法實際自建的基本能力來證明其佔用空間。
- 媒體素材:例如圖片、影片與音訊檔案,增強使用者體驗但可能快速膨脹 App 大小。在面試中處理素材管理時,討論策略例如:
- 在適當處使用向量圖形以維持跨裝置解析度的品質。
- 對不常存取的功能實作按需素材下載。
- 利用伺服器端基於裝置能力的圖片最佳化。
在面試中討論 App 大小最佳化時,展示對平台特定方法的意識。
🛠️ 平台實作細節
Android 與 iOS 都提供全面的工具與文件以減少 App 大小。
- Android:可提及 R8 程式碼縮減、資源縮減與 App Bundles 等技術,僅傳遞特定裝置配置所需的程式碼與資源。
- iOS:可強調 app thinning、on-demand resources 與 Swift 的 whole module optimization。
透過將深思熟慮的 App 大小考量整合到行動系統設計中,我們展示對影響真實世界 App 發佈與採用的實務限制的意識。
產業洞察: Instagram 建立 Instagram Lite Android app,初始大小僅 573 KB。
CI/CD#
行動應用程式重度依賴自動化以實現一致、可靠的交付。持續整合 (Continuous Integration) 與持續交付/部署 (Continuous Delivery/Deployment, CI/CD) 實踐精簡測試與發布流程,確保跨開發階段的高品質。
🎯 何時提及
CI/CD 知識展示我們對完整開發生命週期的理解。可在討論設計在實務中如何實作時提及,特別是需要頻繁更新的複雜系統或解釋如何跨多個平台維持品質時。在描述架構如何支持可測試性或提議涉及複雜整合需求的設計時特別相關。
持續整合 (CI)#
持續整合 (Continuous Integration) 聚焦於頻繁將程式碼變更合併到共享儲存庫。每次開發者推送變更時,自動化管線執行這些基本功能:
- 建置 App 以及早捕捉編譯錯誤。
- 執行單元與整合測試以確保功能性。
- 透過靜態分析工具分析程式碼品質。
這建立有價值的回饋迴圈,問題立即被偵測而非隨時間累積。諸如 Fastlane、GitHub Actions 或 Bitrise 等工具處理這些自動化檢查,確保無需手動介入的一致品質。
持續交付 (CD)#
雖然 CI 聚焦於程式碼品質,持續交付 (Continuous Delivery) 自動化發布的準備與發佈。一旦程式碼通過 CI 檢查,CD 管線處理:
- 配置 App 簽署與憑證。
- 設定環境特定變數。
- 產生發布建置。
- 將建置發佈到測試環境。
🛠️ 平台實作細節
行動應用程式在程式碼簽署周圍有獨特的 CD 需求:
- Android 使用 keystore 檔案驗證 App 真實性。
- iOS 需要 provisioning profiles 與憑證,常透過 Fastlane Match 等工具管理。
每個環境(例如開發、QA 與生產)也需要自己的配置:
- Android 使用
build.gradleflavors 與buildConfigField。- iOS 使用 Xcode build configurations 與 schemes。
持續部署 (Continuous Deployment)#
在其最純粹形式中,持續部署 (Continuous Deployment) 在變更通過所有品質檢查後自動推送到生產環境。與 Web 或後端部署不同,發布對使用者不是立即可用,因為 App 商店審核流程可能引入無法預測的延遲。
CI/CD 的關鍵好處是 一致性!它移除可能引入錯誤的手動步驟,同時確保每個發布遵循相同的經驗證流程。此領域知識顯示面試官我們不僅聚焦於架構卓越,還聚焦於可靠且有效地交付高品質軟體的實務面向。
產業洞察:
- Duolingo 大幅改善其行動 CI/CD 管線,將 Android 與 iOS 建置時間縮短 68%。
- Pinterest 的 iOS 團隊轉換到使用 Bazel 作為其建置系統,使乾淨建置時間減少 21%。
安全性 (Security)#
安全性形成任何行動應用程式的必要基礎。其核心涉及保護使用者資料、確保系統完整性並防止未授權存取,同時維持順暢的使用者體驗。
🎯 何時提及
安全考量顯示對保護使用者資料與系統完整性的意識。在討論處理敏感資訊的功能、身分驗證工作流程或資料儲存決策時提及。
在設計金融、醫療保健或企業應用程式,或解釋架構如何防範常見行動漏洞時特別相關。
與其深入實作細節,讓我們探討行動開發者應熟悉的主要安全領域。雖然安全是需要專業知識才能完整處理的複雜主題,對這些領域的一般理解將協助你有效應對 MSD 面試。
裝置安全#
行動裝置在多樣且常不可預測的環境中運作。關鍵考量包括:
- iOS Jailbreak / Android Root 偵測:識別繞過安全控制的被妥協作業系統。
- App 完整性驗證:確保應用程式未被篡改。
- Android 上,Play Integrity API 驗證 App 真實性並標記修改。
- iOS 上,App Attest 確保 App 如預期運作,未被外部干擾改變。
- 安全本機儲存:妥善處理儲存在裝置上的敏感資料。
- 程式碼混淆 (Code obfuscation):使攻擊者更難反向工程應用程式。
- Android 上,R8 等工具可自動打亂程式碼,改變類別與方法名稱同時保持 App 功能完整。
網路安全#
保護傳輸中資料需要多個安全層:
- 傳輸層安全 (TLS):加密所有客戶端-伺服器通訊。
- 憑證釘選 (Certificate pinning):透過驗證伺服器憑證防止中間人攻擊。
- API 安全:防禦常見 Web 漏洞,例如注入攻擊。
- 安全離線操作:即使連線間歇時也維持安全。
資料安全#
即使本機儲存,敏感資料也需要保護:
- 靜態加密 (Encryption at rest):使用強加密演算法保護儲存資料。
- 安全金鑰管理:妥善處理加密金鑰。
- 敏感資料處理:最小化 PII 與機密資訊的暴露。
- 資料最小化:只收集與儲存必要資訊。
平衡安全與使用者體驗#
雖然穩健安全至關重要,它不應以可用性為代價。考慮這些原則:
- 使用脈絡安全對高風險操作套用更嚴格安全措施。
- 請求敏感權限時提供清楚解釋。
- 實作與受保護資料敏感度成比例的安全措施。
- 使用符合平台慣例與使用者期望的安全措施。
記住客戶端安全只是其中一層防禦。全面安全策略需要強大的行動保護與妥善保護的後端協同運作。
產業洞察: Signal 的整個系統設計以使用者隱私為中心。所有訊息與通話都是端到端加密,意味著連 Signal 的伺服器都無法讀取使用者內容。
支援不同裝置#
為廣泛裝置與平台建構引入獨特挑戰,可能強烈影響設計選擇。
在 MSD 面試中,展示對裝置碎片化的意識顯示延伸到純粹功能關注之外的理解深度。能建立跨不同硬體能力、螢幕大小與作業系統版本一致運作的解決方案,揭示對行動開發挑戰的成熟觀點。
雖然這些主題可能不主導面試討論,它們可能在深入階段出現,面試官評估候選人多麼徹底地考慮設計的實務實作挑戰。準備好處理這些關注顯示技術洞察與產品意識。
手機、平板與其他外型規格#
支援多種裝置類型不僅是錦上添花,常是可顯著擴展 App 觸及的核心業務需求。雖然手機仍是大多數 App 的主要平台,平板、可摺疊裝置、穿戴式裝置甚至電視在不同情境下提供獨特吸引使用者的機會。
🎯 何時提及
討論多外型規格支援顯示我們設計可適應介面與架構的能力。可在解釋系統如何容納不同螢幕大小、輸入方法或裝置能力時提及。在設計需要跨手機、平板、可摺疊裝置或其他專門硬體提供一致體驗的應用程式時特別相關。
成功多外型規格策略的關鍵在於 策略性模組化。App 的核心商業邏輯與資料層通常可跨裝置保持一致,因為這些元件獨立於螢幕大小或輸入方法運作。主要是 UI 與導航層需要裝置特定調整以交付最佳使用者體驗。
考慮不同外型規格如何改變使用者期望與互動模式:
- 平板 (Tablets):受益於能同時顯示更多內容的較大螢幕。平板介面可能並排顯示訊息清單與內容,而手機則將它們呈現為連續畫面。
- 可摺疊裝置 (Foldables):呈現混合挑戰,依其狀態作為手機與平板運作。設計良好的 App 在裝置於摺疊與展開狀態間轉換時動態調整。
- 穿戴式裝置 (Wearables):因螢幕空間有限,需要極度聚焦的介面,提供可一瞥的資訊與簡化互動。
- 電視 (TVs):需要使用遙控器而非觸控良好運作的介面,具較大視覺元素為遠距觀看最佳化。
現代 UI 框架(如 Android 的 Jetpack Compose 與 iOS 的 SwiftUI)使開發可適應介面變得容易得多。這些宣告式 UI 系統讓開發者建立自動調整到不同螢幕尺寸與方向的響應式佈局。
除了 UI 層,某些資料層最佳化可能對不同外型規格是必要的。例如:
- 根據可用裝置記憶體調整快取策略。
- 實作外型規格特定的圖片解析度以平衡品質與效能。
- 根據預期連線與使用模式調整預先擷取演算法。
產業洞察:
- Spotify 將其 App 調整為不同螢幕大小與解析度,確保使用者無論外型規格都有無縫體驗。
- Google Photos 透過實作為平板、可摺疊裝置與 ChromeOS 裝置量身訂做的響應式佈局,增強大螢幕使用者參與。
- Microsoft 透過最佳化佈局並納入多視窗與多實例能力,增強大螢幕 Android 裝置上 Outlook、Teams 與 Office 的使用者體驗,使活躍使用者與留存率增加。
OS 與 SDK 版本#
支援多個 OS 版本帶來涉及平衡使用者觸及與開發複雜性的挑戰。此空間中每個決策都涉及向後相容與利用最新平台能力之間的取捨。
🎯 何時提及
OS 與 SDK 版本支援展示我們對平台演進與向後相容挑戰的理解。可在討論可能依賴新平台能力的功能實作策略、跨不同 OS 版本的安全考量,或在舊裝置上維持功能的方法時提及。在針對版本採用碎片化的平台(如 Android)或設計利用前沿平台能力的功能時特別相關。
最低 OS 版本支援#
最低 OS 版本 (minimum OS version) 確立哪些裝置可以安裝並執行 App。此決策直接影響潛在使用者群:選擇較高的最低版本可能排除擁有舊裝置的使用者,而支援非常舊的版本會顯著增加開發與測試複雜性。
選擇最低版本時,我們應該考慮的不僅是市佔率統計,還包括 App 所需的特定能力。例如,擴增實境或某些安全協定等功能可能僅從特定 OS 版本起可用。
此決策應以資料驅動,權衡數個因素:
- 目標市場中的目前 OS 分佈。
- 核心功能所需的關鍵平台能力。
- 驗證跨多個版本的測試資源可用性。
- 舊 OS 版本的安全與效能考量。
目標 OS 版本#
雖然最低版本定義向後相容性,目標版本 (target version) 決定 App 可以完全利用哪些新平台功能。這通常設為最新的穩定 OS 版本。
定期更新目標版本讓我們能:
- 實作新 SDK 提供的效能改善。
- 存取增強的安全功能與隱私保護。
- 使用可能取代已棄用功能的現代 API。
- 在目前裝置上為使用者提供最佳可能體驗。
最低版本與目標版本之間的差距決定 App 需要維護多少版本特定程式碼。較寬差距可能需要更多條件邏輯與相容層,增加複雜性與運行時問題的潛在性。
API 棄用管理#
平台演進不可避免地導致 API 棄用,因為作業系統改善並取代舊功能。主動處理棄用警告防止技術債累積並減少 API 最終被移除時關鍵失敗的風險。
有效的棄用管理涉及:
- 定期檢視編譯器警告與棄用通知。
- 為受影響程式碼建立優先順序的遷移計畫。
- 在舊與新 OS 版本上徹底測試。
- 維護版本特定實作的清楚文件。
策略性測試方法#
跨支援 OS 版本測試需要系統化方法以確保一致行為。例如,與其在每個版本上測試每個功能,我們可以聚焦於:
- 在最低、目標與一個中間版本上的完整覆蓋測試。
- 在所有支援版本上的冒煙測試 (smoke testing)。
- 更深入測試依賴版本特定實作的功能。
- 根據真實世界使用分析決定測試情境優先順序。
📌 請記住!
OS 更新採用率在不同平台間顯著差異。iOS 使用者通常比 Android 使用者升級更快,後者可能受製造商限制或裝置年齡所限。
OS 版本策略應隨時間演進。我們應隨使用者分析轉變定期重新評估最低版本需求,利用每次主要 App 更新作為潛在放棄支援使用者比例遞減的舊版本的機會。