跨平台應用程式案例研究:工作
Tasky Portable 是簡單的 To-do 列表應用程式。 本文件討論如何設計及建置,並遵循建置跨平臺應用程式檔的指引。 討論涵蓋下列領域:
設計程式
建議您在開始撰寫程序代碼之前,為想要達到的目標建立藍圖。 這特別適用於跨平台開發,您可以在其中建置會以多種方式公開的功能。 從清楚瞭解您要建置的項目開始,可節省開發週期稍後的時間和精力。
需求
設計應用程式的第一個步驟是識別所需的功能。 這些可以是高階目標或詳細的使用案例。 Tasky 具有直接的功能需求:
- 檢視工作清單
- 新增、編輯和刪除工作
- 將工作的狀態設定為 [完成]
您應該考慮使用平臺特定功能。 Tasky 可以利用 iOS 地理柵欄或 Windows Phone 動態磚嗎? 即使您未在第一個版本中使用平臺特定功能,您也應該提前規劃,以確保您的商務和數據層可以容納它們。
使用者介面設計
從可跨目標平台實作的高階設計開始。 請小心記下平台規格 UI 條件約束。 例如, TabBarController
iOS 中的 可以顯示五個以上的按鈕,而 Windows Phone 對等專案最多可以顯示四個按鈕。
使用您選擇的工具繪製螢幕流程(紙張作品)。
資料模型
瞭解需要儲存哪些數據將有助於判斷要使用的持續性機制。 如需可用儲存機制的相關信息,以及協助決定它們之間的資訊,請參閱 跨平臺數據存取 。 在此專案中,我們將使用 SQLite.NET。
Tasky 需要為每個 'TaskItem' 儲存三個屬性:
- 名稱 – 字串
- 附註 – 字串
- 完成 – 布爾值
核心功能
請考慮使用者介面需要取用的 API,以符合需求。 To-do 清單需要下列函式:
- 列出所有工作 – 顯示所有可用工作的主畫面清單
- 取得一個工作 – 當工作數據列觸碰時
- 儲存一個工作 – 編輯工作時
- 刪除工作 – 刪除工作時
- 建立空白工作 – 建立新工作時
若要實現程式代碼重複使用,此 API 應該在 可攜式類別庫中實作一次。
實作
一旦達成應用程式設計後,請考慮如何將它實作為跨平臺應用程式。 這會成為應用程式的架構。 遵循建置跨平臺應用程式檔中的指引,應用程式程式代碼應該分成下列部分:
- Common Code – 包含可重複使用的程式代碼來儲存工作數據的通用專案;公開 Model 類別和 API 來管理資料的儲存和載入。
- 平臺特定程式代碼 – 針對每個作業系統實作原生 UI 的平臺特定專案,並利用通用程式代碼作為「後端」。
下列各節將說明這兩個部分。
通用 (PCL) 程式代碼
Tasky Portable 會使用可攜式類別庫策略來共用一般程序代碼。 如需程式代碼共享選項的描述,請參閱共用程式代碼選項檔。
所有常見的程式代碼,包括數據存取層、資料庫程式代碼和合約,都會放在連結庫專案中。
完整的 PCL 專案如下所示。 可攜式連結庫中的所有程式代碼都與每個目標平臺相容。 部署時,每個原生應用程式都會參考該連結庫。
下列類別圖表顯示依圖層分組的類別。 類別 SQLiteConnection
是 Sqlite-NET 套件中的重複使用程式代碼。 其餘類別是 Tasky 的自訂程式代碼。 TaskItemManager
和 TaskItem
類別代表公開給平臺特定應用程式的 API。
使用命名空間來分隔圖層有助於管理每個圖層之間的參考。 平臺特定項目應該只需要包含 using
商務層的語句。 數據存取層和數據層應該由商務層中公開 TaskItemManager
的 API 封裝。
參考資料
可攜式類別庫需要跨多個平臺使用,每個平臺都有不同層級的平臺和架構功能支援。 因此,可以使用套件和架構連結庫的限制。 例如,Xamarin.iOS 不支援 c# dynamic
關鍵詞,因此可攜式類別庫無法使用任何相依於動態程式碼的套件,即使這類程式碼可在 Android 上運作。 Visual Studio for Mac 會防止您新增不相容的套件和參考,但您會想要記住限制,以避免稍後發生意外。
注意:您會看到項目參考您尚未使用的架構連結庫。 這些參考會包含在 Xamarin 專案範本中。 編譯應用程式時,連結程式將會移除未參考的程序代碼,因此即使 System.Xml
已參考,它也不會包含在最終應用程式中,因為我們未使用任何 Xml 函式。
資料層 (DL)
數據層包含執行數據實體儲存的程式代碼,無論是資料庫、一般檔案或其他機制。 Tasky 數據層包含兩個部分:SQLite-NET 連結庫和新增來連接它的自定義程式代碼。
Tasky 依賴 Sqlite-net NuGet 套件(由 Frank Krueger 發佈)來內嵌 SQLite-NET 程式代碼,以提供物件關係型對應 (ORM) 資料庫介面。 類別 TaskItemDatabase
繼承自 SQLiteConnection
,並將必要的 Create、Read、Update、Delete (CRUD) 方法新增至 SQLite 讀取和寫入數據。 這是可在其他項目中重複使用之泛型 CRUD 方法的簡單未定案實作。
TaskItemDatabase
是單一實例,可確保所有存取都會針對相同的實例進行。 鎖定可用來防止並行存取多個線程。
Windows Phone 上的 SQLite
雖然 iOS 和 Android 都隨附於 SQLite 作為作業系統的一部分,但 Windows Phone 不包含相容的資料庫引擎。 若要在所有三個平臺上共用程序代碼,需要 Windows Phone 原生版本的 SQLite。 如需設定 Sqlite Windows Phone 專案的詳細資訊,請參閱 使用本機資料庫 。
使用介面將數據存取一般化
數據層需要相依性 BL.Contracts.IBusinessIdentity
,以便實作需要主鍵的抽象數據存取方法。 然後,任何實作介面的商務層類別都可以保存在數據層中。
介面只會指定要作為主鍵的整數屬性:
public interface IBusinessEntity {
int ID { get; set; }
}
基類會實作 介面,並新增 SQLite-NET 屬性,將它標示為自動遞增的主鍵。 接著,實作此基類的商務層中的任何類別都可以保存在數據層中:
public abstract class BusinessEntityBase : IBusinessEntity {
public BusinessEntityBase () {}
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
}
使用 介面之數據層中的泛型方法範例如下 GetItem<T>
:
public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
lock (locker) {
return Table<T>().FirstOrDefault(x => x.ID == id);
}
}
鎖定以防止並行存取
鎖定會在 類別內實作TaskItemDatabase
,以防止並行存取資料庫。 這是為了確保來自不同線程的並行存取已串行化(否則 UI 元件可能會在背景線程更新資料庫時嘗試讀取資料庫)。 以下是如何實作鎖定的範例:
static object locker = new object ();
public IEnumerable<T> GetItems<T> () where T : BL.Contracts.IBusinessEntity, new ()
{
lock (locker) {
return (from i in Table<T> () select i).ToList ();
}
}
public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
lock (locker) {
return Table<T>().FirstOrDefault(x => x.ID == id);
}
}
大部分的數據層程式代碼都可以在其他項目中重複使用。 圖層中唯一的應用程式特定程式代碼是 CreateTable<TaskItem>
建構函式中的 TaskItemDatabase
呼叫。
資料存取層 (DAL)
類別 TaskItemRepository
會使用強型別 API 封裝資料儲存機制,以允許 TaskItem
建立、刪除、擷取和更新物件。
使用條件式編譯
類別會使用條件式編譯來設定檔案位置 - 這是實作平台差異的範例。 傳回路徑的屬性會編譯至每個平臺上的不同程序代碼。 程式代碼和平臺特定的編譯程式指示詞如下所示:
public static string DatabaseFilePath {
get {
var sqliteFilename = "TaskDB.db3";
#if SILVERLIGHT
// Windows Phone expects a local path, not absolute
var path = sqliteFilename;
#else
#if __ANDROID__
// Just use whatever directory SpecialFolder.Personal returns
string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); ;
#else
// 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"); // Library folder
#endif
var path = Path.Combine (libraryPath, sqliteFilename);
#endif
return path;
}
}
視平臺而定,輸出會是iOS的「<應用程式路徑/連結庫/TaskDB.db3」、適用於Android的「<應用程式路徑>>/檔/TaskDB.db3」,或只針對Windows Phone 使用 “TaskDB.db3”。
商務層 (BL)
商務層會實作模型類別和外觀來管理它們。
在 Tasky 中,模型是 類別 TaskItem
,並 TaskItemManager
實作外觀模式,以提供用於管理 TaskItems
的 API。
外觀
TaskItemManager
包裝 DAL.TaskItemRepository
,以提供應用程式和UI層將參考的 Get、Save 和Delete方法。
如有需要,商務規則和邏輯會放在這裡,例如儲存物件之前必須滿足的任何驗證規則。
平臺特定程式代碼的 API
撰寫一般程式代碼之後,必須建置使用者介面來收集和顯示其公開的數據。 類別 TaskItemManager
會實作外觀模式,以提供簡單的 API 供應用程式程式代碼存取。
在每個平臺特定專案中撰寫的程式代碼通常會緊密結合至該裝置的原生 SDK,而且只會使用 所 TaskItemManager
定義的 API 來存取通用程式代碼。 這包括它公開的方法和商務類別,例如 TaskItem
。
映射不會跨平台共用,但會獨立新增至每個專案。 這很重要,因為每個平臺會使用不同的檔名、目錄和解析度,以不同的方式處理映像。
其餘各節會討論Tasky UI的平臺特定實作詳細數據。
iOS 應用程式
使用常見的 PCL 專案來儲存和擷取數據,實作 iOS Tasky 應用程式只需要少數類別。 完整的 iOS Xamarin.iOS 專案如下所示:
此圖表中會顯示類別,並分組為圖層。
參考資料
iOS 應用程式會參考平臺特定的 SDK 連結庫 ,例如 Xamarin.iOS 和 MonoTouch.Dialog-1。
它也必須參考 TaskyPortableLibrary
PCL 專案。
參考清單如下所示:
應用層和使用者介面層會使用這些參考在此專案中實作。
應用程式層 (AL)
應用層包含將 PCL 公開的物件系結至 UI 所需的平臺特定類別。 iOS 特定應用程式有兩個類別,可協助顯示工作:
- EditingSource – 這個類別可用來將工作清單系結至使用者介面。 因為
MonoTouch.Dialog
用於工作清單,所以我們需要實作此協助程式,才能在 中UITableView
啟用撥動刪除功能。 撥動到刪除在 iOS 上很常見,但不適用於 Android 或 Windows Phone,因此 iOS 特定專案是唯一實作它的專案。 - TaskDialog – 這個類別可用來將單一工作系結至 UI。 它會使用
MonoTouch.Dialog
反映 API 來以包含正確屬性的類別來「包裝」TaskItem
物件,以允許正確格式化輸入畫面。
類別 TaskDialog
會使用 MonoTouch.Dialog
屬性,根據類別的屬性來建立畫面。 此類別看起來像這樣:
public class TaskDialog {
public TaskDialog (TaskItem task)
{
Name = task.Name;
Notes = task.Notes;
Done = task.Done;
}
[Entry("task name")]
public string Name { get; set; }
[Entry("other task info")]
public string Notes { get; set; }
[Entry("Done")]
public bool Done { get; set; }
[Section ("")]
[OnTap ("SaveTask")] // method in HomeScreen
[Alignment (UITextAlignment.Center)]
public string Save;
[Section ("")]
[OnTap ("DeleteTask")] // method in HomeScreen
[Alignment (UITextAlignment.Center)]
public string Delete;
}
請注意, OnTap
屬性需要方法名稱 – 這些方法必須存在於建立 的類別 MonoTouch.Dialog.BindingContext
中(在此案例中, HomeScreen
下一節所討論的類別)。
使用者介面層 (UI)
使用者介面層包含下列類別:
- AppDelegate – 包含對外觀 API 的呼叫,以設定應用程式中所使用的字型和色彩樣式。 Tasky 是簡單的應用程式,因此沒有其他在 中
FinishedLaunching
執行的初始化工作。 - Screens – 的子類別
UIViewController
,可定義每個畫面及其行為。 畫面會將 UI 與應用層類別和通用 API 結合在一起。TaskItemManager
在此範例中,畫面是在程序代碼中建立的,但可能是使用 Xcode 的 Interface Builder 或分鏡腳本設計工具所設計。 - 影像 – 視覺元素是每個應用程式的重要部分。 Tasky 具有啟動顯示畫面和圖示影像,其適用於 iOS 必須以一般和 Retina 解析度提供。
主畫面
主畫面是顯示 MonoTouch.Dialog
SQLite 資料庫中工作清單的畫面。 它會繼承自 DialogViewController
並實作程式代碼,以設定 Root
,以包含要顯示的物件集合 TaskItem
。
與顯示和與工作清單互動相關的兩個主要方法如下:
- PopulateTable – 使用商務層
TaskManager.GetTasks
的 方法來擷取要顯示的物件TaskItem
集合。 - 已選取 – 當觸及數據列時,會在新畫面中顯示工作。
工作詳細數據畫面
[任務詳細數據] 是一個輸入畫面,允許編輯或刪除工作。
Tasky 會使用 MonoTouch.Dialog
的反映 API 來顯示畫面,因此沒有 UIViewController
實作。 相反地,類別會HomeScreen
具現化並使用應用層中的 TaskDialog
類別來顯示 DialogViewController
。
此螢幕快照顯示空白畫面,示範屬性Entry
在 [名稱] 和 [附註] 字段中設定浮水印文字:
[工作詳細數據] 畫面的功能(例如儲存或刪除工作)必須在 類別中HomeScreen
實作,因為這是建立 的位置MonoTouch.Dialog.BindingContext
。 下列 HomeScreen
方法支援 [工作詳細資料] 畫面:
- ShowTaskDetails – 建立
MonoTouch.Dialog.BindingContext
來轉譯畫面。 它會使用反映建立輸入畫面,以從TaskDialog
類別擷取屬性名稱和類型。 其他資訊,例如輸入方塊的浮浮浮浮水印文字,是以屬性上的屬性實作。 - SaveTask – 這個方法會透過
OnTap
屬性在 類別中TaskDialog
參考。 按下 [儲存] 時呼叫它,並使用MonoTouch.Dialog.BindingContext
來擷取使用者輸入的數據,再使用TaskItemManager
儲存變更。 - DeleteTask – 這個方法是透過
OnTap
屬性在TaskDialog
類別中參考。 它會使用TaskItemManager
來使用主鍵來刪除資料(ID 屬性)。
Android 應用程式
完整的 Xamarin.Android 專案如下圖所示:
類別圖表,類別依圖層分組:
參考資料
Android 應用程式項目必須參考平臺特定的 Xamarin.Android 元件,才能從 Android SDK 存取類別。
它也必須參考 PCL 專案(例如TaskyPortableLibrary) 可存取一般數據和商務層程序代碼。
應用程式層 (AL)
與我們稍早探討的 iOS 版本類似,Android 版本中的應用程式層包含「系結」Core 所公開物件至 UI 所需的平臺特定類別。
TaskListAdapter – 若要顯示物件清單<T>,我們需要實作配接器以在 中ListView
顯示自定義物件。 配接器會控制清單中每個專案所使用的設定– 在此案例中,程式代碼會使用 Android 內建設定 SimpleListItemChecked
。
使用者介面 (UI)
Android 應用程式的使用者介面層是程式代碼和 XML 標記的組合。
- 資源/版面配置 – 螢幕配置,以及實作為 AXML 檔案的數據列數據格設計。 AXML 可以手動撰寫,或使用適用於 Android 的 Xamarin UI 設計工具以可視化方式進行配置。
- 資源/可 繪製 – 影像(圖示)和自定義按鈕。
- Screens – 定義每個畫面及其行為的活動子類別。 將UI與應用層類別和通用 API 系結在一起。
TaskItemManager
主畫面
主畫面是由 Activity 子類別 HomeScreen
和 HomeScreen.axml
檔案所組成,可定義版面配置(按鈕和工作清單的位置)。 畫面看起來像這樣:
主畫面程式代碼會定義處理程式,以按下按鈕和按兩下清單中的專案,以及在方法中 OnResume
填入清單(使其反映工作詳細資料畫面所做的變更)。 數據會使用商務層和TaskItemManager
TaskListAdapter
應用層的 載入。
工作詳細數據畫面
[工作詳細數據] 畫面也包含 Activity
子類別和 AXML 版面配置檔案。 配置會決定輸入控件的位置,而 C# 類別會定義載入和儲存 TaskItem
物件的行為。
PCL 連結庫的所有參考都是透過 類別 TaskItemManager
。
Windows Phone 應用程式
完整的 Windows Phone 專案:
下圖顯示分組為層次的類別:
參考資料
平臺特定項目必須參考必要的平臺特定連結庫(例如 Microsoft.Phone
和 System.Windows
),才能建立有效的 Windows Phone 應用程式。
它也必須參考 PCL 專案 (例如 TaskyPortableLibrary
) 來利用 TaskItem
類別和資料庫。
應用程式層 (AL)
同樣地,如同 iOS 和 Android 版本,應用層包含非視覺元素,可協助將數據系結至使用者介面。
ViewModels
ViewModels 會包裝 PCL ( TaskItemManager
的數據,並以 Silverlight/XAML 數據系結可使用的方式呈現。 這是平臺特定行為的範例(如跨平臺應用程式檔中所述)。
使用者介面 (UI)
XAML 具有唯一的數據系結功能,可在標記中宣告,並減少顯示物件所需的程式代碼數量:
- 頁面 – XAML 檔案及其程式代碼後置會定義使用者介面,並參考 ViewModels 和 PCL 專案來顯示和收集數據。
- 影像 – 啟動顯示畫面、背景和圖示影像是使用者介面的重要部分。
MainPage
MainPage 類別會使用 TaskListViewModel
來使用 XAML 的數據系結功能來顯示數據。 頁面會 DataContext
設定為以異步方式填入的檢視模型。 {Binding}
XAML 中的語法會決定數據的顯示方式。
TaskDetailsPage
每個工作都是藉由將 系結 TaskViewModel
至TaskDetailsPage.xaml 中定義的 XAML 來顯示。 工作數據是透過 TaskItemManager
商務層中的擷取。
結果
產生的應用程式在每個平台上看起來會像這樣:
iOS
應用程式會使用 iOS 標準使用者介面設計,例如位於導覽列中的 [新增] 按鈕,並使用內 建加號 (+) 圖示。 它也會使用預設 UINavigationController
的 『back』 按鈕行為,並支持數據表中的 『swipe-to-delete』。
Android
Android 應用程式會使用內建控件,包括需要顯示「刻度」的數據列內建版面配置。 除了螢幕返回按鈕之外,還支援硬體/系統返回行為。
Windows Phone
Windows Phone 應用程式會使用標準版面配置,在畫面底部填入應用程式行,而不是頂端的導覽列。
摘要
本檔已詳細說明分層應用程式設計的原則如何套用至簡單的應用程式,以協助跨三個行動平臺重複使用程式碼:iOS、Android 和 Windows Phone。
它描述了用來設計應用層的程式,並討論了每個層中已實作的程式代碼和功能。
您可以從 github 下載程式代碼。