언어:
- 한국어(이 페이지)
- English: notification.md
- 日本語: notification.ja.md
사용할 기능에 필요한 권한을 추가합니다.
<!-- Android 13 이상에서 알림 전송에 필요 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- 예약 알림(정확한 알람)에 필요 -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- 포그라운드 서비스에 필요 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />// 가드: Android (Player)만. Editor에서는 네이티브 호출을 피합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
using JonghyunKim.NativeToolkit.Runtime.Notification;
#endif참고:
ChannelPayload,NotificationPayload,AndroidNotificationJsonBuilder는 런타임 패키지에 포함되어 있습니다. optional 필드와data를 포함한 spec 정합 JSON 이 필요하면JsonUtility.ToJson(...)대신AndroidNotificationJsonBuilder를 사용하세요.
#if UNITY_ANDROID && !UNITY_EDITOR
// 알림 권한이 부여되었는지 (Android 13+)
bool hasPermission = AndroidNotificationManager.Instance.HasPermission();
// 앱 알림이 활성화되었는지
bool enabled = AndroidNotificationManager.Instance.AreNotificationsEnabled();
// 정확한 알람 예약이 허용되었는지 (Android 12+)
bool canSchedule = AndroidNotificationManager.Instance.CanScheduleExactAlarms();
// POST_NOTIFICATIONS 권한 요청 (Android 13+)
AndroidNotificationManager.Instance.RequestPermission(granted =>
{
if (granted) { /* 권한 허용됨 */ }
});
// 설정 화면 열기
AndroidNotificationManager.Instance.OpenNotificationSettings();
AndroidNotificationManager.Instance.OpenAppDetailsSettings();
AndroidNotificationManager.Instance.OpenExactAlarmSettings();
#endif알림을 전송하기 전에 알림 채널을 생성해야 합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
string channelJson = AndroidNotificationJsonBuilder.BuildChannelJson(new ChannelPayload
{
id = "my_channel",
name = "내 채널",
importance = 3, // 3 = DEFAULT
description = "샘플 알림 채널",
showBadge = true,
enableLights = true,
lightColor = unchecked((int)0xFF4CAF50),
enableVibration = true,
vibrationPattern = new long[] { 0, 250, 200, 250 },
lockscreenVisibility = 1, // 1 = PUBLIC
groupId = "my_group",
groupName = "내 그룹"
});
AndroidNotificationManager.Instance.CreateChannel(channelJson);
#endif채널 중요도 레벨:
| 값 | 레벨 | 설명 |
|---|---|---|
| 1 | MIN | 소리 없음, 헤드업 없음 |
| 2 | LOW | 소리 없음 |
| 3 | DEFAULT | 소리 있음 |
| 4 | HIGH | 소리 있음 + 헤드업 |
#if UNITY_ANDROID && !UNITY_EDITOR
AndroidNotificationManager.Instance.DeleteChannel("my_channel");
#endif#if UNITY_ANDROID && !UNITY_EDITOR
AndroidNotificationManager.Instance.NotificationOperationCompleted += OnOperationCompleted;
#endif
private void OnOperationCompleted(NotificationResult result)
{
// result.Operation — 어떤 작업이 완료되었는지 나타냄
// result.IsSuccess — 성공 시 true
// result.ErrorMessage — 실패 시 non-null
}#if UNITY_ANDROID && !UNITY_EDITOR
string notificationJson = AndroidNotificationJsonBuilder.BuildNotificationJson(new NotificationPayload
{
id = 1101,
title = "에너지 회복",
message = "부대가 완전히 회복되었습니다. 바로 다음 레이드에 복귀할 수 있습니다.",
tag = "energy",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
largeIcon = CreateUnityAppIconResource(),
subText = "레이드 준비 완료",
autoCancel = true,
priority = 1,
category = "recommendation",
ticker = "Energy refilled",
number = 3,
style = new NotificationStylePayload
{
type = "bigText",
bigText = "부대가 완전히 회복되었습니다. 스태미나가 넘치기 전에 다음 레이드로 복귀하세요.",
bigContentTitle = "에너지 회복",
summaryText = "레이드 준비 완료"
}
});
AndroidNotificationManager.Instance.ShowNotification(notificationJson);
#endif참고: 알림 페이로드의
channel필드는 채널이 없는 경우 생성하는 데 사용됩니다. 이미 생성된 채널에는id와name만 전달해도 됩니다.
참고: 이 코드 예제들은
AndroidNotificationManagerExampleController와 동일하게CreateGameplayChannelReference()와CreateUnityAppIconResource()를 사용하도록 맞췄습니다.
동일한 id / tag 페이로드를 전달하면 현재 표시 중인 알림을 덮어씁니다.
#if UNITY_ANDROID && !UNITY_EDITOR
string updatedNotificationJson = AndroidNotificationJsonBuilder.BuildNotificationJson(new NotificationPayload
{
id = 1101,
title = "일일 보상 준비 완료",
message = "로그인 연속 보상 상자가 마을에서 기다리고 있습니다.",
tag = "energy",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
largeIcon = CreateUnityAppIconResource(),
subText = "마을 보상",
autoCancel = true,
priority = 1,
style = new NotificationStylePayload
{
type = "bigPicture",
picture = CreateUnityAppIconResource(),
largeIcon = CreateUnityAppIconResource(),
hideExpandedLargeIcon = false,
bigText = "로그인 연속 보상 상자가 마을에서 기다리고 있습니다. 지금 받아서 보상 배율을 유지하세요.",
bigContentTitle = "일일 보상 준비 완료",
summaryText = "마을 보상"
}
});
AndroidNotificationManager.Instance.UpdateNotification(updatedNotificationJson);
#endif#if UNITY_ANDROID && !UNITY_EDITOR
// 특정 알림 취소
AndroidNotificationManager.Instance.CancelNotification(1001);
AndroidNotificationManager.Instance.CancelNotification(1001, "energy");
// 모든 알림 취소
AndroidNotificationManager.Instance.CancelAllNotifications();
#endif알림 페이로드의 style 필드를 설정합니다.
// style 필드 없음 — 표준 알림으로 표시됩니다.확장 시 긴 텍스트를 표시합니다.
style = new NotificationStylePayload
{
type = "bigText",
bigText = "확장 시 표시되는 긴 본문 텍스트입니다.",
bigContentTitle = "확장 제목",
summaryText = "요약"
}확장 시 여러 줄을 목록 형식으로 표시합니다. lines 배열에 각 줄을 전달합니다.
style = new NotificationStylePayload
{
type = "inbox",
lines = new[] { "항목 1", "항목 2", "항목 3" },
bigContentTitle = "확장 제목",
summaryText = "3건"
}확장 시 이미지를 표시합니다. picture 에 이미지 리소스 참조를 전달합니다.
style = new NotificationStylePayload
{
type = "bigPicture",
picture = new NotificationResourcePayload { name = "my_image", type = "drawable" },
bigContentTitle = "확장 제목",
summaryText = "이미지 설명"
}커스텀 Android 레이아웃을 사용하여 알림을 표시합니다.
접힘/펼침용 레이아웃 리소스 이름을 지정합니다. 레이아웃 XML은 Assets/Plugins/Android/com.jonghyunkim.nativetoolkit.androidlib/res/layout/에 배치합니다(리소스 이름 충돌 방지를 위해 nt_ 접두사 사용).
#if UNITY_ANDROID && !UNITY_EDITOR
string notificationJson = AndroidNotificationJsonBuilder.BuildNotificationJson(new NotificationPayload
{
id = 1601,
title = "커스텀 레이아웃 알림",
message = "펼쳐서 커스텀 뷰를 확인하고 Dismiss 를 누르세요.",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
autoCancel = true,
style = new NotificationStylePayload
{
type = "decoratedCustomView",
customViewLayout = "nt_notification_custom_view_sample", // 접힘 레이아웃
bigCustomViewLayout = "nt_notification_custom_view_sample_expanded", // 펼침 레이아웃 (선택)
viewActions = new[]
{
new NotificationViewActionPayload
{
type = "setClickIntent",
viewId = "nt_notification_btn_dismiss", // 레이아웃 내 뷰 ID
actionId = "com.jonghyunkim.nativetoolkit.ACTION_CUSTOM_VIEW_DISMISS" // NotificationActionTapped에서 수신
}
}
}
});
AndroidNotificationManager.Instance.ShowNotification(notificationJson);
#endif참고:
RemoteViews제약으로 인해 클릭 가능한 요소에는Button대신LinearLayout+TextView를 사용하세요.
각 작업(표시, 취소, 예약 등)완료 후 발생합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
AndroidNotificationManager.Instance.NotificationOperationCompleted += OnOperationCompleted;
#endif
private void OnOperationCompleted(NotificationResult result)
{
// result.Operation — 예: AndroidNotificationManager.OperationShowNotification
// result.IsSuccess — 성공 시 true
// result.ErrorMessage — 실패 시 non-null
}작업 상수:
| 상수 | 설명 |
|---|---|
AndroidNotificationManager.OperationShowNotification |
ShowNotification 완료 |
AndroidNotificationManager.OperationUpdateNotification |
UpdateNotification 완료 |
AndroidNotificationManager.OperationCancelNotification |
CancelNotification 완료 |
AndroidNotificationManager.OperationCancelAllNotifications |
CancelAllNotifications 완료 |
AndroidNotificationManager.OperationScheduleNotification |
ScheduleNotification 완료 |
AndroidNotificationManager.OperationCancelScheduledNotification |
CancelScheduledNotification 완료 |
AndroidNotificationManager.OperationCancelAllScheduledNotifications |
CancelAllScheduledNotifications 완료 |
AndroidNotificationManager.OperationStartProgressForegroundService |
StartProgressForegroundService 완료 |
AndroidNotificationManager.OperationUpdateProgressForegroundService |
UpdateProgressForegroundService 완료 |
AndroidNotificationManager.OperationCompleteProgressForegroundService |
CompleteProgressForegroundService 완료 |
AndroidNotificationManager.OperationStopProgressForegroundService |
StopProgressForegroundService 완료 |
사용자가 알림 본문, 액션 버튼을 탭하거나 알림을 삭제할 때 발생합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
AndroidNotificationManager.Instance.NotificationActionTapped += OnActionTapped;
#endif
private void OnActionTapped(NotificationActionResult result)
{
bool isBodyTap = result.ActionId == AndroidNotificationManager.ActionBodyTap;
bool isDismiss = result.ActionId == AndroidNotificationManager.ActionNotificationDismissed;
// result.NotificationId — 알림 ID
// result.ActionId — 액션 식별자
// result.Data — Dictionary<string, string> 커스텀 데이터 (null일 수 있음)
}커스텀 데이터를 전송하려면 data 엔트리를 설정한 뒤 AndroidNotificationJsonBuilder 를 사용합니다:
payload.data = new[]
{
new NotificationDataEntryPayload { key = "screen", value = "battle" },
new NotificationDataEntryPayload { key = "matchId", value = "match_5678" }
};
string json = AndroidNotificationJsonBuilder.BuildNotificationJson(payload);예약 알림이 앱 포그라운드 중에 발생했을 때 수신합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
AndroidNotificationManager.Instance.NotificationReceived += OnNotificationReceived;
#endif
private void OnNotificationReceived(NotificationReceivedResult result)
{
// result.NotificationId — 알림 ID
// result.Tag — 태그 (null일 수 있음)
// result.ChannelId — 채널 ID
}알림에 액션 버튼을 추가합니다.
NotificationPayload payload = new NotificationPayload
{
id = 1401,
title = "매치 발견",
message = "랭크 매치가 준비되었습니다. 30초 안에 수락하세요.",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
autoCancel = true,
priority = 1,
launchAction = "open_battle_screen",
actions = new[]
{
new NotificationActionPayload
{
title = "지금 플레이",
actionId = "com.jonghyunkim.nativetoolkit.ACTION_PLAY_NOW",
icon = CreateUnityAppIconResource(),
launchApp = true,
showsUserInterface = true
},
new NotificationActionPayload
{
title = "닫기",
actionId = "com.jonghyunkim.nativetoolkit.ACTION_DISMISS",
launchApp = false,
showsUserInterface = false
}
},
data = new[]
{
new NotificationDataEntryPayload { key = "screen", value = "battle" },
new NotificationDataEntryPayload { key = "matchId", value = "match_5678" }
}
};
string notificationJson = AndroidNotificationJsonBuilder.BuildNotificationJson(payload);기기가 잠겨 있거나 화면이 꺼져 있을 때 전체 화면 액티비티를 실행합니다(알람, 수신 통화 등). fullScreenIntent = true를 설정하고 높은 우선순위 채널(importance = 4)을 사용합니다.
new NotificationPayload
{
id = 1501,
title = "매치 시작",
message = "지금 바로 매치가 시작됩니다. 게임 화면을 실행합니다.",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
priority = 2,
category = "call",
fullScreenIntent = true,
autoCancel = true
}참고: 기기 상태 및 Android 정책에 따라 전체 화면이 아닌 헤드업 알림으로 표시될 수 있습니다.
다운로드나 오래 걸리는 작업의 진행 바를 표시합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
// 결정적 진행 바로 시작
AndroidNotificationManager.Instance.StartProgressForegroundService(AndroidNotificationJsonBuilder.BuildNotificationJson(new NotificationPayload
{
id = 1301,
title = "길드 배틀 에셋 다운로드 중",
message = "다음 매치를 위해 경기장을 준비하고 있습니다.",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
ongoing = true,
autoCancel = false,
progress = new NotificationProgressPayload { max = 100, current = _currentProgressValue, indeterminate = false },
style = new NotificationStylePayload
{
type = "bigText",
bigText = "다음 매치를 위해 경기장을 준비하고 있습니다. 에셋 다운로드가 끝날 때까지 앱을 종료하지 마세요.",
bigContentTitle = "길드 배틀 에셋 다운로드 중",
summaryText = "백그라운드 다운로드"
}
}));
// 진행 업데이트
AndroidNotificationManager.Instance.UpdateProgressForegroundService(AndroidNotificationJsonBuilder.BuildNotificationJson(new NotificationPayload
{
id = 1301,
title = "전설 장비 제작 중",
message = "대장간이 최대 출력으로 가동 중입니다.",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
ongoing = true,
autoCancel = false,
onlyAlertOnce = true,
progress = new NotificationProgressPayload { max = 100, current = progressValue, indeterminate = false },
style = new NotificationStylePayload
{
type = "bigText",
bigText = "대장간이 최대 출력으로 가동 중입니다. 제작이 끝나면 바로 장착할 수 있도록 준비하세요.",
bigContentTitle = "전설 장비 제작 중",
summaryText = "제작 업데이트"
}
}));
// 완료 — 서비스 중지 및 일반 알림으로 강등
AndroidNotificationManager.Instance.CompleteProgressForegroundService(AndroidNotificationJsonBuilder.BuildNotificationJson(new NotificationPayload
{
id = 1301,
title = "제작 완료",
message = "전설 검을 수령할 준비가 되었습니다.",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
ongoing = false,
autoCancel = true,
progress = new NotificationProgressPayload { max = 100, current = 100, indeterminate = false },
style = new NotificationStylePayload
{
type = "bigText",
bigText = "전설 검을 수령할 준비가 되었습니다. 다음 전투 전에 대장간으로 돌아가 장착하세요.",
bigContentTitle = "제작 완료",
summaryText = "제작 완료"
}
}));
// 강제 중지 — 알림도 제거
AndroidNotificationManager.Instance.StopProgressForegroundService();
#endif포그라운드 서비스 알림에는 다음 매니페스트 항목이 필요합니다.
<service
android:name="android.library.notification.presentation.progress.ProgressForegroundService"
android:foregroundServiceType="dataSync"
android:exported="false" />StartProgressForegroundService, UpdateProgressForegroundService, CompleteProgressForegroundService, StopProgressForegroundService의 사용법은 진행 알림을 참조하세요.
지정한 시간에 자동으로 알림을 표시합니다.
#if UNITY_ANDROID && !UNITY_EDITOR
long triggerTime = DateTimeOffset.UtcNow.AddSeconds(15).ToUnixTimeMilliseconds();
string scheduleJson = AndroidNotificationJsonBuilder.BuildScheduledNotificationJson(new ScheduledNotificationEnvelopePayload
{
notification = new NotificationPayload
{
id = 1201,
title = "길드 배틀 곧 시작",
message = "팀 큐가 15초 후에 열립니다. 길드원을 모아 출격 준비를 하세요.",
tag = "guild-battle",
channel = CreateGameplayChannelReference(),
smallIcon = CreateUnityAppIconResource(),
autoCancel = true,
priority = 1,
groupKey = "guild-events",
sortKey = "001",
style = new NotificationStylePayload
{
type = "bigText",
bigText = "팀 큐가 15초 후에 열립니다. 장비를 최종 점검하고 출격 준비를 마치세요.",
bigContentTitle = "길드 배틀 곧 시작",
summaryText = "길드 이벤트"
}
},
schedule = new NotificationSchedulePayload
{
triggerAtMillis = triggerTime,
exact = true, // 정확한 알람 (SCHEDULE_EXACT_ALARM 필요)
allowWhileIdle = true, // Doze 모드에서도 발생
persistAcrossBoot = true,
alarmType = 0 // RTC_WAKEUP
}
});
AndroidNotificationManager.Instance.ScheduleNotification(scheduleJson);
#endif#if UNITY_ANDROID && !UNITY_EDITOR
AndroidNotificationManager.Instance.CancelScheduledNotification(1201, "guild-battle");
AndroidNotificationManager.Instance.CancelAllScheduledNotifications();
#endif#if UNITY_ANDROID && !UNITY_EDITOR
bool isScheduled = AndroidNotificationManager.Instance.IsNotificationScheduled(1201, "guild-battle");
#endifiOS 알림은 IosNotificationManager를 통해 사용할 수 있습니다. 샘플 화면에서는 권한 요청, 즉시 알림, 첨부 알림, 예약 알림, 카테고리/액션, 배지 조작까지 확인할 수 있습니다.
- 알림 권한 요청 / 권한 상태 확인 / 알림 설정 화면 이동
- 즉시 알림 (첨부 파일 알림 포함)
- 예약 알림 (시간 간격 / 캘린더 / 위치 기반)
- 알림 업데이트 / 취소 / 전달 완료 알림 삭제 / 목록 조회
- 배지 개수 설정
- 카테고리 등록 / 액션 / 텍스트 입력 액션
#if UNITY_IOS && !UNITY_EDITOR
IosNotificationManager.Instance.RequestPermission(result =>
{
if (result.IsSuccess)
{
Debug.Log("iOS notification permission granted");
}
else
{
Debug.LogError($"Permission failed: {result.ErrorMessage}");
}
});
IosNotificationManager.Instance.GetAuthorizationStatus(status =>
{
Debug.Log($"Authorization status: {status}");
});
#endif#if UNITY_IOS && !UNITY_EDITOR
var content = new NotificationContentPayload
{
id = "sample-notification",
title = "Energy Refilled",
body = "Your squad is fully rested. Jump back in and clear the next raid.",
sound = "default",
categoryIdentifier = "sample-category"
};
var contentJson = IosNotificationJsonBuilder.BuildContentJson(content);
IosNotificationManager.Instance.ShowNotification(contentJson, null, result =>
{
Debug.Log($"ShowNotification: {result.IsSuccess}");
});
#endif#if UNITY_IOS && !UNITY_EDITOR
var content = new NotificationContentPayload
{
id = "sample-notification",
title = "Immediate Notification with Attachment",
body = "Displayed with app icon attachment.",
sound = "default",
attachments = new[]
{
new NotificationAttachmentPayload
{
identifier = "app-icon",
fileURL = appIconFileUrl
}
}
};
var contentJson = IosNotificationJsonBuilder.BuildContentJson(content);
IosNotificationManager.Instance.ShowNotification(contentJson, null, null);
#endif#if UNITY_IOS && !UNITY_EDITOR
var content = new NotificationContentPayload
{
id = "scheduled-notification",
title = "Guild Battle Starts Soon",
body = "Battle queue opens in 10 seconds. Finalize your loadout and deploy.",
sound = "default"
};
var trigger = new TimeIntervalTriggerPayload
{
repeats = false,
interval = 10.0
};
string contentJson = IosNotificationJsonBuilder.BuildContentJson(content);
string triggerJson = IosNotificationJsonBuilder.BuildTimeIntervalTriggerJson(trigger);
IosNotificationManager.Instance.ScheduleNotification(
contentJson,
triggerJson,
"scheduled-notification",
result => Debug.Log($"ScheduleNotification: {result.IsSuccess}"));
#endif캘린더 예약은 CalendarTriggerPayload, 위치 기반 예약은 LocationTriggerPayload를 사용합니다.
#if UNITY_IOS && !UNITY_EDITOR
var category = new IosNotificationCategoryPayload
{
identifier = "sample-category",
actions = new[]
{
new IosNotificationActionPayload
{
identifier = "open",
title = "Open",
options = new[] { "foreground" }
}
},
textInputActions = new[]
{
new IosNotificationTextInputActionPayload
{
identifier = "reply",
title = "Reply",
buttonTitle = "Send",
textInputPlaceholder = "Type a message"
}
}
};
string categoryJson = IosNotificationJsonBuilder.BuildCategoryJson(category);
IosNotificationManager.Instance.RegisterCategory(categoryJson, null);
#endif#if UNITY_IOS && !UNITY_EDITOR
IosNotificationManager.Instance.NotificationOperationCompleted += result =>
{
Debug.Log($"Operation={result.Operation}, Success={result.IsSuccess}");
};
IosNotificationManager.Instance.NotificationActionReceived += result =>
{
Debug.Log($"Action={result.ActionId}, Notification={result.NotificationId}");
};
IosNotificationManager.Instance.NotificationTextInputActionReceived += result =>
{
Debug.Log($"TextInput={result.UserText}");
};
#endif(준비 중)
(준비 중)












