sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Unity ローカルプッシュ通知のやり方 (Android)

f:id:shikaku_sh:20200910152454p:plain

この記事は Unity で(ローカル)プッシュ通知をするやり方をメモした記事です。

ローカルプッシュ通知とは、端末に通知を送る際にサーバーを使用しません。端末の中に通知予定を入れて、あとから自動的に通知を発生させることが可能です。

プッシュ通知の雰囲気は以下の Android 端末の画像でわかるかと思います。

f:id:shikaku_sh:20200916104208j:plain:h300
こんな感じで、できました

よくあるのは、おつかいを設定して 30 分後になったら、おつかい完了の通知を発生させるようなやつです。

注意:この記事は Android 向けにテストしています。iOS も変わらないと思いますが、テストしていません。


Unity Mobile Notifications Package をインストールする

メニューの「Package Manager」から「Mobile Notifications」をインストールします。

f:id:shikaku_sh:20200916104329p:plain:w600

  • Window > Package Manager > (Menu) Mobile Notifications


プロジェクト設定

Android の通知に表示されるアイコンを設定します。

f:id:shikaku_sh:20200916105010p:plain:w600

  • Edit > Project Settings > (Menu) Mobile Notification Settings

用意するアイコンのサイズは、次のふたつをそれぞれ Type の icon_small と icon_large に設定します。

  • 48x48
  • 192x192

f:id:shikaku_sh:20200916104830p:plainf:id:shikaku_sh:20200916104833p:plain
サンプルで使った画像

48x48 の画像は透明部分をくりぬいた白い画像が表示されました。

設定した画像の identifier はデフォルトだと「icon_0」や「icon_1」になっていますが、このテキストの値はあとで使用するので、きちんと識別子となる名前(テキスト)を設定したほうがよいと思います。

番号をつけても、これは(正確には)連続性がないので、「Unity 開発に関する 50 の Tips 〜ベストプラクティス〜(2016 Edition)」だと Tips 68 のようになっています。やっぱり、推奨とはいかないです。

Android のアイコンアセットには公式が挙げている命名規則がありますので、「アイコン デザイン ガイドライン」をここでは利用することにします。


アイコン アセット共通の命名規約の例

表にすると、こんな感じかと。

アセットタイプ 接頭辞
アイコン ic_ ic_star.png
ランチャー アイコン ic_launcher ic_launcher_calendar.png
メニュー アイコン、アクションバー アイコン ic_menu ic_menu_archive.png
ステータスバー アイコン ic_stat_notify ic_stat_notify_msg.png
タブアイコン ic_tab ic_tab_recent.png
ダイアログ アイコン ic_dialog ic_dialog_info.png

なので、ここでは次のテキストとします。

  • ic_stat_notify_small
  • ic_stat_notify_large

なお、アイコンに使う画像はテクスチャーとして利用しないので「Texture Type」を「Editor GUI and Legacy GUI」にして最適化を防ぐような設定にしました。

f:id:shikaku_sh:20200916104405p:plain


スプリプトの実装

LocalNotification を機能を実行するためのインターフェースを用意しました。この機能は AndroidiOS で別々に実装したほうがよいので、インターフェースを挟んでおいたほうが後々便利です。

using System;

public interface ILocalNotification
{
    /// <summary>
    /// Small Icon タイプのアイコン ID を表すテキストを取得または設定します。
    /// </summary>
    string SmallIcon { get; set; }

    /// <summary>
    /// Large Icon タイプのアイコン ID を表すテキストを取得または設定します。
    /// </summary>
    string LargeIcon { get; set; }

    void CreateChannel(string cannelId, string cannelName, string description);
    void CancelAll();

    int Schedule(string title, string text, DateTime fireTime);
    int ScheduleInSeconds(string title, string text, int seconds);
    int ScheduleInMinutes(string title, string text, int minutes);
    int ScheduleInHours(string title, string text, int hours);
}

Android のコードです。ここがこの記事で一番コアな部分です。

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Notifications.Android;
using UnityEngine;

public class LocalNotificationForAndroid : ILocalNotification
{
    public string ChannelId { get; set; } = "channel-id";
    public string SmallIcon { get; set; } = "icon_0";
    public string LargeIcon { get; set; } = "icon_1";

    public void CreateChannel(string cannelId, string cannelName, string description)
    {
        ChannelId = cannelId;

        var cannel = new AndroidNotificationChannel
        {
            Id = ChannelId,
            Name = cannelName,
            Importance = Importance.High,
            Description = description,
        };

        if (SmallIcon == "icon_0")
        {
            Debug.LogWarning($"{nameof(SmallIcon)} プロパティの値が初期値のままです。アイコンを正しく表示できない恐れがあります。");
        }

        if (LargeIcon == "icon_1")
        {
            Debug.LogWarning($"{nameof(LargeIcon)} プロパティの値が初期値のままです。アイコンを正しく表示できない恐れがあります。");
        }

        AndroidNotificationCenter.RegisterNotificationChannel(cannel);
    }

    public void CancelAll()
    {
        AndroidNotificationCenter.CancelAllScheduledNotifications();
        AndroidNotificationCenter.CancelAllNotifications();
    }

    public int Schedule(string title, string text, DateTime fireTime)
    {
        var notification = new AndroidNotification
        {
            Title = title,
            Text = text,
            FireTime = fireTime,
        };

        notification.SmallIcon = SmallIcon;
        notification.LargeIcon = LargeIcon;

        return AndroidNotificationCenter.SendNotification(notification, ChannelId);
    }

    public int ScheduleInSeconds(string title, string text, int seconds)
        => Schedule(title, text, DateTime.Now.AddSeconds(seconds));
    public int ScheduleInMinutes(string title, string text, int minutes)
        => Schedule(title, text, DateTime.Now.AddMinutes(minutes));
    public int ScheduleInHours(string title, string text, int hours)
        => Schedule(title, text, DateTime.Now.AddHours(hours));
}

このクラスを使って、通知をやってみることにします。注意する点として、登録・通知をしたクラス(ここだと LocalNotificationForAndroid)のインスタンスを保持しておかないと、正しく端末に通知が発生しませんでした。なので、このクラスを singleton にするか singleton クラスのプロパティとして持つようにしてみます。


singleton クラスで保持する例

簡単な singleton パターンを適用したクラスに通知用のクラスを用意しました。ポイントは、アイコンのプロパティに設定しているテキスト値が、「アイコン アセット共通の命名規約の例」で決めた2つのテキストです。

このテキストが、通知されるアイコンと紐づいてしまいます。(テキスト以外の方法で設定をやりたいけど、仕様上やりづらい+ここだけ設定すれば変更することはもう無いので、こうしました)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public sealed class NotificationManager
{
    public static NotificationManager Instance { get; } = new NotificationManager();

    public ILocalNotification Notification { get; private set; }

    private NotificationManager() 
    {

    }

    public void Initialize()
    {
        ILocalNotification notification = new LocalNotificationForAndroid();

        notification.SmallIcon = "ic_stat_notify_small";
        notification.LargeIcon = "ic_stat_notify_large";

        notification.CreateChannel("sample0916", "sampleName", "sampleDescription");

        Notification = notification;
    }
}

ここで突然 ChannelID、CannelName、Description をサンプル的な値で埋めていますが、別にこのコードでも Android で動作するのを確認しています。Android の通知仕様を確認するとわかりやすいかもしれません。(おそらくこれをアダプトしてるのだと思います)

Channel ID は、チャンネル登録の関数と、スケジュール登録の関数で同じ値が利用されています。アイコンのように Unity プロジェクト内と紐づくものは無いと思います。

拡張性の観点から挙げるポイントは、以下のコードブロックです。

ILocalNotification notification = new LocalNotificationForAndroid();

下コードの感じにするだけで、切り替えることができると思います。インターフェースでメソッドを定義してあるので、通知予約のメソッドも共通化できているので便利。(それ以外は LocalNotificationForDummy とかもいいかも)

#if UNITY_ANDROID
    ILocalNotification notification = new LocalNotificationForAndroid();
#elif UNITY_IOS
    ILocalNotification notification = new LocalNotificationForiOS();
#else
    ILocalNotification notification = new LocalNotificationForDummy();
#endif

あと、singleton パターンはとても強力なので、設計のやり甲斐があります。NotificationManager だと他に持つ機能がないかもしれません。EnvironmentManager のようなものに統合してもいいかもしれないです。


通知をテストする(+使い方)

アプリケーションの開始直後に設定をして、あとはそのインスタンスを使いまわす感じにしてみます。なので、アプリケーションのエントリーポイントでやってみることにします。

アプリケーションのエントリーポイントは次の記事で説明しています。

こんな感じになりました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class App
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void EntryPoint()
    {
        // 初期化
        var manager = NotificationManager.Instance;

        manager.Initialize();

        // サンプル用コード
        manager.Notification.ScheduleInSeconds("テストのタイトル", "テストのテキストです。", 10);

        Debug.Log("EntryPoint Called.");
    }
}

実行結果は、こうなります。問題なさそうですね。

f:id:shikaku_sh:20200916104208j:plain:h500
こんな感じで、できました


サンプル

GitHub に「unity-local-notification-practice」サンプルを公開しています。


参考