XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書
顯示具有 Xamarin.Android 標籤的文章。 顯示所有文章
顯示具有 Xamarin.Android 標籤的文章。 顯示所有文章

2019/06/05

Xamarin.Forms 的背景執行緒在 Android / iOS 背景模式下的執行情境測試

Xamarin.Forms 的背景執行緒在 Android / iOS 背景模式下的執行情境測試

對於已經具備擁有 .NET / C# 開發技能的開發者,可以使用 Xamarin.Forms Toolkit 開發工具,便可以立即開發出可以在 Android / iOS 平台上執行的 App;對於要學習如何使用 Xamarin.Forms & XAML 技能,現在已經推出兩本電子書來幫助大家學這這個開發技術。
這兩本電子書內包含了豐富的逐步開發教學內容與相關觀念、各種練習範例,歡迎各位購買。
Xamarin.Forms 電子書
想要購買 Xamarin.Forms 快速上手 電子書,請點選 這裡
想要購買 XAML in Xamarin.Forms 基礎篇 電子書,請點選 這裡


當在進行 Xamarin.Forms 專案開發的時候,必須要能夠了解 Android 與 iOS 應用程式生命週期 Application Life Cycle 的特性,最為重要的是,這兩個平台上對於生命週期的運作方式是不太相同的。原則上,所有的行動裝置應用程式都會分成前景、背景兩種模式,所謂的前景 Foreground 模式,就是該應用程式顯示在螢幕上,而背景 Background 模式,就是這個應用程式無法顯示在螢幕上,因為現在螢幕需要顯示其他應用程式的內容,關於這部分的詳細介紹,可以參考 Android 活動開發週期 與 iOS 中的背景處理簡介 這兩份文件。
當應用程式一起動的時候,此時這個應用程式將要顯示到螢幕上,就會觸發特定的事件,讓應用程式知道現在應用程式的已經進入到前景模式;而例如,當使用者按下手機上的 Home 按鍵,此時,這個應用程式就會切換到背景模式,當然,也會觸發特定的事件。
如同前面所說的,在 Android 與 iOS 系統下,會觸發的事件與可以觸發的事件項目都不相同,底下的圖片為 Android 作業系統下的 Activity 的生命週期狀態;當 Activity 建立後,就會觸發 OnCreate 事件,啟動之後,就會觸發 OnStart 事件;當應用程式按下了 Home 按鍵,就會觸發 OnPause的事件,使用者選擇切換到該應用程式,要讓該應用程式重新顯示到螢幕上,此時,將會觸發 OnRestart 與 OnStart 事件。
若現在的作業系統為 iOS ,此時對於應用程式生命週期相關會使用到的事件,將會如下圖。當應用程式啟動之後,將會觸發 OnActivated 事件,此時的狀態名稱為 Running 或者 Active;若使用者按下了 Home 按鍵,將會觸發 OnResignActivation 事件,此時,可以稱進入到 Inactive 狀態下,緊接著會在觸發 DidEnterBackground 事件,進入到 Background / Suspended 模式下;現在若使用者選擇要把這個應用程式讓他回到螢幕上,這個時候,就會觸發了 WillEnterForeground 事件,如下面流程圖。
然而,在 Xamarin.Forms 中,也會有一個應用程式生命週期的運作模式與特定的事件,只不過在 Xamarin.Forms App 生命週期內就簡單多了,在 Xamarin.Forms 內只有三種生命週期事件
  • OnStart - 會在應用程式啟動時呼叫。
  • OnSleep - 會在每次應用程式被移到背景時呼叫。
  • OnResume - 會在應用程式被傳送到背景後又再次繼續時呼叫。
這些事件可以從 Xamarin.Forms 專案內的 App 類別中來訂閱。

測試用的專案範例解說

這裡將會使用底下的 Xamarin.Forms 專案,進行 Android / iOS 兩個平台的不同生命週期狀態下的程式碼執行狀態來了解,在這裡,將會設計一個按鈕,當按下這個按鈕之後,將會執行 60 次的迴圈,每次迴圈將會休息兩秒鐘,並且將計數器變數加一,而該計數器屬性將會透過資料綁定的方式,將這個計數器值顯示到螢幕上。此時,也會使用 Console.WriteLine 方法,將現在迴圈的 Index 值顯示到螢幕上,所以,可以從 Visual Studio 2019 的輸出視窗中看到這個程式是否還有繼續在執行中;另外,這些相關執行日誌內容,也會寫到檔案中,以便當這個程式在實體手機上,不透過 Visual Studio 來執行,也可以看到這些 Log 執行過程內容。
這裡是這個應用程式的 XAML 設定內容。
xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XF5007.Views.MainPage"
             Title="背景執行緒與背景模式">

    <ScrollView
        Orientation="Both"
        >
        <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
            <Label Text="Welcome to Xamarin Forms and Prism!" />
            <Label Text="{Binding AppLifeStatusRecord}"
               FontSize="{OnPlatform 14, iOS=14}">
            </Label>

            <Label
            Text="{Binding Counter}"
            FontSize="30"
            TextColor="Red"/>
            <Button
            Text="開始定時執行"
            Command="{Binding StartCommand}"/>
            <StackLayout
            Orientation="Horizontal"
            >
                <Button Text="Read" Command="{Binding ReadCommand}"/>
                <Button Text="Reset" Command="{Binding ResetCommand}"/>
            </StackLayout>
        </StackLayout>
    </ScrollView>

</ContentPage>
這裡是這個頁面的 ViewModel,定義了三個按鈕的命令行為,在這裡將會透過 AppLifeStatusRecord 類別內的 ReadAsync / WriteAsync 這兩個方法,進行日誌的檔案讀寫需求。
public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
{
    public event PropertyChangedEventHandler PropertyChanged;
    public DelegateCommand StartCommand { get; set; }
    public DelegateCommand ReadCommand { get; set; }
    public DelegateCommand ResetCommand { get; set; }
    public int Counter { get; set; }
    public string AppLifeStatusRecord { get; set; }
    private readonly INavigationService navigationService;

    public MainPageViewModel(INavigationService navigationService)
    {
        this.navigationService = navigationService;
        ReadCommand = new DelegateCommand(async () =>
        {
            AppLifeStatusRecord = await new AppLifeStatusService().ReadAsync();
        });
        ResetCommand = new DelegateCommand(async () =>
        {
            await new AppLifeStatusService().WriteAsync("", true);
            AppLifeStatusRecord = "";
        });
        StartCommand = new DelegateCommand(async () =>
        {
            for (int i = 0; i < 60; i++)
            {
                await Task.Delay(2000);
                Counter++;
                Console.WriteLine($"   === {i} ===");
                await new AppLifeStatusService().WriteAsync($"     Xamarin.Forms= {i} = > Timer - {DateTime.Now.Minute}:{DateTime.Now.Second}");
            }
        });
    }

    public void OnNavigatedFrom(INavigationParameters parameters)
    {
    }

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }

    public void OnNavigatingTo(INavigationParameters parameters)
    {
    }
}

如何訂用 Xamarin.Forms 的應用程式生命週期的相關事件

請在 Xamarin.Forms 專案中,找到 App.xaml.cs 節點,從這個節點內的 App 類別中,加入底下三個覆寫方法,所以,當應用程式在前景與背景模式下切換的時候,就會觸發這些事件。
protected override async void OnStart()
{
    IsAppInForeground = true;
    new AppLifeStatusService().WriteAsync($"Xamarin.Forms>OnStart - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
protected override async void OnSleep()
{
    IsAppInForeground = false;
    new AppLifeStatusService().WriteAsync($"Xamarin.Forms>OnSleep - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
protected override async void OnResume()
{
    IsAppInForeground = true;
    new AppLifeStatusService().WriteAsync($"Xamarin.Forms>OnResume - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}

如何訂用 Xamarin.Android 的應用程式生命週期的相關事件

請在 Xamarin.Android 專案中,找到 MainActivity.cs 節點,從這個節點內的 MainActivity 類別中,加入底下覆寫方法,所以,當一個 Android 平台下的應用程式在前景與背景模式下切換的時候,就會觸發這些事件。
protected override void OnStart()
{
    base.OnStart();
     new AppLifeStatusService().WriteAsync($"     Android>OnStart - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
protected override void OnResume()
{
    base.OnResume();
     new AppLifeStatusService().WriteAsync($"     Android>OnResume - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}"); 
}
protected override void OnPause()
{
    base.OnPause();
     new AppLifeStatusService().WriteAsync($"     Android>OnPause - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}"); 
}
protected override void OnStop()
{
    base.OnStop();
     new AppLifeStatusService().WriteAsync($"     Android>OnStop - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
protected override void OnRestart()
{
    base.OnRestart();
     new AppLifeStatusService().WriteAsync($"     Android>OnRestart - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
protected override void OnDestroy()
{
    base.OnDestroy();
     new AppLifeStatusService().WriteAsync($"     Android>OnDestroy - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}

如何訂用 Xamarin.iOS 的應用程式生命週期的相關事件

請在 Xamarin.iOS 專案中,找到 AppDelegate.cs 節點,從這個節點內的 AppDelegate 類別中,加入底下覆寫方法,所以,當一個 iOS 平台下的應用程式在前景與背景模式下切換的時候,就會觸發這些事件。
public override void OnActivated(UIApplication application)
{
    base.OnActivated(application);
    new AppLifeStatusService().WriteAsync($"     iOS>OnActivated - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
public override void WillEnterForeground(UIApplication application)
{
    base.WillEnterForeground(application);
    new AppLifeStatusService().WriteAsync($"     iOS>WillEnterForeground - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
public override void OnResignActivation(UIApplication application)
{
    base.OnResignActivation(application);
    new AppLifeStatusService().WriteAsync($"     iOS>OnResignActivation - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
public override void DidEnterBackground(UIApplication application)
{
    base.DidEnterBackground(application);
    new AppLifeStatusService().WriteAsync($"     iOS>DidEnterBackground - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}
// not guaranteed that this will run
public override void WillTerminate(UIApplication application)
{
    base.WillTerminate(application);
    new AppLifeStatusService().WriteAsync($"     iOS>WillTerminate - {DateTime.Now.Minute}:{DateTime.Now.Second} - 執行緒 {Thread.CurrentThread.ManagedThreadId}");
}

開始進行 Android 平台測試

當把這個測試範例專案在 Android 環境下啟動執行之後,可以從 Visual Studio 2019 的輸出視窗中看到底下的輸出日誌,這部分可以對照上面所提到的 應用程式生命週期 說明內容。
   --> Xamarin.Forms>OnStart - 46:53 - 執行緒 1
   -->      Android>OnStart - 46:53 - 執行緒 1
   -->      Android>OnResume - 46:53 - 執行緒 1
下面螢幕截圖將會是這個應用程式的執行結果
現在要點選螢幕上的 [開始定時執行] 這個按鈕,當紅色數字跑到 3 的時候,請點選到 Home 按鍵,這個時候應用程式將會被推到不可見的背景模式,請等候一分鐘左右的時間,將這個 App 切換到可見的前景模式。當應用程式回到可見前景模式,此時看到紅色數字已經變成 40 了。
 
當這個應用程式切換到背景不可見模式下的時候,可以從 Visual Studio 2019 的輸出視窗內,看到還是有不斷的輸出日誌顯示出來,這就表示了,雖然這個 Android 應用程式切換到不可見的背景模式下,可是,他還是會繼續的執行。
   === 0 ===
   -->      Xamarin.Forms= 0 = > Timer - 43:52
   === 1 ===
   -->      Xamarin.Forms= 1 = > Timer - 43:54
   === 2 ===
   -->      Xamarin.Forms= 2 = > Timer - 43:56
   -->      Android>OnPause - 43:57 - 執行緒 1
   --> Xamarin.Forms>OnSleep - 43:57 - 執行緒 1
   -->      Android>OnStop - 43:58 - 執行緒 1
   === 3 ===
   -->      Xamarin.Forms= 3 = > Timer - 43:58
   === 4 ===
   -->      Xamarin.Forms= 4 = > Timer - 44:00
   === 5 ===
   -->      Xamarin.Forms= 5 = > Timer - 44:20
   === 6 ===
   -->      Xamarin.Forms= 6 = > Timer - 44:40
   === 7 ===
   -->      Xamarin.Forms= 7 = > Timer - 44:60
   === 8 ===
   -->      Xamarin.Forms= 8 = > Timer - 44:80
   === 9 ===
   -->      Xamarin.Forms= 9 = > Timer - 44:10
   === 10 ===
   -->      Xamarin.Forms= 10 = > Timer - 44:12

...

   === 33 ===
   -->      Xamarin.Forms= 33 = > Timer - 44:58
   === 34 ===
   -->      Xamarin.Forms= 34 = > Timer - 45:0
   --> Xamarin.Forms>OnResume - 45:2 - 執行緒 1
   -->      Android>OnRestart - 45:2 - 執行緒 1
   === 35 ===
   -->      Xamarin.Forms= 35 = > Timer - 45:2
   === 36 ===
   -->      Xamarin.Forms= 36 = > Timer - 45:5
   === 37 ===
   -->      Xamarin.Forms= 37 = > Timer - 45:7
   === 38 ===
   -->      Xamarin.Forms= 38 = > Timer - 45:9

開始進行 iOS 平台測試

當把這個測試範例專案在 iOS 環境下啟動執行之後,可以從 Visual Studio 2019 的輸出視窗中看到底下的輸出日誌,這部分可以對照上面所提到的 應用程式生命週期 說明內容。
    --> Xamarin.Forms>OnStart - 45:55 - 執行緒 1
    -->      iOS>OnActivated - 45:55 - 執行緒 1
下面螢幕截圖將會是這個應用程式的執行結果
現在要點選螢幕上的 [開始定時執行] 這個按鈕,當紅色數字跑到 3 的時候,請點選到 Home 按鍵,這個時候應用程式將會被推到不可見的背景模式。現在,App 已經在不可見的背景模式下,請觀察 Visual Stuio 2019 的輸出視窗,應該不像是 Android 應用程式,此時輸出視窗內是沒有任何執行日誌輸出到輸出視窗內。
    === 0 ===
    -->      Xamarin.Forms= 0 = > Timer - 49:35
    === 1 ===
    -->      Xamarin.Forms= 1 = > Timer - 49:37
    === 2 ===
    -->      Xamarin.Forms= 2 = > Timer - 49:39
    --> Xamarin.Forms>OnSleep - 49:40 - 執行緒 1
    -->      iOS>OnResignActivation - 49:40 - 執行緒 1
    -->      iOS>DidEnterBackground - 49:41 - 執行緒 1
    === 3 ===
    -->      Xamarin.Forms= 3 = > Timer - 49:41
請等候一分鐘左右的時間,將這個 App 切換到可見的前景模式,當應用程式回到可見前景模式,此時看到紅色數字已經變成 6 了,這裡的紅色數字也與 Android 平台下運作不相同。
 
底下是在 iOS 平台下,當 App 從不可見背景模式切換到可見的模式下,在 Visual Studio 2019 輸出視窗內再度寫入的內容。因此,可以知道,對 iOS App,當應用程式切換到不可見的背景模式下,這個 App 的任何執行緒是沒有且無法做任何事情的。
+

    -->      iOS>WillEnterForeground - 54:31 - 執行緒 1
    === 4 ===
    -->      Xamarin.Forms= 4 = > Timer - 54:31
    --> Xamarin.Forms>OnResume - 54:31 - 執行緒 1
    -->      iOS>OnActivated - 54:31 - 執行緒 1
    === 5 ===
    -->      Xamarin.Forms= 5 = > Timer - 54:33
    === 6 ===
    -->      Xamarin.Forms= 6 = > Timer - 54:35
    === 7 ===
    -->      Xamarin.Forms= 7 = > Timer - 54:37
    === 8 ===
    -->      Xamarin.Forms= 8 = > Timer - 54:39
    === 9 ===
    -->      Xamarin.Forms= 9 = > Timer - 54:41


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



2019/05/28

Xamarin.Android 的遠端推播通知 Remote Push Notification - 使用 Azure Notification Hub

Xamarin.Android 的遠端推播通知 Remote Push Notification - 使用 Azure Notification Hub

本章節將會說明如何透過 Azure Notification Hub 服務,建立起一個具有遠端推播訊息功能的 Android App。

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

本教學課程示範如何使用 Azure 通知中樞將推播通知傳送至 Xamarin.Android 應用程式。 您會建立可使用 Firebase 雲端通訊 (FCM) 接收推播通知的空白 Xamarin.Android 應用程式。 您會使用通知中樞,將推播通知廣播到所有執行您的應用程式的裝置。 NotificationHubs 應用程式範例中提供完成的程式碼。
在本教學課程中,您會執行下列步驟:
  • 建立 Firebase 專案並啟用 Firebase 雲端通訊
  • 建立 Azure 通知中樞
  • 建立 Xamarin.Android 應用程式,並將其連線至通知中樞
  • 從 Azure 入口網站傳送測試通知

必要條件

  • Azure 訂用帳戶。 如果您沒有 Azure 訂用帳戶,請在開始前建立免費 Azure 帳戶。
  • Visual Studio 搭配 Xamarin (在 Windows 上) 或 Visual Studio for Mac (在 OS X 上)。
  • 有效的 Google 帳戶

建立 Firebase 專案並啟用 Firebase 雲端通訊

建立一個新的 Firebase 專案

  • 打開 Firebase 主控台 網頁,使用您的 Google 帳號,登入到 Firebase 系統內 。在登入首頁內,點選 [新增專案] 按鈕
    Firebase 主控台首頁
  • 在 [新增專案] 對話窗中的 [專案名稱] 欄位,輸入 Xamarin-Push-Notification
  • 勾選 [新增專案] 對話窗最下方的 我接受 ... 檢查盒
  • 最後,點選 [建立專案] 按鈕
    Firebase 建立專案 對話窗
  • 當此 Firebase 專案建立完成之後,點選 [繼續] 按鈕
    Firebase 建立專案完成
  • 現在,將會顯示 Xamarin-Push-Notification 這個專案頁面,找到 [首先請新增應用程式] 文字,點選該文字上方代表 Android 機器人的圖示,[將 Firebase 新增至 Android 應用程式]
    Firebase 專案首頁
  • 此時,將會顯示 [新增 Android 應用程式] 頁面,並且將會有四個步驟要進行,分別是 [註冊應用程式]、[下載設定檔]、[新增 Firebase SDK]、[執行應用程式以驗證是否安裝成功]
  • 在 [註冊應用程式] 步驟中,需要填寫底下兩個欄位資料
  • 在 [Android 套件名稱] 欄位中,輸入 com.vulcan.AzureNHub
把這個 Android 套件名稱 字串記錄下來
該 Android 套件名稱 將會於 Xamarin.Android 專案中用到,這將會用於 Xamarin.Android 的 [Xamarin.Android 專案屬性] > [Android 資訊清單] > [套件名稱] 欄位,因此,請將這個 套件名稱 記錄下來
  • 在 [應用程式暱稱 (選填)] 欄位中,輸入 Azure Notification Hub Lab
  • 點選 [註冊應用程式] 按鈕
    Firebase 新增 Android 應用程式 - 註冊應用程式
  • 在 [下載設定檔] 步驟中,請點選 [下載 google-servic.json] 按鈕,取得 google-servic.json 這個檔案,等下在 Xamarin.Android 專案內會使用的到。
請將這個 google-servic.json 妥善保存在適當的地方
google-servic.json 檔案 將會於 Xamarin.Android 專案中用到
  • 點選右下角的 [繼續] 按鈕,繼續下一個步驟
    Firebase 新增 Android 應用程式 - 下載設定檔
  • 在 [新增 Firebase SDK] 步驟中,點選右下角的 [繼續] 按鈕,繼續下一個步驟
    Firebase 新增 Android 應用程式 - 新增 Firebase SDK
  • 在 [執行應用程式以驗證是否安裝成功] 步驟中,點選右下角的 [略過此步驟] 連結,完成 新增 Android 應用程式 程序。
    Firebase 新增 Android 應用程式 - 執行應用程式以驗證是否安裝成功

Firebase 專案設定

  • 現在將會顯示出 [Xamarin-Push-Notification] 頁面,在該頁面左上方找到 [Project Overview] 文字的右方,有個齒輪圖示
  • 點選該齒輪圖示,在彈出子視窗中,點選 [專案設定]
    Firebase 專案設定
  • 此時,將會看到 [設定] 頁面的 [一般] 標籤,若剛剛忘記取得 google-servic.json 這個檔案,可以在這裡點選 [下載 google-servic.json] 按鈕,來重新取得
    Firebase 設定 - 一般
  • 點選 [Cloud Messaging] 標籤,將會看到有個 伺服器金鑰 欄位。
    Firebase 設定 - Cloud Messaging
把這個 伺服器金鑰 字串記錄下來
請把這個 伺服器金鑰 值記錄下來,等下在 Azure 通知中樞內會用到,因此,請將這個 伺服器金鑰 記錄下來

建立 Azure 通知中樞

  • 打開 Azure 入口網站 網頁,使用您的 Microsoft 帳號,登入到 Azure 系統內 。
    Azure 入口網站
  • 在 Azure 入口網站 左方,點選 [所有服務],當 所有服務 的彈出子畫面出現後,在該子畫面最上方輸入 hub ,將會得到與 hub 有關的 Azure 產品。
    此時,請選擇 [Notification Hubs] 這個項目
    Azure 所有服務
  • 現在將會顯示 [Notification Hubs] 頁面,請點選上方工具列的 [新增] 按鈕,準備新增一個 Notification Hubs 服務
    Azure Notification Hubs
  • 當顯示新 刀鋒 視窗,分別在底下欄位填入適當的欄位值
    • Notification Hub
      這裡需要填入這個通知中樞服務的名稱,請輸入 azure-notification-hub-lab
    • Create a new namespace
      在這裡填入一個通知中樞的命名空間,由於在這裡僅是做練習,因此請在此輸入 azure-notification-hub-lab ,建立一個新的通知中樞命名空間
    • 位置
      在這裡選擇 東南亞
    • Resource Group
      由於在這裡僅是做練習,在這裡點選 [新建] 連結,當顯示文字 資源群組是能夠存放 Azure 解決方案相關資源的容器。 的彈出視窗,在該彈出視窗的 名稱 欄位內,填入 azure-notification-hub-lab,完成後,點選 [確定] 按鈕。
    • 訂用帳戶
      依據登入帳號可以使用訂用帳戶,選擇合適的帳戶項目
    • Pricing tier
      由於在這裡僅是做練習,在這裡就僅選擇 Free
把這個 通知中樞服務的名稱 字串記錄下來
通知中樞服務的名稱 等下在 Xamarin.Android 專案內會用到,因此,請將這個通知中樞服務的名稱 字串記錄下來
  • 最後,在該 刀鋒 視窗下方,點選 [建立] 按鈕
    新建一個通知中樞
  • 當通知中樞建立完成之後,可以點選 [通知] 鈴鐺圖示,然後選取 [前往資源] 按鈕,或重新整理 [通知中樞] 頁面中的清單,然後選取您的通知中樞
    通知中樞完成的提示訊息
  • 當進入到剛剛建立的 [azure-notification-hub-lab] 通知中樞項目頁面,在左方的功能表清單中,選取 [Access Policies]。
    通知中樞 - Access Policies
把這個 DefaultListenSharedAccessSignature 字串記錄下來
這裡存在兩個連接字串,請記下 [DefaultListenSharedAccessSignature] 這個連結字串,因為,等下在 Xamarin.Android 專案中會使用到

設定通知中樞的 GCM 設定

  • 在剛剛的 [azure-notification-hub-lab] 通知中樞項目頁面,從左方功能表清單內,點選 [Google (GCM/FCM)] 這個項目
  • 找出剛剛在 Firebase 專案內的 伺服器金鑰 ,將這個 伺服器金鑰 值填入到 [API Key] 欄位內,然後點選 [Save] 按鈕,儲存這個設定
    通知中樞 - Google (GCM/FCM) - API Key

建立 Xamarin.Android 應用程式,並將其連線至通知中樞

建立 Xamarin.Forms 專案

請依照底下說明步驟,建立一個使用 Prism 開發框架的 Xamarin.Forms 練習專案
  • 啟動 Visual Studio 2017 應用程式
  • 點選功能表 [檔案] > [新增] > [專案]
  • 在 [新增專案] 對話窗左方,點選 [Prism] 項目
  • 在 [新增專案] 對話窗中間,點選 [Prism Blank App (Xamarin.Forms)],
  • 在 [新增專案] 對話窗下方的 [名稱] 欄位,輸入 XFAzureNHub
  • 點選 [新增專案] 對話窗右下方的 [確定] 按鈕
  • 當 [PRISM PROJECT WIZARD] 對話窗出現之後,請確定有勾選 [ANDROID] 與 [iOS] 這兩個項目,而 [UWP] 這個項目則不要勾選
  • 確定 [Container] 下拉選單欄位,選擇的是 [Unity]
  • 最後,點選 [CREATE PROJECT] 按鈕
  • 現在,請等候 Xamarin.Forms 專案建立完成

套用 Firebase 設定到 Xamarin.Android 專案

  • 展開 [XFAzureNHub.Android] 專案,使用滑鼠雙擊 [Properties] 節點
  • 點選 [Android 資訊清單] 標籤頁次,將剛剛在 Firebase 專案中設定的套件名稱,也就是 com.vulcan.AzureNHub ,填入到 [套件名稱] 欄位內。
    Xamarin.Android 的套件名稱修正
需要設定與 Firebase 專案上相同的 套件名稱
若 Xamarin.Android 專案屬性中的套件名稱與 Firebase 專案內的套件名稱兩者不一致,將會造成這個 Xamarin.Android 專案無法正常接收到來自遠端的推播通知。

安裝相關 NuGet 套件

  • 滑鼠右擊 [XFAzureNHub.Android] 專案的 [參考] 節點,選取 [管理 NuGet 套件] 選項
  • 切換到 [瀏覽] 標籤頁次
  • 搜尋 [Xamarin.GooglePlayServices.Base] 套件,安裝到 [XFAzureNHub.Android] 專案
    安裝 Xamarin.GooglePlayServices.Base 套件
  • 搜尋 [Xamarin.Firebase.Messaging] 套件,安裝到 [XFAzureNHub.Android] 專案
    安裝 Xamarin.Firebase.Messaging 套件
  • 搜尋 [Xamarin.Azure.NotificationHubs.Android] 套件,安裝到 [XFAzureNHub.Android] 專案
    安裝 Xamarin.Azure.NotificationHubs.Android 套件

新增 Google Services JSON 檔案

接下來要來處理一個 Firebase 很重要的檔案,也就是剛剛從 Google Firebase 主控台下載下來的 google-services.json 檔案
  • 找到剛剛下載下來的 google-services.json,把它拖拉到方案總管的 [XFAzureNHub.Android] 專案內
  • 點選 google-services.json 檔案,並且在 [屬性] 窗格中,將 [建置動作] 設定為 GoogleServicesJson。
    設定 google-services.json 建置動作為 GoogleServicesJson
看不到 GoogleServicesJson 選項之處置做法
如果您未看到 GoogleServicesJson 選項,請關閉 Visual Studio 再加以重新啟動,並重新開啟專案,然後重複上述步驟,就會看到了 GoogleServicesJson 選項。

開始設計 Firebase 推播通知的相關程式碼

  • 展開 [XFAzureNHub.Android] 專案內的 [Properties] 節點,打開 [AndroidManifest.xml] 檔案
    設定 google-services.json 建置動作為 GoogleServicesJson
  • 將下列 <receiver> 元素插入 <application> 元素中
 
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
    <intent-filter>
    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
    <category android:name="${applicationId}" />
    </intent-filter>
</receiver>
  • 底下是修正後的 [AndroidManifest.xml] 檔案內容
 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.vulcan.AzureNHub" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
    <uses-permission android:name="android.permission.INTERNET" />
    <application android:label="XFAzureNHub.Android" android:icon="@mipmap/ic_launcher">
    <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
    <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
      <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
        <category android:name="${applicationId}" />
      </intent-filter>
    </receiver>
  </application>
</manifes>

建立 Constants.cs 類別

  • 在 [XFAzureNHub.Android] 專案內,建立一個 Constants.cs 類別,並定義類別中的下列常數值。
    其中,[ListenConnectionString] 這個常數,需要到 Azure 通知中樞中,找到 [DefaultListenSharedAccessSignature] 這個欄位值來填入進來,這個欄位值可以從 [azure-notification-hub-lab] 這個通知中樞設定頁面,在左方功能清單的最下方,找到 [Manage] > [Access Policies] 連結,點擊之後,就會在右方視窗中看到 [DefaultListenSharedAccessSignature] 欄位。
    在 Azure Notification Hub 的 DefaultListenSharedAccessSignature 值
    另外一個 [NotificationHubName] 常數,就是這個 Azure 通知中樞的名稱,也就是 [azure-notification-hub-lab]
    在 Azure Notification Hub 的該中樞的名稱
 Constants.cs
public static class Constants
{
    public const string ListenConnectionString = "請在這裡填入 Azure Notification Hub 的 DefaultListenSharedAccessSignature 值";
    public const string NotificationHubName = "這裡填入 Azure Notificaion Hub 的名稱";
    public const string ChannelName = "azure-notification-hub-lab";
    public const string CHANNEL_ID = "location_notification";
    public const string NotificationTitle = "Azure 通知中樞訊息";
}

修正 MainActivity 類別

  • 打開 [MainActivity.cs] 檔案,在最前面加入底下的命名空間參考
 MainActivity.cs
using Android.Util;
在這個 [MainActivity] 類別,加入底下的常數欄位宣告
 MainActivity.cs
public const string TAG = "MainActivity";
  • 將下列程式碼新增到 base.OnCreate(savedInstanceState) 之後的 OnCreate
 MainActivity.cs
if (Intent.Extras != null)
{
    foreach (var key in Intent.Extras.KeySet())
    {
        if(key!=null)
        {
            var value = Intent.Extras.GetString(key);
            Log.Debug(TAG, "Key: {0} Value: {1}", key, value);
        }
    }
}

建立 MyFirebaseIIDService 類別

  • 在 [XFAzureNHub.Android] 專案內,建立一個 MyFirebaseIIDService.cs 類別
  • 使用底下程式碼替換掉檔案內容
 MyFirebaseIIDService.cs
using System.Collections.Generic;

using Android.App;
using Android.Content;
using Android.Util;
using WindowsAzure.Messaging;
using Firebase.Iid;

namespace XFAzureNHub.Droid
{
    [Service]
    [IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
    public class MyFirebaseIIDService : FirebaseInstanceIdService
    {
        const string TAG = "MyFirebaseIIDService";
        NotificationHub hub;

        public override void OnTokenRefresh()
        {
            var refreshedToken = FirebaseInstanceId.Instance.Token;
            Log.Debug(TAG, "FCM token: " + refreshedToken);
            SendRegistrationToServer(refreshedToken);
        }

        void SendRegistrationToServer(string token)
        {
            // Register with Notification Hubs
            hub = new NotificationHub(Constants.NotificationHubName,
                                        Constants.ListenConnectionString, this);

            var tags = new List<string>() { };
            var regID = hub.Register(token, tags.ToArray()).RegistrationId;

            Log.Debug(TAG, $"Successful registration of ID {regID}");
        }
    }
}

建立 MyFirebaseMessagingService 類別

  • 在 [XFAzureNHub.Android] 專案內,建立一個 MyFirebaseMessagingService.cs 類別
  • 使用底下程式碼替換掉檔案內容
 MyFirebaseMessagingService.cs
using System;
using System.Linq;

using Android.App;
using Android.Content;
using Android.Media;
using Android.OS;
using Android.Support.V4.App;
using Android.Util;
using Firebase.Messaging;

namespace XFAzureNHub.Droid
{
    [Service]
    [IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
    public class MyFirebaseMessagingService : FirebaseMessagingService
    {
        const string TAG = "MyFirebaseMsgService";
        public override void OnMessageReceived(RemoteMessage message)
        {
            Log.Debug(TAG, "From: " + message.From);
            if (message.GetNotification() != null)
            {
                //These is how most messages will be received
                Log.Debug(TAG, "Notification Message Body: " + message.GetNotification().Body);
                createNotification(Constants.NotificationTitle, message.GetNotification().Body);
            }
            else
            {
                //Only used for debugging payloads sent from the Azure portal
                createNotification(Constants.NotificationTitle, message.Data.Values.First());
            }
        }

        void createNotification(string title, string desc)
        {
            var intent = new Intent(this, typeof(MainActivity));
            intent.AddFlags(ActivityFlags.ClearTop);
            var pendingIntent = PendingIntent.GetActivity(this, RandomGenerator(), intent, PendingIntentFlags.OneShot);

            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .SetSmallIcon(Resource.Drawable.ic_launcher)
                .SetContentTitle(title)
                .SetContentText(desc)
                .SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))
                .SetAutoCancel(true)
                .SetContentIntent(pendingIntent);

            NotificationManager notificationManager;
            notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);

            if (Build.VERSION.SdkInt < BuildVersionCodes.O)
            {
                notificationManager.Notify(RandomGenerator(), notificationBuilder.Build());
            }
            else
            {
                notificationBuilder.SetChannelId(Constants.CHANNEL_ID);
                var name = Constants.ChannelName;
                var description = desc;
                var channel = new NotificationChannel(Constants.CHANNEL_ID, name, NotificationImportance.Default)
                {
                    Description = description, 
                };

                notificationManager.CreateNotificationChannel(channel);
                notificationManager.Notify(RandomGenerator(), notificationBuilder.Build());
            }
        }
        private int RandomGenerator()
        {
            return new Random().Next(int.MinValue, int.MaxValue);
        }
    }
}

開始進行測試

  • 滑鼠右擊專案 [XFAzureNHub.Android],點選 [設定為起始專案] 選項
  • 在 Azure 通知中樞 (azure-notification-hub-lab) 的網頁上,點選 [Test Send] 按鈕
    Azure 通知中樞 (azure-notification-hub-lab)
  • 切換 [Platform] 欄位值為 [Android]
  • 點選下方的 [Send] 按鈕
    準備進行 Android 平台的通知測試
  • 當最下方的 [Result] 列表出現新的紀錄,則表示剛剛的通知推播已經傳送到遠端裝置上了
    推播通知傳送結果
  • 從模擬器上就可以看到這個遠端推播訊息通知
    接收到推播通知
選擇模擬器或者實體裝置
當要執行這個練習專案,並且可以接收到來自於 Azure 通知中樞的遠端推播內容,需要在目的模擬器或者實體裝置上有安裝 Google Play Service 這個軟體,否則,是無法接收到來自於遠端推播通知訊息。
因此,在 Android Device Manager 對話窗中,請針對要進行除錯的模擬器,使用滑鼠右擊該裝置項目,再彈出對話窗中,選擇 [編輯] 選項
Android Device Manager
當該模擬器裝置的屬性對話窗出現之後,在其裝置屬性對話窗的左下方,將會看到有個 [Google Play Store] 選項,請勾選它;若無法勾選這個選項,請將 處理器 這個下拉選單,選擇 x86 這個項目即可。
Android Device Manager