中的本機通知 Xamarin.Forms

Download Sample 下載範例

本機通知是由安裝在行動裝置上的應用程式所傳送的警示。 本機通知通常用於下列功能:

  • 行事曆事件
  • 提醒
  • 以位置為基礎的觸發程式

每個平台都會以不同的方式處理本機通知的建立、顯示和取用。 本文說明如何建立跨平臺抽象概念,以使用 Xamarin.Forms傳送、排程及接收本機通知。

Local notifications application on iOS and Android

建立跨平臺介面

應用程式 Xamarin.Forms 應該建立和使用通知,而不需考慮基礎平台實作。 下列 INotificationManager 介面會在共用程式代碼連結庫中實作,並定義應用程式可用來與通知互動的跨平臺 API:

public interface INotificationManager
{
    event EventHandler NotificationReceived;
    void Initialize();
    void SendNotification(string title, string message, DateTime? notifyTime = null);
    void ReceiveNotification(string title, string message);
}

此介面將會在每個平台項目中實作。 事件 NotificationReceived 可讓應用程式處理傳入通知。 Initialize方法應該執行準備通知系統所需的任何原生平台邏輯。 方法 SendNotification 應該會在選擇性 DateTime的傳送通知。 收到 ReceiveNotification 訊息時,基礎平臺應該呼叫 方法。

在中使用介面 Xamarin.Forms

建立介面之後,即使尚未建立平台實作,仍可在共用 Xamarin.Forms 專案中取用它。 範例應用程式包含 ContentPage 名為 MainPage.xaml 且內容如下:

<StackLayout Margin="0,35,0,0"
             x:Name="stackLayout">
    <Label Text="Click the button below to create a local notification."
           TextColor="Red"
           HorizontalOptions="Center"
           VerticalOptions="Start" />
    <Button Text="Create Notification"
            HorizontalOptions="Center"
            VerticalOptions="Start"
            Clicked="OnSendClick" />
    <Label Text="Click the button below to schedule a local notification for in 10 seconds time."
           TextColor="Red"
           HorizontalOptions="Center"
           VerticalOptions="Start" />
    <Button Text="Create Notification"
            HorizontalOptions="Center"
            VerticalOptions="Start"
            Clicked="OnScheduleClick" />
</StackLayout>

版面配置包含 Label 說明指示的專案,以及 Button 點選時傳送或排程通知的專案。

類別程式 MainPage 代碼後置會處理通知的傳送和接收:

public partial class MainPage : ContentPage
{
    INotificationManager notificationManager;
    int notificationNumber = 0;

    public MainPage()
    {
        InitializeComponent();

        notificationManager = DependencyService.Get<INotificationManager>();
        notificationManager.NotificationReceived += (sender, eventArgs) =>
        {
            var evtData = (NotificationEventArgs)eventArgs;
            ShowNotification(evtData.Title, evtData.Message);
        };
    }

    void OnSendClick(object sender, EventArgs e)
    {
        notificationNumber++;
        string title = $"Local Notification #{notificationNumber}";
        string message = $"You have now received {notificationNumber} notifications!";
        notificationManager.SendNotification(title, message);
    }

    void OnScheduleClick(object sender, EventArgs e)
    {
        notificationNumber++;
        string title = $"Local Notification #{notificationNumber}";
        string message = $"You have now received {notificationNumber} notifications!";
        notificationManager.SendNotification(title, message, DateTime.Now.AddSeconds(10));
    }

    void ShowNotification(string title, string message)
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            var msg = new Label()
            {
                Text = $"Notification Received:\nTitle: {title}\nMessage: {message}"
            };
            stackLayout.Children.Add(msg);
        });
    }
}

類別建 MainPage 構函式會使用 Xamarin.FormsDependencyService 來擷取 平臺特定的實例 INotificationManagerOnSendClickOnScheduleClicked 方法會INotificationManager使用 實例來傳送和排程新的通知。 從 ShowNotification 附加至 NotificationReceived 事件的事件處理程式呼叫 方法,並在叫用事件時將新的 Label 插入頁面。

NotificationReceived事件處理程式會將其事件自變數NotificationEventArgs轉換成 。 這個類型定義於共享 Xamarin.Forms 專案中:

public class NotificationEventArgs : EventArgs
{
    public string Title { get; set; }
    public string Message { get; set; }
}

如需 的詳細資訊 Xamarin.FormsDependencyService,請參閱 Xamarin.Forms DependencyService

建立Android介面實作

若要讓 Xamarin.Forms 應用程式在Android上傳送和接收通知,應用程式必須提供介面的實作 INotificationManager

建立 AndroidNotificationManager 類別

類別 AndroidNotificationManager 會實作 INotificationManager 介面:

using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using AndroidX.Core.App;
using Xamarin.Forms;
using AndroidApp = Android.App.Application;

[assembly: Dependency(typeof(LocalNotifications.Droid.AndroidNotificationManager))]
namespace LocalNotifications.Droid
{
    public class AndroidNotificationManager : INotificationManager
    {
        const string channelId = "default";
        const string channelName = "Default";
        const string channelDescription = "The default channel for notifications.";

        public const string TitleKey = "title";
        public const string MessageKey = "message";

        bool channelInitialized = false;
        int messageId = 0;
        int pendingIntentId = 0;

        NotificationManager manager;

        public event EventHandler NotificationReceived;

        public static AndroidNotificationManager Instance { get; private set; }

        public AndroidNotificationManager() => Initialize();

        public void Initialize()
        {
            if (Instance == null)
            {
                CreateNotificationChannel();
                Instance = this;
            }
        }

        public void SendNotification(string title, string message, DateTime? notifyTime = null)
        {
            if (!channelInitialized)
            {
                CreateNotificationChannel();
            }

            if (notifyTime != null)
            {
                Intent intent = new Intent(AndroidApp.Context, typeof(AlarmHandler));
                intent.PutExtra(TitleKey, title);
                intent.PutExtra(MessageKey, message);

                PendingIntent pendingIntent = PendingIntent.GetBroadcast(AndroidApp.Context, pendingIntentId++, intent, PendingIntentFlags.CancelCurrent);
                long triggerTime = GetNotifyTime(notifyTime.Value);
                AlarmManager alarmManager = AndroidApp.Context.GetSystemService(Context.AlarmService) as AlarmManager;
                alarmManager.Set(AlarmType.RtcWakeup, triggerTime, pendingIntent);
            }
            else
            {
                Show(title, message);
            }
        }

        public void ReceiveNotification(string title, string message)
        {
            var args = new NotificationEventArgs()
            {
                Title = title,
                Message = message,
            };
            NotificationReceived?.Invoke(null, args);
        }

        public void Show(string title, string message)
        {
            Intent intent = new Intent(AndroidApp.Context, typeof(MainActivity));
            intent.PutExtra(TitleKey, title);
            intent.PutExtra(MessageKey, message);

            PendingIntent pendingIntent = PendingIntent.GetActivity(AndroidApp.Context, pendingIntentId++, intent, PendingIntentFlags.UpdateCurrent);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, channelId)
                .SetContentIntent(pendingIntent)
                .SetContentTitle(title)
                .SetContentText(message)
                .SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.xamagonBlue))
                .SetSmallIcon(Resource.Drawable.xamagonBlue)
                .SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);

            Notification notification = builder.Build();
            manager.Notify(messageId++, notification);
        }

        void CreateNotificationChannel()
        {
            manager = (NotificationManager)AndroidApp.Context.GetSystemService(AndroidApp.NotificationService);

            if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
            {
                var channelNameJava = new Java.Lang.String(channelName);
                var channel = new NotificationChannel(channelId, channelNameJava, NotificationImportance.Default)
                {
                    Description = channelDescription
                };
                manager.CreateNotificationChannel(channel);
            }

            channelInitialized = true;
        }

        long GetNotifyTime(DateTime notifyTime)
        {
            DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(notifyTime);
            double epochDiff = (new DateTime(1970, 1, 1) - DateTime.MinValue).TotalSeconds;
            long utcAlarmTime = utcTime.AddSeconds(-epochDiff).Ticks / 10000;
            return utcAlarmTime; // milliseconds
        }
    }
}

命名空間 assembly 上方的屬性會 INotificationManagerDependencyService註冊介面實作。

Android 可讓應用程式定義通知的多個通道。 方法 Initialize 會建立範例應用程式用來傳送通知的基本通道。 方法 SendNotification 會定義建立和傳送通知所需的平臺特定邏輯。 ReceiveNotification收到訊息時,Android OS 會呼叫 方法,並叫用事件處理程式。

方法 SendNotification 會立即建立本機通知,或直接 DateTime建立 。 您可以使用 類別來排程通知DateTimeAlarmManager,而衍生自 BroadcastReceiver 類別的物件將會收到通知:

[BroadcastReceiver(Enabled = true, Label = "Local Notifications Broadcast Receiver")]
public class AlarmHandler : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        if (intent?.Extras != null)
        {
            string title = intent.GetStringExtra(AndroidNotificationManager.TitleKey);
            string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);

            AndroidNotificationManager manager = AndroidNotificationManager.Instance ?? new AndroidNotificationManager();
            manager.Show(title, message);
        }
    }
}

重要

根據預設,使用 類別排程的 AlarmManager 通知將不會在裝置重新啟動時存留。 不過,您可以設計應用程式,以在裝置重新啟動時自動重新排程通知。 如需詳細資訊,請在 [排程 developer.android.com 上的重複警示] 和範例重新啟動裝置時啟動警示。 如需Android上背景處理的相關信息,請參閱 developer.android.com 背景處理 指南。

如需廣播接收器的詳細資訊,請參閱 Xamarin.Android 中的廣播接收器。

處理Android上的傳入通知

類別 MainActivity 必須偵測傳入通知,並通知 AndroidNotificationManager 實例。 類別 Activity 上的 MainActivity 屬性應該指定 LaunchMode 的值 LaunchMode.SingleTop

[Activity(
        //...
        LaunchMode = LaunchMode.SingleTop]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        // ...
    }

模式 SingleTop 可防止在應用程式處於前景時啟動的多個實例 Activity 。 這可能 LaunchMode 不適用於在更複雜的通知案例中啟動多個活動的應用程式。 如需列舉值的詳細資訊 LaunchMode ,請參閱 Android 活動 LaunchMode

在類別中 MainActivity ,會修改為接收傳入通知:

protected override void OnCreate(Bundle savedInstanceState)
{
    // ...

    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    LoadApplication(new App());
    CreateNotificationFromIntent(Intent);
}

protected override void OnNewIntent(Intent intent)
{
    CreateNotificationFromIntent(intent);
}

void CreateNotificationFromIntent(Intent intent)
{
    if (intent?.Extras != null)
    {
        string title = intent.GetStringExtra(AndroidNotificationManager.TitleKey);
        string message = intent.GetStringExtra(AndroidNotificationManager.MessageKey);
        DependencyService.Get<INotificationManager>().ReceiveNotification(title, message);
    }
}

方法 CreateNotificationFromIntent 會從 自變數擷 intent 取通知資料,並使用 方法提供給 AndroidNotificationManagerReceiveNotification 。 從 CreateNotificationFromIntent 方法和 OnNewIntent 方法呼叫 OnCreate 方法:

  • 當應用程式由通知數據啟動時, Intent 數據會傳遞至 OnCreate 方法。
  • 如果應用程式已經在前景中, Intent 數據將會傳遞至 OnNewIntent 方法。

Android 提供許多通知的進階選項。 如需詳細資訊,請參閱 Xamarin.Android 中的通知。

建立 iOS 介面實作

若要讓 Xamarin.Forms 應用程式在 iOS 上傳送和接收通知,應用程式必須提供的 INotificationManager實作。

建立 iOSNotificationManager 類別

類別 iOSNotificationManager 會實作 INotificationManager 介面:

using System;
using Foundation;
using UserNotifications;
using Xamarin.Forms;

[assembly: Dependency(typeof(LocalNotifications.iOS.iOSNotificationManager))]
namespace LocalNotifications.iOS
{
    public class iOSNotificationManager : INotificationManager
    {
        int messageId = 0;
        bool hasNotificationsPermission;
        public event EventHandler NotificationReceived;

        public void Initialize()
        {
            // request the permission to use local notifications
            UNUserNotificationCenter.Current.RequestAuthorization(UNAuthorizationOptions.Alert, (approved, err) =>
            {
                hasNotificationsPermission = approved;
            });
        }

        public void SendNotification(string title, string message, DateTime? notifyTime = null)
        {
            // EARLY OUT: app doesn't have permissions
            if (!hasNotificationsPermission)
            {
                return;
            }

            messageId++;

            var content = new UNMutableNotificationContent()
            {
                Title = title,
                Subtitle = "",
                Body = message,
                Badge = 1
            };            

            UNNotificationTrigger trigger;
            if (notifyTime != null)
            {
                // Create a calendar-based trigger.
                trigger = UNCalendarNotificationTrigger.CreateTrigger(GetNSDateComponents(notifyTime.Value), false);
            }
            else
            {
                // Create a time-based trigger, interval is in seconds and must be greater than 0.
                trigger = UNTimeIntervalNotificationTrigger.CreateTrigger(0.25, false);
            }                      

            var request = UNNotificationRequest.FromIdentifier(messageId.ToString(), content, trigger);
            UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
            {
                if (err != null)
                {
                    throw new Exception($"Failed to schedule notification: {err}");
                }
            });
        }

        public void ReceiveNotification(string title, string message)
        {
            var args = new NotificationEventArgs()
            {
                Title = title,
                Message = message
            };
            NotificationReceived?.Invoke(null, args);
        }

        NSDateComponents GetNSDateComponents(DateTime dateTime)
        {
            return new NSDateComponents
            {
                Month = dateTime.Month,
                Day = dateTime.Day,
                Year = dateTime.Year,
                Hour = dateTime.Hour,
                Minute = dateTime.Minute,
                Second = dateTime.Second
            };
        }
    }
}

命名空間 assembly 上方的屬性會 INotificationManagerDependencyService註冊介面實作。

在 iOS 上,您必須要求許可權才能使用通知,才能嘗試排程通知。 方法 Initialize 會要求授權以使用本機通知。 方法 SendNotification 會定義建立和傳送通知所需的邏輯。 ReceiveNotification收到訊息時,iOS 會呼叫 方法,並叫用事件處理程式。

注意

方法會 SendNotification 立即使用 UNTimeIntervalNotificationTrigger 物件建立本機通知,或直接 DateTime 使用 物件建立本機 UNCalendarNotificationTrigger 通知。

處理 iOS 上的傳入通知

在 iOS 上,您必須建立子類別 UNUserNotificationCenterDelegate 的委派來處理傳入訊息。 範例應用程式會定義類別 iOSNotificationReceiver

public class iOSNotificationReceiver : UNUserNotificationCenterDelegate
{
    public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
    {
        ProcessNotification(notification);
        completionHandler(UNNotificationPresentationOptions.Alert);
    }

    void ProcessNotification(UNNotification notification)
    {
        string title = notification.Request.Content.Title;
        string message = notification.Request.Content.Body;

        DependencyService.Get<INotificationManager>().ReceiveNotification(title, message);
    }    
}

這個類別會使用 DependencyService 取得 類別的 iOSNotificationManager 實例,並將傳入通知數據 ReceiveNotification 提供給方法。

類別 AppDelegate 必須在應用程式啟動期間指定 iOSNotificationReceiver 物件做為 UNUserNotificationCenter 委派。 這發生在 方法中 FinishedLaunching

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();

    UNUserNotificationCenter.Current.Delegate = new iOSNotificationReceiver();

    LoadApplication(new App());
    return base.FinishedLaunching(app, options);
}

iOS 提供許多通知的進階選項。 如需詳細資訊,請參閱 Xamarin.iOS 中的通知。

測試應用程式

一旦平台專案包含介面的已註冊實作 INotificationManager ,就可以在兩個平臺上測試應用程式。 執行應用程式,然後按兩下其中一個 [建立通知 ] 按鈕來建立通知。

在 Android 上,通知會出現在通知區域中。 點選通知時,應用程式會收到通知並顯示訊息:

Local notifications on Android

在 iOS 上,應用程式會自動收到傳入通知,而不需要使用者輸入。 應用程式會收到通知,並顯示訊息:

Local notifications on iOS