Unity3D游戏中Android本地推送通知整理

现在手游中出现了越多的定时通知的需求,因为是固定时间推送,所以不需要通过服务器进行推送,只需要利用各个平台自己的本地推送机制就可以实现这个功能。
在Unity3D官方提供的API中,UnityEngine.iOS中有一个类:LocalNotification
它只实现了iOS的本地推送,即仅适用于iphone/ipad/ipod Touch.
Unity3D API链接:https://docs.unity3d.com/ScriptReference/iOS.LocalNotification.html
之前看过一篇文章,有关iOS本地推送的文章,可以看看。
iOS本地推送通知链接:http://www.xuanyusong.com/archives/2632

对于Android端,Unity3D并没有提供现成的接口,所以使用GitHub上的第三方库中实现的接口
GitHub地址:https://github.com/Agasper/unity-android-notifications
我们将这个第三方库clone到本地的一个自建文件夹,打开后有两个文件夹:branches和trunk,使用Unity3D打开trunk中的UnityProject。然后打包,在手机上进行测试,可以实现本地推送。
下面看看其中的接口方法:

    public static int SendNotification(TimeSpan delay, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        int id = new System.Random().Next();
        return SendNotification(id, (long)delay.TotalSeconds * 1000, title, message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendNotification(int id, TimeSpan delay, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        return SendNotification(id, (long)delay.TotalSeconds * 1000, title, message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendNotification(int id, long delayMs, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass pluginClass = new AndroidJavaClass(fullClassName);
        if (pluginClass != null)
        {
            pluginClass.CallStatic("SetNotification", id, delayMs, title, message, message,
                sound ? 1 : 0, soundName, vibrate ? 1 : 0, lights ? 1 : 0, bigIcon, "notify_icon_small",
                ToInt(bgColor), bundleIdentifier, channel, PopulateActions(actions));
        }
        return id;
#elif UNITY_IOS && !UNITY_EDITOR
        iOSNotification notification = new iOSNotification();
        notification.identifier = id;
        notification.message = message;
        notification.delay = ((double) delayMs) / 1000.0;
        notification.repeat = 0;
        notification.category = channel;
        notification.sound = sound;
        notification.soundName = soundName;
        SetActions(ref notification, actions);
        scheduleNotification(ref notification);
        return id;
#else
        return 0;
#endif
    }

我们来看一下这三个重载方法:SendNotification,前两个是对第三个的封装,区别就只是参数:

int id //notification的编号
TimeSpan delay //延迟时间(5000,就是5秒后触发)
string title //推送内容标题
string message //推送内容
Color32 bgColor //Unity提供的API,以32位格式表示RGBA颜色
bool sound = true //声音
bool vibrate = true //颤动
bool lights = true //闪亮
string bigIcon = “” //缺省值
String soundName = null //声音文件名
string channel = “default” //都同意给false,表示本地推送(我理解的)
params Action[] actions //params表示函数的参数是可变个数的。一个table

我们来看项目中给到的测试代码(NotificationTest.cs):

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class NotificationTest : MonoBehaviour
{
    public InputField m_input;

    void Awake()
    {
        LocalNotification.ClearNotifications();
    }

    public void OneTime()
    {
        long delay = long.Parse(m_input.text);   //string转换为long
        LocalNotification.SendNotification(1, delay, "Title", "Long message text", new Color32(0xff, 0x44, 0x44, 255));
    }

    public void Time()   //我自己加的,使用InputField输入时间戳,然后在指定时间推送
    {
        long delay = long.Parse(m_input.text);
        LocalNotification.notification(1, delay, "Title", "Long message text with big icon", new Color32(0xff, 0x44, 0x44, 255), true, true, true, "app_icon");
        m_input.text = "hello";
    }

    public void OneTimeBigIcon()
    {
        LocalNotification.SendNotification(1, 5000, "Title", "Long message text with big icon", new Color32(0xff, 0x44, 0x44, 255), true, true, true, "app_icon");
    }

    public void OneTimeWithActions()
    {
        LocalNotification.Action action1 = new LocalNotification.Action("background", "In Background", this);
        action1.Foreground = false;
        LocalNotification.Action action2 = new LocalNotification.Action("foreground", "In Foreground", this);
        LocalNotification.SendNotification(1, 5000, "Title", "Long message text with actions", new Color32(0xff, 0x44, 0x44, 255), true, true, true, null, "boing", "default", action1, action2);
    }

    public void Repeating()
    {
        LocalNotification.SendRepeatingNotification(1, 5000, 60000, "Title", "Long message text", new Color32(0xff, 0x44, 0x44, 255));
    }

    public void Stop()
    {
        LocalNotification.CancelNotification(1);
    }

    public void OnAction(string identifier)
    {
        Debug.Log("Got action " + identifier);
    }
}

LocalNotification.cs

using System;
using System.IO;
using UnityEngine;
#if UNITY_IOS
using System.Runtime.InteropServices;
#endif
using System.Collections.Generic;

public class LocalNotification
{
#if UNITY_ANDROID && !UNITY_EDITOR
    private static string fullClassName = "net.agasper.unitynotification.UnityNotificationManager";
    private static string actionClassName = "net.agasper.unitynotification.NotificationAction";
#endif

#if UNITY_5_6_OR_NEWER
    private static string bundleIdentifier { get { return Application.identifier; } }
#else
    private static string bundleIdentifier { get { return Application.bundleIdentifier; } }
#endif

    public static void notification(int id, long time, string title, string message,Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));  //当地时区
        DateTime dt = startTime.AddMilliseconds(time);
        TimeSpan Timespan = dt - DateTime.Now;
        long delay = Timespan.Days * 24 * 3600 * 1000 + Timespan.Hours * 3600 * 1000 + Timespan.Minutes * 60 * 1000 + Timespan.Seconds * 1000 + Timespan.Milliseconds;
        SendNotification(id, delay, title,message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendNotification(TimeSpan delay, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        int id = new System.Random().Next();
        return SendNotification(id, (long)delay.TotalSeconds * 1000, title, message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendNotification(int id, TimeSpan delay, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        return SendNotification(id, (long)delay.TotalSeconds * 1000, title, message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendNotification(int id, long delayMs, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass pluginClass = new AndroidJavaClass(fullClassName);
        if (pluginClass != null)
        {
            pluginClass.CallStatic("SetNotification", id, delayMs, title, message, message,
                sound ? 1 : 0, soundName, vibrate ? 1 : 0, lights ? 1 : 0, bigIcon, "notify_icon_small",
                ToInt(bgColor), bundleIdentifier, channel, PopulateActions(actions));
        }
        return id;
#elif UNITY_IOS && !UNITY_EDITOR
        iOSNotification notification = new iOSNotification();
        notification.identifier = id;
        notification.message = message;
        notification.delay = ((double) delayMs) / 1000.0;
        notification.repeat = 0;
        notification.category = channel;
        notification.sound = sound;
        notification.soundName = soundName;
        SetActions(ref notification, actions);
        scheduleNotification(ref notification);
        return id;
#else
        return 0;
#endif
    }

    public static int SendRepeatingNotification(TimeSpan delay, TimeSpan timeout, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        int id = new System.Random().Next();
        return SendRepeatingNotification(id, (long)delay.TotalSeconds * 1000, (int)timeout.TotalSeconds, title, message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendRepeatingNotification(int id, TimeSpan delay, TimeSpan timeout, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
        return SendRepeatingNotification(id, (long)delay.TotalSeconds * 1000, (int)timeout.TotalSeconds, title, message, bgColor, sound, vibrate, lights, bigIcon, soundName, channel, actions);
    }

    public static int SendRepeatingNotification(int id, long delayMs, long timeoutMs, string title, string message, Color32 bgColor, bool sound = true, bool vibrate = true, bool lights = true, string bigIcon = "", String soundName = null, string channel = "default", params Action[] actions)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass pluginClass = new AndroidJavaClass(fullClassName);
        if (pluginClass != null)
        {
            pluginClass.CallStatic("SetRepeatingNotification", id, delayMs, title, message, message, timeoutMs,
                sound ? 1 : 0, soundName, vibrate ? 1 : 0, lights ? 1 : 0, bigIcon, "notify_icon_small",
                ToInt(bgColor), bundleIdentifier, channel, PopulateActions(actions));
        }
        return id;
#elif UNITY_IOS && !UNITY_EDITOR
        iOSNotification notification = new iOSNotification();
        notification.identifier = id;
        notification.message = message;
        notification.delay = ((double) delayMs) / 1000.0;
        notification.repeat = CalculateiOSRepeat(timeoutMs);
        notification.category = channel;
        notification.sound = sound;
        notification.soundName = soundName;
        SetActions(ref notification, actions);
        scheduleNotification(ref notification);
        return id;
#else
        return 0;
#endif
    }

    public static void CancelNotification(int id)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass pluginClass = new AndroidJavaClass(fullClassName);
        if (pluginClass != null) {
            pluginClass.CallStatic("CancelPendingNotification", id);
        }
#elif UNITY_IOS && !UNITY_EDITOR
        cancelNotification(id);
#endif
    }

    public static void ClearNotifications()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass pluginClass = new AndroidJavaClass(fullClassName);
        if (pluginClass != null) {
            pluginClass.CallStatic("ClearShowingNotifications");
        }
#elif UNITY_IOS && !UNITY_EDITOR
        cancelAllNotifications();
#endif
    }

    /// This allows you to create a custom channel for different kinds of notifications.
    /// Channels are required on Android Oreo and above. If you don't call this method, a channel will be created for you with the configuration you give to SendNotification.
    public static void CreateChannel(string identifier, string name, string description, Color32 lightColor, bool enableLights = true, string soundName = null, Importance importance = Importance.Default, bool vibrate = true, long[] vibrationPattern = null)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        AndroidJavaClass pluginClass = new AndroidJavaClass(fullClassName);
        if (pluginClass != null)
        {
            pluginClass.CallStatic("CreateChannel", identifier, name, description, (int) importance, soundName, enableLights ? 1 : 0, ToInt(lightColor), vibrate ? 1 : 0, vibrationPattern, bundleIdentifier);
        }
#endif
    }

    public enum Importance
    {
        /// Default notification importance: shows everywhere, makes noise, but does not visually intrude.
        Default = 3,

        /// Higher notification importance: shows everywhere, makes noise and peeks. May use full screen intents.
        High = 4,

        /// Low notification importance: shows everywhere, but is not intrusive.
        Low = 2,

        /// Unused.
        Max = 5,

        /// Min notification importance: only shows in the shade, below the fold. This should not be used with Service.startForeground since a foreground service is supposed to be something the user cares about so it does not make semantic sense to mark its notification as minimum importance. If you do this as of Android version O, the system will show a higher-priority notification about your app running in the background.
        Min = 1,

        /// A notification with no importance: does not show in the shade.
        None = 0
    }

    public class Action
    {
        public Action(string identifier, string title, MonoBehaviour handler)
        {
            this.Identifier = identifier;
            this.Title = title;
            if (handler != null)
            {
                this.GameObject = handler.gameObject.name;
                this.HandlerMethod = "OnAction";
            }
        }

        public string Identifier;
        public string Title;
        public string Icon;
        public bool Foreground = true;
        public string GameObject;
        public string HandlerMethod;
    }

    private static int ToInt(Color32 color)
    {
        return color.r * 65536 + color.g * 256 + color.b;
    }

#if UNITY_ANDROID && !UNITY_EDITOR
    private static AndroidJavaObject PopulateActions(Action[] actions)
    {
        AndroidJavaObject actionList = null;
        if (actions.Length > 0)
        {
            actionList = new AndroidJavaObject("java.util.ArrayList");
            for (int i = 0; i < actions.Length; i++)
            {
                var action = actions[i];
                using (AndroidJavaObject notificationObject = new AndroidJavaObject(actionClassName))
                {
                    notificationObject.Call("setIdentifier", action.Identifier);
                    notificationObject.Call("setTitle", action.Title);
                    notificationObject.Call("setIcon", action.Icon);
                    notificationObject.Call("setForeground", action.Foreground);
                    notificationObject.Call("setGameObject", action.GameObject);
                    notificationObject.Call("setHandlerMethod", action.HandlerMethod);
                    actionList.Call<bool>("add", notificationObject);
                }
            }
        }
        return actionList;
    }
#endif

#if UNITY_IOS && !UNITY_EDITOR
    internal struct iOSNotification {
        public int identifier;
        public string message;
        public double delay;
        public UnityEngine.iOS.CalendarUnit repeat;
        public string category;
        public bool sound;
        public string soundName;
        public int actionCount;
        public iOSNotificationAction action1;
        public iOSNotificationAction action2;
        public iOSNotificationAction action3;
        public iOSNotificationAction action4;
    }

    internal struct iOSNotificationAction {
        public string identifier;
        public string title;
        public bool foreground;
        public string gameObject;
        public string handlerMethod;
    }

    [DllImport ("__Internal")] internal static extern void scheduleNotification(ref iOSNotification notification);
    [DllImport ("__Internal")] internal static extern void cancelNotification(int identifier);
    [DllImport ("__Internal")] internal static extern void cancelAllNotifications();

    internal static void SetActions(ref iOSNotification notification, Action[] actions)
    {
        notification.actionCount = actions.Length;
        if (actions.Length > 0)
            notification.action1 = CreateAction(actions[0]);
        if (actions.Length > 1)
            notification.action2 = CreateAction(actions[1]);
        if (actions.Length > 2)
            notification.action3 = CreateAction(actions[2]);
        if (actions.Length > 3)
            notification.action4 = CreateAction(actions[3]);
    }

    internal static iOSNotificationAction CreateAction(Action from)
    {
        iOSNotificationAction action;
        action.identifier = from.Identifier;
        action.title = from.Title;
        action.foreground = from.Foreground;
        action.gameObject = from.GameObject;
        action.handlerMethod = from.HandlerMethod;
        return action;
    }

    public static UnityEngine.iOS.CalendarUnit CalculateiOSRepeat(long timeoutMS)
    {
        if (timeoutMS == 0)
            return 0;

        long timeoutMinutes = timeoutMS / (1000 * 60);
        if (timeoutMinutes == 1)
            return UnityEngine.iOS.CalendarUnit.Minute;
        if (timeoutMinutes == 60)
            return UnityEngine.iOS.CalendarUnit.Hour;
        if (timeoutMinutes == 60 * 24)
            return UnityEngine.iOS.CalendarUnit.Day;
        if (timeoutMinutes >= 60 * 24 * 2 && timeoutMinutes <= 60 * 24 * 5)
            return UnityEngine.iOS.CalendarUnit.Weekday;
        if (timeoutMinutes == 60 * 24 * 7)
            return UnityEngine.iOS.CalendarUnit.Week;
        if (timeoutMinutes >= 60 * 24 * 28 && timeoutMinutes <= 60 * 24 * 31)
            return UnityEngine.iOS.CalendarUnit.Month;
        if (timeoutMinutes >= 60 * 24 * 91 && timeoutMinutes <= 60 * 24 * 92)
            return UnityEngine.iOS.CalendarUnit.Quarter;
        if (timeoutMinutes >= 60 * 24 * 365 && timeoutMinutes <= 60 * 24 * 366)
            return UnityEngine.iOS.CalendarUnit.Year;
        throw new ArgumentException("Unsupported timeout for iOS - must equal a minute, hour, day, 2-5 days (for 'weekday'), week, month, quarter or year but was " + timeoutMS);
    }
#endif
}

附加一个测试代码:

using UnityEngine;
using System.Collections;

#if UNITY_IPHONE
using NotificationServices = UnityEngine.iOS.NotificationServices;
using NotificationType = UnityEngine.iOS.NotificationType;
#endif

public class NewBehaviourScript : MonoBehaviour {
    //本地推送
    public static void NotificationMessage(string message,int hour ,bool isRepeatDay)
    {
        int year = System.DateTime.Now.Year;
        int month = System.DateTime.Now.Month;
        int day= System.DateTime.Now.Day;
        System.DateTime newDate = new System.DateTime(year,month,day,hour,0,0);
        NotificationMessage(message,newDate,isRepeatDay);
    }
    //本地推送 你可以传入一个固定的推送时间
    public static void NotificationMessage(string message,System.DateTime newDate,bool isRepeatDay)
    {
        #if UNITY_IPHONE
        //推送时间需要大于当前时间
        if(newDate > System.DateTime.Now)
        {
            UnityEngine.iOS.LocalNotification localNotification = new UnityEngine.iOS.LocalNotification();
            localNotification.fireDate =newDate;    
            localNotification.alertBody = message;
            localNotification.applicationIconBadgeNumber = 1;
            localNotification.hasAction = true;
            localNotification.alertAction = "这是notificationtest的标题";
            if(isRepeatDay)
            {
                //是否每天定期循环
                localNotification.repeatCalendar = UnityEngine.iOS.CalendarIdentifier.ChineseCalendar;
                localNotification.repeatInterval = UnityEngine.iOS.CalendarUnit.Day;
            }
            localNotification.soundName = UnityEngine.iOS.LocalNotification.defaultSoundName;
            UnityEngine.iOS.NotificationServices.ScheduleLocalNotification(localNotification);
        }
        #endif
        #if UNITY_ANDROID
        if (newDate > System.DateTime.Now) 
        {
            LocalNotification.SendNotification(1,10,"这是notificationtest的标题","这是notificationtest的消息",new Color32(0xff, 0x44, 0x44, 255));
            if (System.DateTime.Now.Hour >= 12) {
                //System.DateTime dataTimeNextNotify = new System.DateTime(
                long delay = 24 * 60 * 60 - ((System.DateTime.Now.Hour - 12)* 60 * 60 + System.DateTime.Now.Minute * 60 + System.DateTime.Now.Second);
                LocalNotification.SendRepeatingNotification(2,delay, 24 * 60 * 60,"这是notificationtest的标题","每天中午12点推送",new Color32(0xff, 0x44, 0x44, 255));
            }
            else 
            {
                long delay = (12 - System.DateTime.Now.Hour)* 60 * 60 - System.DateTime.Now.Minute * 60 - System.DateTime.Now.Second;
                LocalNotification.SendRepeatingNotification(2,delay,24 * 60 * 60 ,"这是notificationtest的标题","每天中午12点推送",new Color32(0xff, 0x44, 0x44, 255));  
            }
        }
        #endif
    }

    void Awake()
    {
        #if UNITY_IPHONE
        UnityEngine.iOS.NotificationServices.RegisterForNotifications (
            NotificationType.Alert |
            NotificationType.Badge |
            NotificationType.Sound);
        #endif
        //第一次进入游戏的时候清空,有可能用户自己把游戏冲后台杀死,这里强制清空
        CleanNotification();
    }

    void OnApplicationPause(bool paused)
    {
        //程序进入后台时
        if(paused)
        {
            //10秒后发送
            NotificationMessage("这是notificationtest的推送正文信息",System.DateTime.Now.AddSeconds(10),false);
            //每天中午12点推送
            NotificationMessage("每天中午12点推送",12,true);
        }
        else
        {
            //程序从后台进入前台时
            CleanNotification();
        }
    }

    //清空所有本地消息
    void CleanNotification()
    {
        #if UNITY_IPHONE
        UnityEngine.iOS.LocalNotification l = new UnityEngine.iOS.LocalNotification (); 
        l.applicationIconBadgeNumber = -1; 
        UnityEngine.iOS.NotificationServices.PresentLocalNotificationNow (l); 
        UnityEngine.iOS.NotificationServices.CancelAllLocalNotifications (); 
        UnityEngine.iOS.NotificationServices.ClearLocalNotifications (); 
        #endif
        #if UNITY_ANDROID
        LocalNotification.CancelNotification(1);
        LocalNotification.CancelNotification(2);
        #endif
    }
}

上面这个示例中,用UNITY_IPHONE包围起来的是iOS平台相关代码,用UNITY_ANDROID包围起来的是Android平台相关代码。除了以下代码外,推送还需要注意以下几点:

Android推送需要在manifest文件中加入一些权限,具体请参考Android第三方库中示例工程。
推送的时间点应该以服务器为主,当游戏采取全球发行时,就需要谨慎处理时区问题。

你可能感兴趣的:(游戏开发)