我可以让 Xamarin 应用程序进行后台刷新并向用户发送通知吗?

Can I make a Xamarin app do a background refresh and send a notification to the user?

我正在学习 Xamarin,我想实现一种方法,让应用程序大约每 20 分钟左右在后台发出一次 HttpClient 请求,并根据从请求中获得的数据向用户发送推送通知回复。我相当有信心这是可能的,但通过在互联网上的搜索,我还没有在 Xamarin.Forms.

中看到执行此操作的方法

这需要完成 platform-specific。 使用 android 调度请求会更简单一些,因为这可以通过后台服务 运行 周期性任务和广播接收器来实现。您可以在此处阅读更多相关信息 https://docs.microsoft.com/en-us/xamarin/android/app-fundamentals/services/ and https://docs.microsoft.com/en-us/xamarin/android/app-fundamentals/broadcast-receivers.

在共享项目中:

public class BackgroundJobManager 
{
    private int _currentId;

    private readonly List<BackgroundJob> _backgroundJobs = new List<BackgroundJob>();
    public IReadOnlyList<BackgroundJob> BackgroundJobs  => _backgroundJobs; 

    private int NextId() => Interlocked.Increment(ref _currentId);

    public int AddJob(Action action, TimeSpan delay, TimeSpan interval, string description)
    {
        var nextJobId = NextId();
        MessagingCenter.Subscribe<IAlarmReceiver>(this, nextJobId.ToString(), (sender) => action());
        _backgroundJobs.Add(new BackgroundJob() { DueTime = delay, Interval = interval, ID = nextJobId });
        return nextJobId;
    }

    public void RemoveJob(int jobId)
    {
        MessagingCenter.Unsubscribe<IAlarmReceiver>(this, jobId.ToString());
        _backgroundJobs.RemoveWhere(job => job.ID == jobId);
    }
} 

INotificationManager

public interface INotificationManager
{
    event EventHandler<NotificationEventArgs> NotificationReceived;
    void Initialize();
    void SendNotification(string title, string message);
    void ReceiveNotification(string title, string message);
}

NotificationEventArgs

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

IAlarmReceiver

public interface IAlarmReceiver
{
}

在您提出请求的 class 中:

private void SetUpBackgroundRequest()
{   
    Action startBackGroundJob = async () => await MakeYourRequestHere();
    _backgroundJobManager.AddJob(startBackGroundJob, TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(20), "Making request");
        
}

Android 在您的 MainActivity.cs 中添加以下内容:

private Intent _serviceIntent;
protected override void OnPause()
{
    base.OnPause();
    _serviceIntent = new Intent(this, typeof(PeriodicService));
    StartService(_serviceIntent);
}
protected override void OnResume()
{
    base.OnResume();
    if (_serviceIntent != null)
        StopService(_serviceIntent);
}

定期服务

[Service]
class PeriodicService : Service
{
    private static AlarmHandler alarm = new AlarmHandler();
    private readonly BackgroundJobManager _backgroundJobManager = new YourSharedProject.BackgroundJobManager();

    public override StartCommandResult OnStartCommand(Intent intent,
        StartCommandFlags flags, int startId)
    {
        foreach(var job in _backgroundJobManager.BackgroundJobs)
        {
            alarm.SetAlarm(job.DueTime, job.Interval, job.ID);
        }

        return StartCommandResult.Sticky;
    }

    public override bool StopService(Intent name)
    {
        foreach (var job in _backgroundJobManager.BackgroundJobs)
        {
            alarm.UnsetAlarm(job.ID);
        }
        return base.StopService(name);
    }

    public override void OnDestroy()
    {
        base.OnDestroy();
    }

    public override IBinder OnBind(Intent intent)
    {
        return null;
    }
}

AndroidNotificationManager

public class AndroidNotificationManager : YourSharedProject.NotificationManager
    {
        private const string ChannelId = "default";
        private const string ChannelName = "Default";
        private const string ChannelDescription = "The default channel for notifications.";

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

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

        private NotificationManager manager;

        public static AndroidNotificationManager Instance { get; private set; }

        public AndroidNotificationManager() => Initialize();

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

        public override void SendNotification(string title, string message)
        {
            if (!channelInitialized)
            {
                CreateNotificationChannel();
            }

            Show(title, message);

        }

        public void Show(string title, string message)
        {
            Interlocked.Increment(ref messageId);
            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.BigTextStyle textStyle = new NotificationCompat.BigTextStyle();

            NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, ChannelId)
                .SetContentIntent(pendingIntent)
                .SetContentTitle(title)
                .SetContentText(message)
                .SetStyle(textStyle)
                .SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.launchIcon))
                .SetSmallIcon(Resource.Drawable.launchIcon)
                .SetDefaults((int)NotificationDefaults.All)
                .SetAutoCancel(true);

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

        private void CreateNotificationChannel()
        {
            manager = (NotificationManager)AndroidApp.Context.GetSystemService(Context.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;
        }
    }

报警接收器

[BroadcastReceiver]
class AlarmReciver : BroadcastReceiver, IAlarmReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        var id = intent.GetStringExtra("jobId");
        MessagingCenter.Send<IAlarmReceiver>(this, id);
    }
}

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

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


    public void SetAlarm(TimeSpan dueTime, TimeSpan interval, int jobId)
    {
        var alarmIntent = new Intent(Application.Context, typeof(AlarmReciver));
        alarmIntent.PutExtra("jobId", jobId.ToString());
        var pending = PendingIntent.GetBroadcast(Application.Context,
            jobId, alarmIntent, PendingIntentFlags.UpdateCurrent);
        var alarmManager = Application.Context.GetSystemService(Context.AlarmService)
            .JavaCast<AlarmManager>();

        alarmManager.SetRepeating(AlarmType.RtcWakeup, (long)dueTime.TotalMilliseconds,
            (long)interval.TotalMilliseconds, pending);
    }

    public void UnsetAlarm(int jobId)
    {
        var alarmIntent = new Intent(Application.Context, typeof(AlarmReciver));
        var pending = PendingIntent.GetBroadcast(Application.Context,
            jobId, alarmIntent, PendingIntentFlags.UpdateCurrent);
        var alarmManager = Application.Context.GetSystemService(Context.AlarmService)
            .JavaCast<AlarmManager>();

        alarmManager.Cancel(pending);
    }
}

对于 iOS 它有点复杂。 IOS 提供用于刷新 non-critcal 内容的后台提取。无法选择间隔,因为 UIApplication.BackgroundFetchIntervalMinimum 取决于很多因素,例如应用程序使用情况、电池寿命等。您仍然希望使用 UIApplication.BackgroundFetchIntervalMinimum 来获取内容 尽可能多。 另一种方法是使用远程通知,使用 Apple 推送通知服务 (APN)。 此处的信息和示例 https://docs.microsoft.com/en-us/xamarin/ios/app-fundamentals/backgrounding/ios-backgrounding-techniques/updating-an-application-in-the-background.

两个平台的另一个选择是 Azure,如此处所述https://docs.microsoft.com/en-us/azure/developer/mobile-apps/notification-hubs-backend-service-xamarin-forms