XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2017/09/22

剖析 .NET Framework / .NET Core / Xamarin / PCL / .NET Standard 類別庫的演進歷程

現在的 .NET 開發環境已經是百花齊放,除了可以在 Windows 平台下開發出可在 .NET Framework 的應用程式,還可以在 Linux、macOS、UWP、Android、iOS 等不同作業系統下,使用 .NET 技術進行應用程式的開發。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

 不過,若要能夠讓我們開發出來的類別可以在不同專案或者不同平台下能夠做到共用與分享,該使用甚麼樣的方法呢?在本篇文章中,我們將會進行不同角度的探討與測試。

只有 Windows 作業系統環境下

在以往的 .NET 生態系統中,若想要開發出 .NET 應用程式,此時,就只能選擇在 Windows 作業系統下來執行這個應用程式,並且,所使用的 .NET 運行生態,也就是我們常在使用的 .NET Framework 透過 .NET Framework 的開發環境,我們可以使用 BCL (基底類別庫 Base Class Library) / FCL (框架類別庫 Framework Class Library) 所提供豐富的類別,進行開發出 Windows Forms / WPF / ASP.NET Web Forms / ASP.NET MVC 這樣不同類型的應用程式。
當然,這段時期,也有使用 .NET Framework 子集合環境所創造出來的,如:Silverlight、Windows Phone 7.x、.NET Compact等等,不過,由於這些環境已經不再支援,所以,我們將不會進行討論

在專案之間共用類別庫

在 .NET Framework 開發環境下,我們可以設計一個屬於自己環境使用的類別庫 Class Library (這裡會加入我們自行開發的不同類別、介面、委派等型別與不同商業邏輯 API) 專案,經過建置之後,就會成為一個類別庫組件 Class Library Assembly;之後,我們可以讓不同的 .NET Framework 專案,將這個類別庫專案的參考,加入到正在開發的專案內,如此,便可以共享與共用這個類別庫裡面所設計出來的各種商業邏輯與類別。
由於我們透過類別庫進行設計出自己或者公司需要用到的各種不同類別與功能和計算能力,全部整合在一個或者多個組件 Assembly 內,一旦這些商業邏輯或者類別有修正的時候,我們就僅僅需要維護一套類別庫專案原始碼即可,就可以將修正後的類別庫組件讓其他專案來更新與使用。這就是 .NET 類別庫的主要目的,可以讓您共享自行開發的 .NET 類別於不同專案上來使用。

.NET 可以跨平台了 Windows / Linux / macOS / Android / iOS / UWP

現在,在 .NET 生態系統中,除了可以在 Windows 作業系統下執行,也可以在 Linux / macOS / Android / iOS / UWP 系統下開發出可以執行 .NET 應用程式。因此,在 .NET 生態系統中,除了以往的 .NET Framework 之外,另外增加了 .NET Core 與 Xamarin 這兩個開發生態環境。
在這三個 .NET 開發生態環境中,.NET Framework / .NET Core / Xamarin,他們都各自有屬於自己定義出來的 BCL (基底類別庫 Base Class Library) 與通用語言執行階段 CLR (Common Language Runtime) ,最重要的是,這些 BCL 並不是在每個平台下都有完整的設計與實作出來(最完整的 .NET 基底類別庫,則是完全實作在 .NET Framework生態環境中)。
現在,我們遇到一個問題,當我們需要設計出屬於自己會重複使用到的類別庫,要共享這些程式碼到不同平台、不同專案下,該如何進行設計呢?

在每個 .NET 平台建立屬於該平台下的類別庫

我們可以在這三個 .NET 平台 (.NET Framework, .NET Core, Mono Xamarin)中,分別建立出這三個平台會用到的類別庫專案;如此,每個.NET平台下的不同專案,就可以參考該平台所建立的類別庫專案,這樣,就可以彼此互相共享類別與商業邏輯設計了。
使用這樣解決方案,你需要維護可以分別在 .NET Framework / .NET Core / Mono Xamarin 平台下的三套類別庫原始碼。
在這篇文章,將會透過 Visual Studio 2017 所建立一個方案,進行相關問題測試;在這個方案內,我們建立了許多類別庫與各平台的可執行專案,如下圖所示。
.NET Standard
在這個測試方案中,我們分別建立了
  • .NET Framework 類別庫專案
  • .NET Core 類別庫專案
  • Android 類別庫專案
  • PCL 可攜式專案 (這裡建立的是 Profile259 的可攜式專案類別庫)
  • .NET 標準類別庫專案 ( .NET Standard Class Library)
想要建立這些不同平台的類別庫,可以在 Visual Studio 2017 內,增加一個專案項目,當 新增專案 對話窗出現之後,你可以在右上方的搜尋條件文字輸入盒內,輸入 類別庫 C# 關鍵字,此時,對話窗終將會列出可以新建的類別庫專案類型,如下圖所示。
Visual Studio 新增項目
 你也許會想到說,幹嘛這麼麻煩,這很簡單,我們只需要建立一個 .NET Framework 的類別庫,並且將它的參考加入到 .NET Core / Xamarin 專案內,這樣,我們就可以透過 .NET Framework 所創建出來的類別庫,達到共享程式碼的目的了,而且我們也只需要維護一套類別庫原始碼。
所以,有時候,現實是殘酷的,這樣的想法與做法,還是無法解決我們跨平台專案共享程式碼的需求,在底下的文章內容,你將會看到這樣開發過程所產生的問題。

建立每個平台下的可執行專案

為了要能夠讓我們可以在不同的 .NET 平台下可以執行這些平台的應用程式,所以,我們接著透過 Visual Studio 2017 建立起不同平台的可執行專案:
  • NETFrameworkConsoleApp
    這是可以在 Windows 作業系統下的主控台應用程式
  • NETCoreConsoleApp
    這是可以在 Windows / Linux / macOS 作業系統下的主控台應用程式
  • AndroidApp
    這是可以在 Android 裝置下執行的行動應用程式 (Mobile App)
要建立上述不同 .NET 平台下的可執行專案,請在如下圖的 新增專案 對話窗中,選擇適當的專案類型即可。
.NET Framework / .NET Core / Android

設計 .NET Framework 類別庫,使其在 .NET Framework 下共用

在這裡,我們在 .NET Framework 的 NETFrameworkClassLibrary 類別庫專案內,設計一個類別,如下所示:
這個類別庫 (Class Library)專案, FrameworkClass, 設計的相當簡單,他只有一個屬性成員,並且有建立這個屬性的物件。這裡用到的類別 Bitmap 將會是由 .NET Framework 中的基底類別庫 (BCL) 中所提供的。
    public class FrameworkClass
    {
        public Bitmap MyProperty { get; set; } = new Bitmap(100, 100);
    }
接著,我們需要在 NETFrameworkConsoleApp 可執行專案下,加入 .NET Framework 類別庫的專案 NETFrameworkClassLibrary,如此,我們才可以在這個可執行專案內,使用這個 .NET Framework 類別庫內的自行開發設計的型別。
請在 Visual Studio 2017 內,在 NETFrameworkConsoleApp 專案加入參考
請參考下圖,加入 NETFrameworkClassLibrary 類別庫專案到這個可執行專案內
加入參考
由於,這個類別庫會參考用到了 BCL 的 System.Drawing 這個組件,所以,請在同樣的 參考管理員 對話窗內,點選 組件 頁次,接著勾選 System.Drawing 項目。
加入參考
好的,現在我們可以到 NETFrameworkConsoleApp 專案內,使用 NETFrameworkClassLibrary 類別庫內的 FrameworkClass 類別了。
我們在可執行專案內的 Program.cs檔案內之方法 Main 內,撰寫了底下程式碼,並且請您執行該專案;此時,您將不會得到任何例外異常錯誤,這個專案可以正常執行。
    class Program
    {
        static void Main(string[] args)
        {
            var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
            var barObject = fooObject.MyProperty;
            Console.WriteLine("Hello World!");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }

共享 .NET Framework 類別庫給 .NET Core 的可執行專案

類別庫存在的目的,就是要能夠重複使用(避免重複設計,這樣也會避免因為重複的程式碼,造成更多出錯的機會)這些類別,因此,我們來將 .NET Framework 類別庫加入到 .NET Core 的專案內。
我們將 NETCoreConsoleApp 專案內,打開 參考管理員 - NETCoreConsoleApp 對話窗,找到 專案 頁次,勾選 NETFrameworkClassLibrary 項目,將這個專案參考加入到 NETCoreConsoleApp 專案內。
另外,所們在這裡 參考管理員 - NETCoreConsoleApp 似乎沒有看到如同 .NET Framework 平台下的 組件 頁次,可以讓我們選擇 System.Drawing 這個組件
Class Library 類別庫
在您加入完這類別庫參考之後,請重新開啟 參考管理員 - NETCoreConsoleApp,很不幸的,所們在這裡 參考管理員 - NETCoreConsoleApp 似乎沒有看到如同 .NET Framework 平台下的 組件 頁次,可以讓我們選擇 System.Drawing 這個組件。
在 .NET Core 中,現在還沒有支援這個 BCL 類別 Bitmap,所以,就算你可以正常把 .NET Framework 類別庫加入到 .NET Core 的可執行專案內,在 .NET Core 的專案內,還是無法正常建置與執行。
我們在 NETCoreConsoleApp 撰寫同樣的程式碼
    class Program
    {
        static void Main(string[] args)
        {
            var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
            var barObject = fooObject.MyProperty;
            Console.WriteLine("Hello World!");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }
您會看到底下的錯誤訊息
錯誤    CS0012    類型 'Bitmap' 定義在未參考的組件中。您必須加入組件 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 的參考。    NETCoreConsoleApp    D:\Temp\NETClassLibrary\NETCoreConsoleApp\Program.cs    12    使用中
不過,若我們將 var barObject = fooObject.MyProperty; 這行程式碼註解起來,您將會發現到,這個 .NET Core 專案竟然可以建置成功了。
不過,不要高興得太早,我們將這個 .NET Core 可執行專案設定為預設起始專案,並且執行這個專案,您將會得到底下的執行時期錯誤訊息。
發生 System.IO.FileNotFoundException
  HResult=0x80070002
  Message=Could not load file or assembly 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. 系統找不到指定的檔案。
  Source=<無法評估例外狀況來源>
  StackTrace: 
   at NETFrameworkClassLibrary.FrameworkClass..ctor() in D:\Temp\NETClassLibrary\NETFrameworkClassLibrary\FrameworkClass.cs:line 12
   at NETCoreConsoleApp.Program.Main(String[] args) in D:\Vulcan\GitHub\CSharpOOP\NETClassLibrary\NETCoreConsoleApp\Program.cs:line 11
FileNotFoundException
    class Program
    {
        static void Main(string[] args)
        {
            var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
            //var barObject = fooObject.MyProperty;
            Console.WriteLine("Hello World!");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }
 所以,若我們想要設計出一個可以用於不同 .NET 平台都可以執行的類別庫專案,該如何解決此一問題呢?

想要在 .NET 跨平台開發環境下,建立各平台都可以用的類別庫

從剛剛的過程中,我們發現到,我們可以在各個平台 .NET Framework / .NET Core / Xamarin 下,建立自己平台的類別庫,這些類別庫,是可以在自己的平台下重複使用(只要加入這個類別庫到自己的專案內即可);可是,我們卻無法跨平台的將 A 平台的類別庫,加入到 B 平台專案內,例如上面個過程,將 .NET Framework 建立的類別庫,加入到 .NET Core 專案內來使用。
對於這樣跨平台的類別庫需求,.NET 生態系統提供了兩種選擇 可攜式類別庫 (PCL Portable Class Library與 .NET 標準類別庫 (.NET Standard Class Library)

第一代的跨平台類別庫 : PCL 可攜式類別庫

PCL 可攜式類別庫是早期用於 .NET 跨平台類別庫的共享技術,在您建立一個 PCL 可攜式類別庫 的時候,您需要指定這個 PCL 可攜式類別庫需要與那些平台共享,一旦決定可以共享的平台之後,在這個 PCL 可攜式類別庫 內,可以使用的類別,就是這些平台都有提供的類別與成員和方法。
PCL 可攜式類別庫
我們在這裡建立一個 Profile259 的 PCL 可攜式類別庫,可是,當我們要建立一個類別,裡面會有個 HttpClient 成員。
    public class PCL259Class
    {
        HttpClient client;
    }
您會看到底下錯誤訊息,竟然無法使用 HttpClient 這個類別,那麼,我們如同 .NET Framework 平台下,將這個 HttpClient 類別的組件加入進來,不就解決掉這個問題了嗎?不幸的是,當您打開了 參考管理員 - PCL259ClassLibrary 對話窗,切換到 組件 頁次,且是得到此訊息 所有 Framework 組件都已經被參考了,請使用物件瀏覽器瀏覽 Framework 中的參考。這是因為在 PCL 可攜式類別庫 內,採用的技術是將要支援的不同平台的已經存在的類別庫,以交集做運算,僅提供每個平台都有提供的類別,才能夠在 PCL 可攜式類別庫 內使用。
錯誤    CS0246    找不到類型或命名空間名稱 'HttpClient' (是否遺漏了 using 指示詞或組件參考?)    PCL259ClassLibrary    D:\Temp\NETClassLibrary\PCL259ClassLibrary\PCL259Class.cs    11    使用中

下一代的跨平台類別庫 : .NET 標準類別庫 (.NET Standard Class Library)

這是微軟針對 PCL 可攜式類別庫 所面臨到的困境,設計出來的跨平台類別庫設計方法。
他與 PCL 可攜式類別庫的不同在於:
  • PCL 可攜式類別庫
    是針對現有平台可以支援的 BCL 所有類別,與要支援平台,定義出一個 Profile#,例如,上面的例子中,我們建立一個 Profile259 的PCL 可攜式類別庫;在這個 Profile259 的 PCL 可攜式類別庫可以使用的類別,將會取其各平台 BCL 都有的類別,取其交集,這樣,就可以在 PCL 可攜式類別庫 使用這些類別。
  • .NET 標準類別庫 ( .NET Standard )
    .NET 標準類別庫的作法卻與PCL 可攜式類別庫完全不同。.NET 標準類別庫是一個規格,在.NET 標準類別庫訂出的一個新版本規格之後,所有平台 BCL 都需要能夠支援這個 .NET 標準類別庫的新標準,因此,只要這個新版本的.NET 標準類別庫有列出的類別,您就可以在 .NET 標準類別庫 內使用這些類別與型別。
    如此,當您設計好一個屬於自己的 .NET 標準類別庫後,就可以在不同平台下的專案,把這個 .NET 標準類別庫直接加入其參考,並且可以正常與順利使用與執行這些共用類別與相關 API。

建立一個各平台都可以用的類別庫

我們在這裡建立一個.NET 標準類別庫,NETStandardClassLibrary,並且將這個專案參考,加入到各平台專案內;若要建立一個.NET 標準類別庫,請在新增專案的時候,選擇如下圖的 .NET Standard 標籤頁次,接著選取 類別庫 (.NET Standard) 項目即可。
.NET Standard 標準類別庫
接著,我們在這個 NETStandardClassLibrary 專案內,設計底下 StandardClass,它會抓取微軟網站的內容,並且回傳這個網站內容字串。
      public static class StandardClass
    {
        public static async Task<string> GetContent()
        {
            HttpClient client = new HttpClient();
            var result = await client.GetStringAsync("http://www.microsoft.com");
            return result;
        }
    }
接著,我們分別在 .NET Framework / .NET Core 平台下的專案,使用底下程式碼來進行測試,結果是可以正常運作的。
        static void Main(string[] args)
        {
            var content = NETStandardClassLibrary.StandardClass.GetContent().Result;
            Console.WriteLine($"Web Content Length : {content.Length}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }

結論

若您想要開發出可以用於跨 .NET 不同平台專案下,可以共用的類別庫,請選擇建立與使用 .NET 標準類別庫。

2017/07/13

開啟舊的專案並且重新建置,得到錯誤訊息:java.lang.IllegalArgumentException: already added

今天,將一個月前的專案開啟,並且進行重新建置,想要看看執行結果,不過,卻得到底下的錯誤訊息:
java.lang.IllegalArgumentException: already added : Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat;
我非常確定在最後一次開啟那個專案的時候,確實是可以建置、除錯與執行的。
我現在的 Visual Studio 2017 已經更新到最新版本,而底下是我的 Visual Studio 2017 的版本資訊:
  • Visual Studio Enterprise 2017 版本 15.2 (26430.15)
  • Xamarin 4.5.0.486
  • Xamarin.Android SDK 7.3.1.2
  • Xamarin.ios and Xamarion.Mac SDK 10.10.0.37

如何解決一問題

我嘗試了許多不同方法,試圖來解決此一問題,不過,最終都失敗收場;最後,我解決的如下:
我這裡的解決方法
  • 在方案總管中,滑鼠右擊方案,選擇 管理方案的 NuGet 套件
  • 選擇 已安裝 標籤頁次,在文字搜尋輸入盒中,輸入 Xamarin.Android.Support.v4 查詢出這個套件。
  • 勾選右半部的 Android 專案
  • 選擇最新的版本,在這個時間點,最新的版本將會是 25.3.1
  • 點選安裝按鈕,進行升級到最新套件。
  • 當相關 NuGet 套件都升級完成之後,關閉 Visual Studio
  • 將該方案所在目錄下的 Packages 目錄刪除
  • 重新使用 Visual Studio 2017 開啟這個方案
  • 在 Visual Studio 的方案總管視窗內,將核心 PCL 專案與 Android 專案內的 bin & obj 目錄刪除
  • 分別重新建置核心 PCL 專案與 Android 專案
  • 應該就可以正常建置成功了

2017/07/05

從網路下載 APK 檔案,並且於 Xamarin.Forms 專案內進行升級動作

在這份筆記中,將會進行如何透過 Xamarin.Forms 的 App,從網路上下載最新的 APK 檔案到手機上,接著,進行這個 APK 檔案的安裝與升級動作。
在這樣的需求中,我們需要解決與處理底下這些需求:
  • 如何從網路上,透過指定的 URL ,取得最新的 APK 檔案。
  • 當取得 APK 檔案之後,要能夠儲存到手機記憶卡中。
  • 最後,要能夠執行這個 APK 檔案,便可以進行 App 的升級。

如何從網路上,透過指定的 URL ,取得最新的 APK 檔案

這當然需要使用 .NET 的 HttpClient 類別所產生出來的物件,使用這個物件的 GetStreamAsync 方法,便可以透過非同步的方式,取得遠端 Web Server 上的這個 APK 檔案。
由於這個方法 GetStreamAsync 會回傳一個 Stream 物件,我們便可以將這個物件,傳遞到原生 Android 專案內,就可以使用原生 Android SDK API,將這些檔案內容,寫入到 Android 專案內。
            DownloadCommand = new DelegateCommand(async () =>
            {
                HttpClientHandler handle = new HttpClientHandler();
                HttpClient client = new HttpClient(handle);
                Title = "正在下載中";
                using (var fooStream = await client.GetStreamAsync("https://github.com/vulcanlee/test/raw/master/com.vulcanlab.task.apk"))
                {
                    _APK.GenApkFile(fooStream);
                }

                Title = "已經下載完成";
            });

取得 APK 檔案之後,要能夠儲存到手機記憶卡中

由於 PCLStorage 僅能夠提供讀寫 App 的沙箱儲存體內,寫入到這些沙箱內的檔案,任何人與相關程式,是無法存取這個檔案的;因此,我們需要把 APK 檔案寫入到一個公開的目錄下,在這裡,我們將會寫入到 下載 資料夾內。
要做到這樣的工作,我們需要透過相依性服務注入功能,將 Android 原生平台下實作的介面物件,注入到 核心PCL 專案內來使用。
我們需要先在核心PCL專案內,建立一個介面:
在這個介面內,宣告了兩個方法需要在原生 Android 平台下來實作,一個是將剛剛從網路上取 APK 檔案的 Stream 物件內容,寫入到 Android 裝置記憶卡內;另外一個是將剛剛下載下來的檔案,進行安裝。
    public interface IAPK
    {
        void InstallAPK();

        void GenApkFile(Stream downloadStream);
    }
IAPK 介面在 Android 平台下實作的類別如下所示:
  • GenApkFile
    這裡使用了 Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).ToString(); 敘述,取得了手機中下載資料夾路徑,接著判斷該檔案是否存在,若不存在,則會產生出這個檔案,若已經存在,則會直接開啟,等候寫入這個檔案;最後,使用 CopyToAsnyc 方法,針對兩個 Stream 來複製,完成檔案寫入的需求。
[assembly: Xamarin.Forms.Dependency(typeof(SelfInstAPK.Droid.Infrastructures.APK_Droid))]
namespace SelfInstAPK.Droid.Infrastructures
{
    public class APK_Droid : IAPK
    {
        public string DownlaodPath = "";
        public string Filename = "new.apk";
        public string FullFilename = "";
        FileStream fooStream;

        public void GenApkFile(Stream downloadStream)
        {
            DownlaodPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).ToString();
            FullFilename = Path.Combine(DownlaodPath, Filename);

            if (File.Exists(FullFilename) == false)
            {
                Directory.CreateDirectory(DownlaodPath);
                fooStream= File.Create(FullFilename);
            }
            else
            {
                fooStream= File.OpenWrite(FullFilename);
            }


            using (FileStream fs = fooStream)
            {
                downloadStream.CopyTo(fs);
            }
        }

        public void InstallAPK()
        {
            Intent setupIntent = new Intent(Intent.ActionView);
            setupIntent.SetDataAndType(Android.Net.Uri.FromFile(new Java.IO.File(FullFilename)), "application/vnd.android.package-archive");
            setupIntent.AddFlags(ActivityFlags.NewTask);

            var context = Android.App.Application.Context;
            context.StartActivity(setupIntent);
        }
    }
}

最後,要能夠執行這個 APK 檔案,便可以進行 App 的升級

這個實作介面的另外一個方法就是,InstallAPK,我們建立一個 Intent 物件,設定好相關參數,最後,使用 StartActivity 啟動安裝這個 APK 需求。

使用 Prism 自動注入這個介面實作物件

我們不需要使用 Xamarin.Forms 內建的 DependencyService 提供的靜態方法來注入實作物件,我們僅需要在 ViewModel 內的建構式,填入這個介面參數,Prism 便會自動幫您注入實作好的物件,這樣,您就可以在 PCL 專案內,將資料寫到 Android 的目錄下。
        IAPK _APK;

        public MainPageViewModel(IAPK apk)
        {
            _APK = apk;
            ...
        }

其他說明

若您沒有將 APK 檔案寫入到空開可以存取的目錄下,則當進行安裝 APK 檔案的時候,會出現如下圖錯誤訊息畫面。
Parse Error

There was a problem parsing the package.
而正常的情況下,會出現底下的畫面,讓您可以安裝這個 APK 檔案。