第 5 部分 - 實用的程式碼共用策略
本節提供如何共用常見應用程式案例程序代碼的範例。
數據層
數據層是由儲存引擎和讀取和寫入資訊的方法所組成。 為了達到效能、彈性和跨平臺相容性,建議針對 Xamarin 跨平臺應用程式使用 SQLite 資料庫引擎。 它會在各種不同的平台上執行,包括 Windows、Android、iOS 和 Mac。
SQLite
SQLite 是開放原始碼資料庫實作。 您可以在 SQLite.org 找到來源和檔。SQLite 支援可在每個行動平臺上取得:
- iOS – 內建至作業系統。
- Android – 內建於自 Android 2.2 (API 層級 10) 以來的操作系統。
- Windows – 請參閱 SQLite for 通用 Windows 平台 擴充功能。
即使所有平臺上都有可用的資料庫引擎,存取資料庫的原生方法也不同。 iOS 和 Android 都提供內建 API 來存取可從 Xamarin.iOS 或 Xamarin.Android 使用的 SQLite,不過使用原生 SDK 方法則無法共用程式代碼(除了 SQL 查詢本身以外,假設它們儲存為字元串)。 如需 iOS 或 Android 類別SQLiteOpenHelper
中搜尋CoreData
原生資料庫功能的詳細數據,因為這些選項並非跨平臺,因此超出本文件的範圍。
ADO.NET
Xamarin.iOS 和 Xamarin.Android 都支援 System.Data
和 Mono.Data.Sqlite
(如需詳細資訊,請參閱 Xamarin.iOS 檔 )。
使用這些命名空間可讓您撰寫可在這兩個平台上運作 ADO.NET 程序代碼。 編輯項目的參考,以包含 System.Data.dll
和 Mono.Data.Sqlite.dll
,並將這些 using 語句新增至您的程式代碼:
using System.Data;
using Mono.Data.Sqlite;
然後,下列範例程式代碼將會運作:
string dbPath = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.Personal),
"items.db3");
bool exists = File.Exists (dbPath);
if (!exists)
SqliteConnection.CreateFile (dbPath);
var connection = new SqliteConnection ("Data Source=" + dbPath);
connection.Open ();
if (!exists) {
// This is the first time the app has run and/or that we need the DB.
// Copy a "template" DB from your assets, or programmatically create one like this:
var commands = new[]{
"CREATE TABLE [Items] (Key ntext, Value ntext);",
"INSERT INTO [Items] ([Key], [Value]) VALUES ('sample', 'text')"
};
foreach (var command in commands) {
using (var c = connection.CreateCommand ()) {
c.CommandText = command;
c.ExecuteNonQuery ();
}
}
}
// use `connection`... here, we'll just append the contents to a TextView
using (var contents = connection.CreateCommand ()) {
contents.CommandText = "SELECT [Key], [Value] from [Items]";
var r = contents.ExecuteReader ();
while (r.Read ())
Console.Write("\n\tKey={0}; Value={1}",
r ["Key"].ToString (),
r ["Value"].ToString ());
}
connection.Close ();
ADO.NET 的實際實作顯然會分割到不同的方法和類別之間(本範例僅供示範之用)。
SQLite-NET – 跨平台 ORM
ORM(或物件關係型對應程式)會嘗試簡化類別中模型化數據的儲存。 ORM 不會手動撰寫 CREATE TABLEs 或 SELECT、INSERT 和 DELETE 數據,而是從類別字段和屬性手動擷取,而是會為您新增一層程式代碼。 使用反映來檢查類別的結構,ORM 可以自動建立符合類別的數據表和數據行,併產生查詢來讀取和寫入數據。 這可讓應用程式程式代碼只傳送和擷取物件實例到 ORM,這會負責處理所有 SQL 作業。
SQLite-NET 可作為簡單的 ORM,可讓您在 SQLite 中儲存和擷取類別。 它會使用編譯程式指示詞和其他技巧的組合,隱藏跨平臺 SQLite 存取的複雜性。
SQLite-NET 的功能:
- 數據表是藉由將屬性加入模型類別來定義。
- 資料庫實例是由 的子類別
SQLiteConnection
表示,這是 SQLite-Net 連結庫中的主要類別。 - 您可以使用物件來插入、查詢和刪除資料。 不需要 SQL 語句(雖然您可以視需要撰寫 SQL 語句)。
- 基本 Linq 查詢可以在 SQLite-NET 所傳回的集合上執行。
SQLite-NET 的原始程式碼和檔可在 github 上的 SQLite-Net 取得,並已在這兩個案例研究中實作。 SQLite-NET 程式代碼的簡單範例如下所示(來自 Tasky Pro 案例研究)。
首先,類別 TodoItem
會使用屬性來定義要成為資料庫主鍵的欄位:
public class TodoItem : IBusinessEntity
{
public TodoItem () {}
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public bool Done { get; set; }
}
這可讓 TodoItem
數據表在 實例上使用 SQLiteConnection
下列程式代碼行來建立(且沒有 SQL 語句):
CreateTable<TodoItem> ();
數據表中的數據也可以與 上的 SQLiteConnection
其他方法一起操作(同樣地,不需要 SQL 語句):
Insert (TodoItem); // 'task' is an instance with data populated in its properties
Update (TodoItem); // Primary Key field must be populated for Update to work
Table<TodoItem>.ToList(); // returns all rows in a collection
如需完整的範例,請參閱案例研究原始程式碼。
檔案存取
檔案存取一定是任何應用程式的重要部分。 可能屬於應用程式一部分的檔案常見範例包括:
- SQLite 資料庫檔案。
- 用戶產生的數據(文字、影像、聲音、視訊)。
- 已下載快取的數據(影像、html 或 PDF 檔案)。
System.IO 直接存取
Xamarin.iOS 和 Xamarin.Android 都允許使用命名空間中的 System.IO
類別來存取檔案系統。
每個平臺都有必須考慮的不同存取限制:
- iOS 應用程式會在具有非常受限檔案系統存取權的沙箱中執行。 Apple 會藉由指定備份的特定位置,以及未備份的其他位置,來進一步指示您應該如何使用文件系統。 如需 詳細資訊,請參閱在 Xamarin.iOS 中使用文件系統指南。
- Android 也會限制存取與應用程式相關的特定目錄,但它也支援外部媒體(例如SD 卡)和存取共享數據。
- Windows Phone 8 (Silverlight) 不允許直接檔案存取 – 只能使用
IsolatedStorage
操作檔案。 - Windows 8.1 WinRT 和 Windows 10 UWP 專案僅透過
Windows.Storage
API 提供異步檔案作業,這與其他平臺不同。
iOS 和 Android 的範例
撰寫和讀取文字文件的簡單範例如下所示。
使用 Environment.GetFolderPath
可讓相同的程式代碼在iOS和Android上執行,每個程式代碼都會根據其文件系統慣例傳回有效的目錄。
string filePath = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.Personal),
"MyFile.txt");
System.IO.File.WriteAllText (filePath, "Contents of text file");
Console.WriteLine (System.IO.File.ReadAllText (filePath));
如需 iOS 特定文件系統功能的詳細資訊,請參閱 Xamarin.iOS 使用文件系統 檔。 撰寫跨平臺檔案存取程式代碼時,請記住,某些文件系統區分大小寫,且具有不同的目錄分隔符。 在建構檔案或目錄路徑時,一律使用相同的檔名大小寫和 Path.Combine()
方法是很好的作法。
適用於 Windows 8 和 Windows 10 的 Windows.Storage
使用 Xamarin.Forms 建立行動應用程式一章第 20 章。異步和檔案 I/O 包含適用於 Windows 8.1 和 Windows 10 的範例。
DependencyService
使用,可以使用支援的 API,在這些平台上讀取和檔案檔案:
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
IStorageFile storageFile = await localFolder.CreateFileAsync("MyFile.txt",
CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, "Contents of text file");
如需詳細資訊, 請參閱第 20 章。
Windows Phone 7 和 8 上的隔離存儲設備(Silverlight)
隔離儲存區是一種常見的 API,可用於儲存和載入所有 iOS、Android 和舊版 Windows Phone 平台的檔案。
這是 Windows Phone (Silverlight) 中已在 Xamarin.iOS 和 Xamarin.Android 中實作之檔案存取的默認機制,以允許撰寫常見的檔案存取程式代碼。 您可以在共享專案中的所有三個平台中參考 類別System.IO.IsolatedStorage
。
如需詳細資訊,請參閱 Windows Phone 的隔離儲存區概觀。
在可攜式類別庫中無法使用隔離儲存 API。 PCL 的其中一個替代方案是 PCLStorage NuGet
PCL 中的跨平台檔案存取
此外,還有 PCL 相容的 NuGet – PCLStorage – 為支援 Xamarin 的平臺和最新的 Windows API 提供跨平台檔案存取。
網路作業
大部分的行動應用程式都會有網路元件,例如:
- 下載影像、視訊和音訊(例如縮圖、相片、音樂)。
- 下載檔(例如HTML、PDF)。
- 上傳用戶數據(例如相片或文字)。
- 存取 Web 服務或第三方 API(包括 SOAP、XML 或 JSON)。
.NET Framework 提供幾個不同的類別來存取網路資源: HttpClient
、 WebClient
和 HttpWebRequest
。
HttpClient
HttpClient
命名空間中的 System.Net.Http
類別可在 Xamarin.iOS、Xamarin.Android 和大部分的 Windows 平臺中使用。 有一個 Microsoft HTTP 用戶端連結庫 NuGet 可用來將此 API 帶入可攜式類別庫 (和 Windows Phone 8 Silverlight)。
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://xamarin.com");
var response = await myClient.SendAsync(request);
WebClient
類別 WebClient
提供簡單的 API,可從遠端伺服器擷取遠端資料。
通用 Windows 平台 作業必須是異步的,即使 Xamarin.iOS 和 Xamarin.Android 支援同步作業(可以在背景線程上完成)。
簡單異步 WebClient
作業的程式代碼如下:
var webClient = new WebClient ();
webClient.DownloadStringCompleted += (sender, e) =>
{
var resultString = e.Result;
// do something with downloaded string, do UI interaction on main thread
};
webClient.Encoding = System.Text.Encoding.UTF8;
webClient.DownloadStringAsync (new Uri ("http://some-server.com/file.xml"));
WebClient
也具有 DownloadFileCompleted
和 DownloadFileAsync
,可用於擷取二進位數據。
HttpWebRequest
HttpWebRequest
提供比 WebClient
和 更多的自定義,因此需要更多程序代碼才能使用。
簡單同步 HttpWebRequest
作業的程式代碼如下:
var request = HttpWebRequest.Create(@"http://some-server.com/file.xml ");
request.ContentType = "text/xml";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
Console.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
var content = reader.ReadToEnd();
// do something with downloaded string, do UI interaction on main thread
}
}
Web 服務檔中有一個範例。
連線性
行動裝置在各種網路條件下運作,從快速Wi-Fi或4G連線到接收區域不佳,以及EDGE資料連結變慢。 因此,最好先偵測網路是否可用,如果是的話,在嘗試連線到遠端伺服器之前,可以使用哪種類型的網路。
行動應用程式在這些情況下可能採取的動作包括:
- 如果網路無法使用,請通知使用者。 如果他們已手動停用它(例如飛機模式或關閉Wi-Fi,然後他們可以解決問題。
- 如果連線為 3G,應用程式的行為可能會不同(例如,Apple 不允許超過 20Mb 的應用程式透過 3G 下載)。 應用程式可以使用這項資訊,在擷取大型檔案時警告使用者過度下載時間。
- 即使網路可供使用,在起始其他要求之前,最好先確認與目標伺服器的連線。 這可防止應用程式的網路作業重複逾時,也允許向用戶顯示更具資訊性的錯誤訊息。
WebServices
請參閱使用 Web 服務的檔,其中涵蓋使用 Xamarin.iOS 存取 REST、SOAP 和 WCF 端點。 手動製作 Web 服務要求並剖析回應是可行的,不過有連結庫可供簡化,包括 Azure、RestSharp 和 ServiceStack。 即使在 Xamarin 應用程式中也可以存取基本的 WCF 作業。
Azure
Microsoft Azure 是雲端平臺,可為行動應用程式提供各種服務,包括數據記憶體和同步處理,以及推播通知。
請 造訪 azure.microsoft.com 免費試用。
RestSharp
RestSharp 是一個 .NET 連結庫,可以包含在行動應用程式中,以提供可簡化 Web 服務的存取權的 REST 用戶端。 它可藉由提供簡單的 API 來要求數據並剖析 REST 回應來協助。 RestSharp 可能很有用
RestSharp 網站包含如何使用 RestSharp 實作 REST 用戶端的檔。 RestSharp 在 github 上提供 Xamarin.iOS 和 Xamarin.Android 範例。
我們的 Web 服務檔中也有 Xamarin.iOS 代碼段。
ServiceStack
不同於 RestSharp,ServiceStack 既是裝載 Web 服務的伺服器端解決方案,也是可在行動應用程式中實作以存取這些服務的用戶端連結庫。
ServiceStack 網站說明專案的目的,以及文件和程式代碼範例的連結。 這些範例包括 Web 服務的完整伺服器端實作,以及可存取 Web 服務的各種用戶端應用程式。
WCF
Xamarin 工具可協助您取用一些 Windows Communication Foundation (WCF) 服務。 一般而言,Xamarin 支援與 Silverlight 運行時間隨附的相同 WCF 用戶端子集。 這包括 WCF 最常見的編碼和通訊協定實作:使用 BasicHttpBinding
透過 HTTP 傳輸通訊協定的文字編碼 SOAP 訊息。
由於 WCF 架構的大小和複雜度,可能會有目前和未來的服務實作會落在 Xamarin 用戶端子集網域所支援的範圍之外。 此外,WCF 支援只需要在 Windows 環境中使用工具,才能產生 Proxy。
執行緒處理
應用程式回應性對於行動應用程式而言很重要 – 使用者預期應用程式會快速載入並執行。 停止接受使用者輸入的「凍結」畫面似乎表示應用程式已當機,因此請務必不要將 UI 線程與長時間執行的封鎖呼叫系結在一起,例如網路要求或緩慢的本機作業(例如解壓縮檔案)。 特別是啟動程式不應該包含長時間執行的工作 – 所有行動平臺都會終止應用程式,而應用程式載入的時間太長。
這表示您的使用者介面應該實作「進度指標」或「可使用」UI,以快速顯示,以及執行背景作業的異步工作。 執行背景工作需要使用線程,這表示背景工作需要一種方式來向主要線程進行通訊,以指出進度或完成時間。
平行工作連結庫
使用平行工作連結庫建立的工作可以異步執行,並在其呼叫線程上傳回,使其非常適用於觸發長時間執行的作業,而不會封鎖使用者介面。
簡單的平行工作作業可能如下所示:
using System.Threading.Tasks;
void MainThreadMethod ()
{
Task.Factory.StartNew (() => wc.DownloadString ("http://...")).ContinueWith (
t => label.Text = t.Result, TaskScheduler.FromCurrentSynchronizationContext()
);
}
索引鍵會 TaskScheduler.FromCurrentSynchronizationContext()
重複使用呼叫 方法的 Thread SynchronizationContext.Current,也就是執行中的主要線程 MainThreadMethod
,以封送處理該線程的呼叫。 這表示如果在UI線程上呼叫方法,它會 ContinueWith
在UI線程上執行作業。
如果程式代碼從其他線程啟動工作,請使用下列模式來建立UI線程的參考,而工作仍然可以回呼它:
static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();
在UI線程上叫用
對於未使用平行工作連結庫的程式代碼,每個平臺都有自己的語法,可將作業封送處理回UI線程:
- iOS –
owner.BeginInvokeOnMainThread(new NSAction(action))
- Android –
owner.RunOnUiThread(action)
- Xamarin.Forms –
Device.BeginInvokeOnMainThread(action)
- Windows –
Deployment.Current.Dispatcher.BeginInvoke(action)
iOS 和 Android 語法都需要有『context』 類別才能使用,這表示程式代碼需要將此對象傳遞至任何需要 UI 線程上回呼的方法。
若要在共用程式代碼中呼叫 UI 線程,請遵循 IDispatchOnUIThread 範例(@follesoe)。 將 和 程式宣告為 IDispatchOnUIThread
共用程式代碼中的介面,然後實作平臺特定類別,如下所示:
// program to the interface in shared code
public interface IDispatchOnUIThread {
void Invoke (Action action);
}
// iOS
public class DispatchAdapter : IDispatchOnUIThread {
public readonly NSObject owner;
public DispatchAdapter (NSObject owner) {
this.owner = owner;
}
public void Invoke (Action action) {
owner.BeginInvokeOnMainThread(new NSAction(action));
}
}
// Android
public class DispatchAdapter : IDispatchOnUIThread {
public readonly Activity owner;
public DispatchAdapter (Activity owner) {
this.owner = owner;
}
public void Invoke (Action action) {
owner.RunOnUiThread(action);
}
}
// WP7
public class DispatchAdapter : IDispatchOnUIThread {
public void Invoke (Action action) {
Deployment.Current.Dispatcher.BeginInvoke(action);
}
}
Xamarin.Forms 開發人員應該 Device.BeginInvokeOnMainThread
在一般程式代碼中使用 (共用專案或 PCL)。
平臺和裝置功能和效能降低
平臺功能檔中會提供處理不同功能的進一步特定範例。 它會處理偵測不同的功能,以及如何正常降級應用程式,以提供良好的用戶體驗,即使應用程式無法運作其完整潛力也一樣。