XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 開發實戰

特別說明

2019/06/05

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

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

當在進行 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



沒有留言:

張貼留言