第 4 部分 - 處理多個平台
處理平台差異和功能
分歧不僅僅是一個「跨平臺」問題;「相同」平臺上的裝置具有不同的功能(尤其是各種可用的 Android 裝置)。 最明顯的和基本是螢幕大小,但其他裝置屬性可能會有所不同,而且需要應用程式根據其存在(或缺席)來檢查特定功能並有不同的行為。
這表示所有應用程式都需要處理功能正常降低,否則會呈現不具吸引力、最低通用分母功能集。 Xamarin 與每個平臺原生 SDK 的深度整合可讓應用程式利用平臺特定功能,因此設計應用程式以使用這些功能是合理的。
如需平臺在功能上有何差異的概觀,請參閱平臺功能檔。
平台差異的範例
跨平臺存在的基本元素
行動應用程式有一些通用的特性。 這些概念通常適用於所有裝置,因此可以形成應用程式設計的基礎:
- 透過索引標籤或功能選取功能
- 數據和卷動的清單
- 單一數據檢視
- 編輯單一數據檢視
- 往後巡覽
設計高階螢幕流程時,您可以基於這些概念的常見用戶體驗。
平臺特定屬性
除了存在於所有平臺上的基本元素之外,您還需要解決設計的主要平台差異。 您可能需要考慮這些差異(並特別撰寫程式代碼來處理):
- 螢幕大小 – 某些平臺(例如 iOS 和舊版 Windows 電話 版本)具有相對容易以為目標的標準化螢幕大小。 Android 裝置有各種不同的螢幕尺寸,需要更努力地支援您的應用程式。
- 流覽隱喻 – 不同平臺 (例如硬體 'back' 按鈕、全景 UI 控件)和平臺內 (Android 2 和 4, i 電話 vs iPad)。
- 鍵盤 – 某些 Android 裝置具有實體鍵盤,而其他裝置則只有軟體鍵盤。 偵測到螢幕中虛鍵盤遮蔽部分時的程式代碼,必須對這些差異有敏感性。
- 觸控和手勢 – 操作系統對手勢辨識的支援會有所不同,特別是在舊版的每種操作系統中。 舊版 Android 對觸控作業的支援非常有限,這表示支援較舊的裝置可能需要個別的程式代碼
- 推播通知 – 每個平臺上都有不同的功能/實作(例如Windows 上的動態磚)。
裝置特定功能
判斷應用程式所需的最低功能為何;或決定要在每個平臺上利用哪些其他功能時。 程式代碼需要偵測功能並停用功能或提供替代方案(例如地理位置的替代方案可能是讓使用者輸入位置或從地圖中選擇):
- 相機 – 不同裝置的功能不同:有些裝置沒有相機,有些裝置有正面和後向相機。 有些相機能夠錄製視訊。
- 地理位置和地圖 – 所有裝置上都沒有 GPS 或 Wi-Fi 位置的支援。 應用程式也需要滿足每個方法所支援的各種精確度層級。
- 加速計、陀螺儀和指南針 – 這些功能通常只會在每個平臺上選擇裝置中找到,因此當不支持硬體時,應用程式幾乎一律需要提供後援。
- Twitter 和 Facebook – 僅分別在 iOS5 和 iOS6 上「內建」。 在舊版和其他平臺上,您必須提供您自己的驗證函式,並直接與每個服務的 API 介面。
- 近距離現場通信 (NFC) - 僅在 (一些) Android 手機上 (在寫作時) 。
處理平台差異
有兩種不同的方法可從相同的程式代碼基底支援多個平臺,每個平臺都有其本身的一組優點和缺點。
- 平臺抽象 – 商務外觀模式,提供跨平台的統一存取,並將特定平台實作抽象化成單一整合的 API。
- 不同的實 作 – 透過介面和繼承或條件式編譯等架構工具,透過不同的實作調用特定平臺功能。
平台抽象
類別抽象概念
使用在共用程式代碼中定義的介面或基類,並在平臺特定專案中實作或擴充。 撰寫和擴充具有類別抽象概念的共用程式代碼特別適合可攜式類別庫,因為它們的架構子集有限,而且不能包含編譯程式指示詞來支援平臺特定的程式代碼分支。
介面
使用介面可讓您實作仍可傳遞至共用連結庫的平臺特定類別,以利用一般程序代碼。
介面定義於共用程式代碼中,並傳遞至共用連結庫做為參數或屬性。
平臺特定應用程式接著可以實作 介面,但仍利用共用程式代碼來「處理」它。
優點
實作可以包含平臺特定的程序代碼,甚至參考平臺特定的外部連結庫。
缺點
必須建立實作並將實作傳遞至共享程序代碼。 如果介面在共用程序代碼中深入使用,則最終會透過多個方法參數傳遞,否則會透過呼叫鏈結向下推送。 如果共用程式代碼使用許多不同的介面,則必須在某處的共用程序代碼中建立和設定它們。
繼承
共用程式代碼可以實作一或多個平臺特定專案中可以擴充的抽象或虛擬類別。 這類似於使用介面,但已實作某些行為。 介面或繼承是否為較佳的設計選擇有不同的觀點:特別是因為 C# 只允許單一繼承,所以它可以決定 API 未來設計的方式。 請小心使用繼承。
介面的優點和缺點同樣適用於繼承,而基類可以包含一些實作程式代碼的額外優點(或許是可選擇性擴充的整個平臺無關實作)。
Xamarin.Forms
其他跨平台連結庫
這些連結庫也為 C# 開發人員提供跨平臺功能:
- Xamarin.Essentials – 適用於常見功能的跨平臺 API。
- SkiaSharp – 跨平臺 2D 圖形。
條件式編譯
在某些情況下,您的共用程式代碼仍需要在每個平臺上以不同的方式運作,可能存取不同行為的類別或功能。 條件式編譯最適合與共用資產專案搭配使用,其中在已定義不同符號的多個專案中會參考相同的原始程序檔。
Xamarin 專案一律會定義 __MOBILE__
iOS 和 Android 應用程式專案的 true (請注意這些符號上的雙底線前置和後置修正)。
#if __MOBILE__
// Xamarin iOS or Android-specific code
#endif
iOS
Xamarin.iOS 會 __IOS__
定義可用來偵測 iOS 裝置的裝置。
#if __IOS__
// iOS-specific code
#endif
還有手錶和電視特定的符號:
#if __TVOS__
// tv-specific stuff
#endif
#if __WATCHOS__
// watch-specific stuff
#endif
Android
只應該編譯成 Xamarin.Android 應用程式的程式代碼可以使用下列程式代碼
#if __ANDROID__
// Android-specific code
#endif
每個 API 版本也會定義新的編譯程式指示詞,因此,如果目標為較新的 API,這類程式代碼可讓您新增功能。 每個 API 層級都包含所有「較低」層級符號。 這項功能對於支援多個平臺並不實用;一般而言, __ANDROID__
符號就已足夠。
#if __ANDROID_11__
// code that should only run on Android 3.0 Honeycomb or newer
#endif
Mac:
Xamarin.Mac 會 __MACOS__
定義您只能用於針對 macOS 進行編譯的 :
#if __MACOS__
// macOS-specific code
#endif
通用 Windows 平台 (UWP)
使用 WINDOWS_UWP
。 字串周圍沒有底線,例如 Xamarin 平台符號。
#if WINDOWS_UWP
// UWP-specific code
#endif
使用條件式編譯
條件式編譯的簡單案例研究範例是設定 SQLite 資料庫檔案的檔案位置。 這三個平臺對於指定檔案位置的需求稍有不同:
- iOS – Apple 偏好將非用戶數據放在特定位置 (Library 目錄),但此目錄沒有系統常數。 需要平臺特定的程序代碼,才能建置正確的路徑。
- Android – 傳
Environment.SpecialFolder.Personal
回的系統路徑是儲存資料庫檔案的可接受位置。 - Windows 電話 – 隔離儲存機制不允許指定完整路徑,只有相對路徑和檔名。
- 通用 Windows 平台 – 使用
Windows.Storage
API。
下列程式代碼會使用條件式編譯來確保 DatabaseFilePath
每個平臺都正確:
public static string DatabaseFilePath
{
get
{
var filename = "TodoDatabase.db3";
#if SILVERLIGHT
// Windows Phone 8
var path = filename;
#else
#if __ANDROID__
string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#else
#if __IOS__
// we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms
// (they don't want non-user-generated data in Documents)
string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
string libraryPath = Path.Combine (documentsPath, "..", "Library");
#else
// UWP
string libraryPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
#endif
#endif
var path = Path.Combine(libraryPath, filename);
#endif
return path;
}
}
結果是可在所有平臺上建置及使用的類別,將 SQLite 資料庫檔案放在每個平臺上的不同位置。