Unity 本地通知推送的实现

1,716 阅读3分钟

前言

       我们玩过很多游戏都有通知推送功能,如android和ios平台的游戏都有此功能。此功能的用途可谓十分重要,例如玩家久不登录游戏,我们可以在适当的时间内提醒玩家登录,进而吸引玩家回流。或者游戏有更新时,也可以通知玩家更新,让玩家能及时玩到更新的内容,进而提升玩家对游戏的粘度。下面我们就来实现unity的本地通知推送功能。

一、集成Unity sdk

打开包管理器,搜索 Mobile notifycations 组件安装,如图:

安装完毕后,在Packages 目录下会出现相关文件夹,如图:

安卓需要配置通知图标,Ios 不用配置,如图:

二、定义基类

       由于我们要上ios 和 android 平台,如果只是利用组件直接写代码实现通知推送并不是十分明智的选择。更好的方案是对组件再包装一层,然后直接对第三方调用提供通用的接口,把平台的差异和复杂性在接口范围内解决掉。

首先,定义一个基类:

#if(NotifyLocal)
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Gamelogic
{

    public class NotifyInfo
    {
        public string Title;
        public string Text;
        public int Day;
        public int Hour;
        public int Minute;
        public int Second;
        public string SamllIcon = "smaicon";
        public string LargeIcon = "laricon";
        public bool Repeats = true;
    }

    public class NotifyInfos
    {
        public List<NotifyInfo> Notifys = new List<NotifyInfo>();
    }

    public class NotifyGenerater
    {

        public int NotifyNumber { get; set; } = 0;
        /// <summary>
        /// 获取下一次通知发送的时间
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        public DateTime GetNextTime(NotifyInfo info)
        {
            var now = DateTime.Now;
            var nextt = now + new TimeSpan(info.Day, info.Hour, info.Minute, info.Second);
            //23点后8点前不发通知
            if (nextt.Hour >= 23)
                nextt += new TimeSpan(9, 0, 0);
            if (nextt.Hour <= 8)
                nextt += new TimeSpan(8 - nextt.Hour, 0, 0);
            return nextt;
        }

        public virtual void Dispose() { }

        private void LogNotify(NotifyInfo info)
        {
            Debug.Log($"{nameof(NotifyInfo)}.{nameof(GenerateIntervalNotify)} {info.Day},{info.Hour},{info.Minute},{info.Second},{info.SamllIcon},{info.LargeIcon}");
            Debug.Log($"{nameof(NotifyInfo)}.{nameof(GenerateIntervalNotify)} {info.Title}");
            Debug.Log($"{nameof(NotifyInfo)}.{nameof(GenerateIntervalNotify)} {info.Text}");
        }

        /// <summary>
        /// 生成间隔通知,会在间隔一定时间后触发
        /// </summary>
        /// <param name="info"></param>
        public virtual void GenerateIntervalNotify(NotifyInfo info)
        {
            LogNotify(info);
        }

        /// <summary>
        /// 生成日历通知,会在指定的下个时间点触发
        /// </summary>
        /// <param name="info"></param>
        public virtual void GenerateCalendarNotify(NotifyInfo info)
        {
            LogNotify(info);
        }
        /// <summary>
        /// 清除已经显示的通知
        /// </summary>
        public virtual void ClearAllDisplayed()
        {

        }
    }

  
}
#endif

三、Android 本地通知推送实现

       我们定义了基类后,也就是定义了对外通用的接口方法,接下来就是针对特定的平台实现。我们先来实现安卓平台的推送,代码如下:

#if(NotifyLocal)
#if  UNITY_ANDROID
using System;
using System.Collections.Generic;
using Unity.Notifications.Android;

namespace Gamelogic
{
    /// <summary>
    /// 安桌本地通知发生器
    /// </summary>
    public class AndroidNotifyGenerater : NotifyGenerater
    {
        private string _channelId = "bubbleId";
        public AndroidNotifyGenerater()
        {
            ResetNotify();           
        }
 
        /// <summary>
        /// 重设通知
        /// </summary>
        private void ResetNotify()
        {
            //清空计数、清空所有已注册的通知和channel
            NotifyNumber = 0;
            AndroidNotificationCenter.CancelAllNotifications();
            AndroidNotificationCenter.DeleteNotificationChannel(_channelId);

            //重新注册channel
            var channel = new AndroidNotificationChannel();
            channel.Id = _channelId;
            channel.Name = "bubbleName";
            channel.Importance = Importance.High;
            channel.Description = "Generic notifications";
            channel.CanShowBadge = true;
            channel.EnableLights = true;
            channel.LockScreenVisibility = LockScreenVisibility.Public;
            AndroidNotificationCenter.RegisterNotificationChannel(channel);            
        }


        public override void GenerateIntervalNotify(NotifyInfo info)
        {
            base.GenerateIntervalNotify(info);
            var nextt = GetNextTime(info);
            var notify = new AndroidNotification();
            notify.Title = info.Title;
            notify.Text = info.Text;
            notify.FireTime = nextt;
            notify.SmallIcon = info.SamllIcon;
            notify.LargeIcon = info.LargeIcon;
            notify.Number = NotifyNumber++;
            notify.ShouldAutoCancel = true;
            notify.RepeatInterval = TimeSpan.FromHours(1);
           
            AndroidNotificationCenter.SendNotification(notify, _channelId);
        }

        public override void GenerateCalendarNotify(NotifyInfo info)
        {
            base.GenerateCalendarNotify(info);
        }

        public override void ClearAllDisplayed()
        {
            base.ClearAllDisplayed();
            AndroidNotificationCenter.CancelAllDisplayedNotifications();
        }
    }
}
#endif
#endif

四、Ios 本地通知推送实现

代码如下:

#if (NotifyLocal)
#if !UNITY_IOS
using System;
using System.Collections.Generic;
using Unity.Notifications.iOS;

namespace Gamelogic
{
    /// <summary>
    /// Ios通知触发器
    /// </summary>
    public class IosNotifyGenerater : NotifyGenerater
    {      
        public IosNotifyGenerater()
        {
            ResetNotify();           
        }

        private void ResetNotify()
        {
            //清0,清空已显示的和已计划的
            NotifyNumber = 0;
            iOSNotificationCenter.ApplicationBadge = 0;
            iOSNotificationCenter.RemoveAllDeliveredNotifications();
            iOSNotificationCenter.RemoveAllScheduledNotifications();
        }

        public override void GenerateIntervalNotify(NotifyInfo info)
        {
            base.GenerateIntervalNotify(info);
            var tt = new iOSNotificationTimeIntervalTrigger();//间隔通知触发器
            tt.TimeInterval = new TimeSpan(info.Day, info.Hour, info.Minute, info.Second);
            tt.Repeats = info.Repeats;

            var notify = new iOSNotification();
            notify.Title = info.Title;
            notify.Body = info.Text;
            notify.Badge = NotifyNumber++;
            notify.ShowInForeground = true;
            notify.ForegroundPresentationOption = (PresentationOption.Alert | PresentationOption.Sound | PresentationOption.Badge);
            notify.CategoryIdentifier = "category_a";
            notify.ThreadIdentifier = "thread1";
            notify.Trigger = tt;
        }

        public override void GenerateCalendarNotify(NotifyInfo info)
        {
            base.GenerateCalendarNotify(info);
            //所有字段都是可选的,但您需要设置至少一个字段才能使触发器起作用。
            //例如,如果您只设置小时和分钟字段,系统会在下一个指定的小时和分钟自动触发通知。
            var tt = new iOSNotificationCalendarTrigger();
            if(info.Day > 0)
                tt.Day = info.Day; 
            if(info.Hour > 0)
                tt.Hour = info.Hour;
            if(info.Minute > 0)
                tt.Minute = info.Minute;
            if (info.Second > 0)
                tt.Second = info.Second;

            tt.Repeats = info.Repeats;
                     
            var notify = new iOSNotification();
            notify.Title = info.Title;
            notify.Body = info.Text;
            notify.Badge  = NotifyNumber++;
            notify.ShowInForeground = true;
            notify.ForegroundPresentationOption = (PresentationOption.Alert | PresentationOption.Sound | PresentationOption.Badge);
            notify.CategoryIdentifier = "category_a";
            notify.ThreadIdentifier = "thread1";
            notify.Trigger = tt;

            iOSNotificationCenter.ScheduleNotification(notify);
        }
        public override void ClearAllDisplayed()
        {
            base.ClearAllDisplayed();
            iOSNotificationCenter.RemoveAllDeliveredNotifications();
        }
    }
}
#endif
#endif

五、通知管理器实现

       至此,我们已经针对平台实现了本地通知推送功能,但对于接口使用者来说,什么时候什么情况下该使用什么平台的接口,使用者还需要进行各种条件的判断,显而对使用者友好度不高。所以,接下来我们还需要定义一个通知管理器,把这些逻辑差异性在内部处理掉。

代码如下:

#if(NotifyLocal)
using System;
using System.Collections;
using System.Collections.Generic;
#if UNITY_ANDROID
using Unity.Notifications.Android;
#elif UNITY_IOS
using Unity.Notifications.iOS;
#endif
using UnityEngine;
namespace Gamelogic
{

    public class NotifyMono : MonoBehaviour
    {        
        private void OnApplicationFocus(bool focus)
        {
            if(focus)
            {
                NotifyMgr.Notifyer.ClearAllDisplayed();
            }
        }
    }


    public class NotifyMgr
    {

        public static NotifyGenerater Notifyer;
        private static GameObject _notifyGo;
        private static NotifyMono _notifyMono;
        private static bool _init = false;

        public static void Init()
        {
            if (_init)
                return;

            if(_notifyMono == null)
            {
                _notifyGo = new GameObject("NotifyMono");
                _notifyGo.transform.position = Vector3.zero;
                _notifyMono = _notifyGo.AddComponent<NotifyMono>();
                GameObject.DontDestroyOnLoad(_notifyGo);
            }
            _notifyMono.StartCoroutine(RequestNotifyPermission());            
        }

        static IEnumerator RequestNotifyPermission()
        {
#if UNITY_ANDROID
            var request = new PermissionRequest();
            while (request.Status == PermissionStatus.RequestPending)
                yield return null;

            Notifyer = new AndroidNotifyGenerater();
            _init = true;

#elif UNITY_IOS
             var aho = AuthorizationOption.Alert | AuthorizationOption.Badge;
            using (var req = new AuthorizationRequest(aho, true))
            {
                while (!req.IsFinished)
                {
                    yield return null;
                };

                string res = " RequestAuthorization:";
                res += " finished: " + req.IsFinished;
                res += " granted :  " + req.Granted;
                res += " error:  " + req.Error;
                res += " deviceToken:  " + req.DeviceToken;
                Debug.Log($"==============={res}");

                Notifyer = new IosNotifyGenerater();
                _init = true;
            }
#endif
        }
      

        public static void GenerateIntervalNotify(NotifyInfo info)
        {
            if (Notifyer != null)
                Notifyer.GenerateIntervalNotify(info);
        }

        public static void GenerateCalendarNotify(NotifyInfo info)
        {
            if (Notifyer != null)
                Notifyer.GenerateCalendarNotify(info);
        }
    }
}
#endif

      通过代码我们可以看到,使用者只要打开 [NotifyLocal] 宏,就可以把本地通知推送功编译进游戏里,否则就没有推送功能,就像没有添加任何东西一样。如需要间隔时间触发通知,就只要调用[GenerateIntervalNotify]方法; 如需要重复指定时间点触发通知,就只要调用[GenerateCalendarNotify]方法,其它的平台差异性和复杂性都很好的屏蔽在方法内了。

六、使用方法

#if (NotifyLocal)
            NotifyMgr.Init();
            var ninfo = new NotifyInfo();
            ninfo.Day = 0;
            ninfo.Hour = 1;
            ninfo.Minute = 0;
            ninfo.Second = 0;
            ninfo.Title = "This is my game";
            ninfo.Text = "Welcome to my game, hople play happy!";
            NotifyMgr.GenerateIntervalNotify(ninfo);
#endif

如上面代码,打开游戏后,每隔一小时会触发一次通知推送,即使把游戏关闭也仍然有效。

七、效果测试

感谢阅读。