Udostępnij za pośrednictwem


Powiadomienia lokalne w programie Xamarin.Forms

Powiadomienia lokalne to alerty wysyłane przez aplikacje zainstalowane na urządzeniu przenośnym. Powiadomienia lokalne są często używane w przypadku takich funkcji jak:

  • Zdarzenia kalendarza
  • Przypomnienia
  • Wyzwalacze oparte na lokalizacji

Każda platforma obsługuje tworzenie, wyświetlanie i użycie powiadomień lokalnych w inny sposób. W tym artykule wyjaśniono, jak utworzyć abstrakcję międzyplatformową w celu wysyłania, planowania i odbierania powiadomień lokalnych za pomocą polecenia Xamarin.Forms.

Aplikacja powiadomień lokalnych w systemach iOS i Android

Tworzenie interfejsu międzyplatformowego

Aplikacja Xamarin.Forms powinna tworzyć i korzystać z powiadomień bez obaw dotyczących implementacji podstawowej platformy. INotificationManager Następujący interfejs jest implementowany w udostępnionej bibliotece kodu i definiuje międzyplatformowy interfejs API, którego aplikacja może używać do interakcji z powiadomieniami:

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

Ten interfejs zostanie zaimplementowany w każdym projekcie platformy. Zdarzenie NotificationReceived umożliwia aplikacji obsługę powiadomień przychodzących. Metoda Initialize powinna wykonywać dowolną natywną logikę platformy wymaganą do przygotowania systemu powiadomień. Metoda SendNotification powinna wysłać powiadomienie w opcjonalnym DateTimepliku . Metoda ReceiveNotification powinna być wywoływana przez platformę bazową po odebraniu komunikatu.

Korzystanie z interfejsu w programie Xamarin.Forms

Po utworzeniu interfejsu można go używać w projekcie udostępnionym Xamarin.Forms , mimo że implementacje platformy nie zostały jeszcze utworzone. Przykładowa aplikacja zawiera ContentPage plik MainPage.xaml z następującą zawartością:

<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>

Układ zawiera Label elementy, które wyjaśniają instrukcje, oraz Button elementy, które wysyłają lub zaplanują powiadomienie po naciśnięciu.

Klasa MainPage code-behind obsługuje wysyłanie i odbieranie powiadomień:

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);
        });
    }
}

Konstruktor MainPage klasy używa klasy , Xamarin.FormsDependencyService aby pobrać wystąpienie specyficzne dla platformy .INotificationManager Metody OnSendClick i OnScheduleClicked używają wystąpienia do wysyłania INotificationManager i planowania nowych powiadomień. Metoda jest wywoływana ShowNotification z programu obsługi zdarzeń dołączonego NotificationReceived do zdarzenia i wstawi nowe Label na stronie po wywołaniu zdarzenia.

Procedura NotificationReceived obsługi zdarzeń rzutuje argumenty zdarzeń na NotificationEventArgs. Ten typ jest zdefiniowany w projekcie udostępnionym Xamarin.Forms :

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

Aby uzyskać więcej informacji o obiekcie Xamarin.FormsDependencyService, zobacz Xamarin.Forms DependencyService.

Tworzenie implementacji interfejsu systemu Android

Xamarin.Forms Aby aplikacja wysyłała i odbierała powiadomienia w systemie Android, aplikacja musi zapewnić implementację interfejsuINotificationManager.

Tworzenie klasy AndroidNotificationManager

Klasa AndroidNotificationManager implementuje INotificationManager interfejs:

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
        }
    }
}

Atrybut assembly powyżej przestrzeni nazw rejestruje implementację interfejsu INotificationManager za pomocą elementu DependencyService.

System Android umożliwia aplikacjom definiowanie wielu kanałów dla powiadomień. Metoda Initialize tworzy podstawowy kanał używany przez przykładową aplikację do wysyłania powiadomień. Metoda SendNotification definiuje logikę specyficzną dla platformy wymaganą do utworzenia i wysłania powiadomienia. Metoda ReceiveNotification jest wywoływana przez system operacyjny Android po odebraniu komunikatu i wywołuje procedurę obsługi zdarzeń.

Metoda SendNotification tworzy powiadomienie lokalne natychmiast lub dokładnie DateTime. Powiadomienie można zaplanować dla dokładnego DateTimeAlarmManager użycia klasy, a powiadomienie zostanie odebrane przez obiekt pochodzący z BroadcastReceiver klasy:

[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);
        }
    }
}

Ważne

Domyślnie powiadomienia zaplanowane przy użyciu klasy nie przetrwają ponownego AlarmManager uruchomienia urządzenia. Można jednak zaprojektować aplikację tak, aby automatycznie zmieniała harmonogram powiadomień w przypadku ponownego uruchomienia urządzenia. Aby uzyskać więcej informacji, zobacz Uruchamianie alarmu po ponownym uruchomieniu urządzenia w harmonogramie powtarzających się alarmów na developer.android.com. Aby uzyskać informacje na temat przetwarzania w tle w systemie Android, zobacz Przewodnik po przetwarzaniu w tle w developer.android.com.

Aby uzyskać więcej informacji na temat odbiorników emisji, zobacz Odbiorniki emisji na platformie Xamarin.Android.

Obsługa powiadomień przychodzących w systemie Android

Klasa MainActivity musi wykrywać powiadomienia przychodzące i powiadamiać AndroidNotificationManager wystąpienie. Atrybut Activity klasy MainActivity powinien określać LaunchMode wartość LaunchMode.SingleTop:

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

Tryb SingleTop uniemożliwia uruchamianie wielu wystąpień elementu Activity , gdy aplikacja znajduje się na pierwszym planie. Może to LaunchMode nie być odpowiednie w przypadku aplikacji, które uruchamiają wiele działań w bardziej złożonych scenariuszach powiadomień. Aby uzyskać więcej informacji na temat LaunchMode wartości wyliczenia, zobacz Android Activity LaunchMode.

W klasie jest modyfikowana w MainActivity celu odbierania powiadomień przychodzących:

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);
    }
}

Metoda CreateNotificationFromIntent wyodrębnia dane powiadomienia z argumentu intent i dostarcza je do AndroidNotificationManager metody przy użyciu ReceiveNotification metody . Metoda CreateNotificationFromIntent jest wywoływana zarówno z metody, jak OnCreate i OnNewIntent metody:

  • Gdy aplikacja zostanie uruchomiona przez dane powiadomień, Intent dane zostaną przekazane do OnCreate metody .
  • Jeśli aplikacja znajduje się już na pierwszym planie, Intent dane zostaną przekazane do OnNewIntent metody .

System Android oferuje wiele zaawansowanych opcji powiadomień. Aby uzyskać więcej informacji, zobacz Notifications in Xamarin.Android (Powiadomienia na platformie Xamarin.Android).

Tworzenie implementacji interfejsu systemu iOS

Xamarin.Forms Aby aplikacja wysyłała i odbierała powiadomienia w systemie iOS, aplikacja musi zapewnić implementację .INotificationManager

Tworzenie klasy iOSNotificationManager

Klasa iOSNotificationManager implementuje INotificationManager interfejs:

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
            };
        }
    }
}

Atrybut assembly powyżej przestrzeni nazw rejestruje implementację interfejsu INotificationManager za pomocą elementu DependencyService.

W systemie iOS należy zażądać uprawnień do korzystania z powiadomień przed podjęciem próby zaplanowana powiadomienia. Metoda Initialize żąda autoryzacji do korzystania z powiadomień lokalnych. Metoda SendNotification definiuje logikę wymaganą do utworzenia i wysłania powiadomienia. Metoda ReceiveNotification zostanie wywołana przez system iOS po odebraniu komunikatu i wywołuje procedurę obsługi zdarzeń.

Uwaga

Metoda SendNotification tworzy powiadomienie lokalne natychmiast, przy użyciu UNTimeIntervalNotificationTrigger obiektu lub dokładnie DateTime przy użyciu UNCalendarNotificationTrigger obiektu.

Obsługa powiadomień przychodzących w systemie iOS

W systemie iOS należy utworzyć delegata, który podklasy UNUserNotificationCenterDelegate obsługuje komunikaty przychodzące. Przykładowa aplikacja definiuje klasę 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);
    }    
}

Ta klasa używa DependencyService klasy , aby uzyskać wystąpienie iOSNotificationManager klasy i dostarcza przychodzące dane powiadomień do ReceiveNotification metody .

Klasa AppDelegate musi określić iOSNotificationReceiver obiekt jako delegat podczas uruchamiania UNUserNotificationCenter aplikacji. Dzieje się tak w metodzie 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);
}

System iOS oferuje wiele zaawansowanych opcji powiadomień. Aby uzyskać więcej informacji, zobacz Powiadomienia w środowisku Xamarin.iOS.

Testowanie aplikacji

Gdy projekty platformy zawierają zarejestrowaną implementację interfejsu INotificationManager , aplikację można przetestować na obu platformach. Uruchom aplikację i kliknij jeden z przycisków Utwórz powiadomienie , aby utworzyć powiadomienie.

W systemie Android powiadomienia będą wyświetlane w obszarze powiadomień. Po naciśnięciu powiadomienia aplikacja odbiera powiadomienie i wyświetla komunikat:

Powiadomienia lokalne w systemie Android

W systemie iOS powiadomienia przychodzące są automatycznie odbierane przez aplikację bez konieczności wprowadzania danych przez użytkownika. Aplikacja odbiera powiadomienie i wyświetla komunikat:

Powiadomienia lokalne w systemie iOS