유니티 안드로이드 앱에서 FCM 서버 푸시 받기

Jonghwan Choi·2023년 4월 13일
0

Unity Android App

목록 보기
3/6

유니티 모바일 앱 개발시 안드로이드 앱은 Firebase Cloud Messaging(이하 FCM) 서비스, iOS 앱은 Apple Push Notification 서비스를 이용해서 (FCM으로도 가능) 푸시를 수신할 수 있다. 그 중 FCM을 이용한 안드로이드 푸시 수신에 대해서 정리해 보겠다. 푸시를 보내는 서버 세팅에 대해서는 추후 학습하게 된다면 작성하겠다.

1. FCM 연동 준비

Firebase 홈페이지에 가입 후, 프로젝트를 등록하고, FCM 라이브러리 패키지를 다운받고, 프로젝트에 import한다. 공식 가이드를 참고하여 4단계까지 진행하면 된다.

2. FCM 초기화

Start()

초기화 코드는 앱이 켜지자마자 바로 발동하는 것이 좋으니 MonoBehaviour의 Start() 메서드에 작성하는 것이 무난하다.

public class MainBehaviour : MonoBehaviour
{
	string CHANNEL_ID = "myChannel";
	int apiLevel;
    
    void Start()
    {
#if UNITY_ANDROID
		InitializeFCM(); // 아래 참고
        
#elif UNITY_IOS
		// iOS 푸시 수신 코드
        
#endif 
    }
}

InitializeFCM()

Firebase를 이용하기 위해서는 디바이스에 Google Play service가 최신 버전으로 설치되어 있어야 한다. 그래서 Google Play 버전 체크를 진행한 뒤, 버전이 유효할 경우 FCM을 초기화하는 코드이다.

public void InitializeFCM()
{
	// Google Play 버전 체크 (비동기)
	FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
		var dependencyStatus = task.Result;
		if (dependencyStatus == DependencyStatus.Available)
		{
			Debug.Log("Google Play version OK");
            
			// FCM 초기화
			FirebaseMessaging.TokenReceived += OnTokenReceived; // 아래 참고
			FirebaseMessaging.MessageReceived += OnMessageReceived; // 아래 참고
			FirebaseMessaging.RequestPermissionAsync().ContinueWithOnMainThread(task => {
				Debug.Log("push permission: "+task.Status.ToString());
			});
		}
		else
		{
			Debug.LogError(string.Format(
				"Could not resolve all Firebase dependencies: {0}", 
				dependencyStatus
			));
		}
	});
}

OnTokenReceived()

FCM서버에서 새 푸시 토큰을 발급하면 자동 호출되는 콜백 메서드. 위의 FirebaseMessaging.TokenReceived 이벤트 핸들러에 등록해서 사용한다. 새 토큰이 발급될 때마다 서버에 전송하도록 코드를 짤 경우 여기에 해당 코드를 작성하면 된다.

public void OnTokenReceived(object sender, TokenReceivedEventArgs token)
{
	Debug.Log("OnTokenReceived: " + token.Token);
}

OnMessageReceived()

푸시 메시지를 수신하면 자동 호출되는 콜백 메서드. 위의 FirebaseMessaging.MessageReceived 이벤트 핸들러에 등록해서 사용한다. 수신한 메시지로 후속 작업을 해야 할 때 여기에 해당 코드를 작성하면 된다.

public void OnMessageReceived(object sender, MessageReceivedEventArgs e)
{
	// [3. FCM 서버 푸시 수신]에서 작성
}

여기까지만 진행해도 일단 FCM 서버 푸시를 수신하는 것은 가능하다.

3. 수신한 서버 푸시 처리

FCM의 푸시 메시지는 '알림(notification) 메시지' 와 '데이터(data) 메시지' 2가지로 나뉜다. 이는 메시지로 전송되는 JSON string의 구성에 따른 분류이다. JSON 객체 message 안에 notification이 존재한다면 알림 메시지, data만 있고 notification이 없다면 데이터 메시지이다.

FCM 푸시 메시지의 구성 예시

{
  "message":{
    "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification":{
      "title":"Portugal vs. Denmark",
      "body":"great match!"
    },
    "data" : {
      "Nick" : "Mario",
      "Room" : "PortugalVSDenmark"
    }
  }
}

공식 문서

이 구분이 왜 중요한지는 알림 팝업창과 관계가 있기 때문에 아래에서 다루기로 하고, 일단 메시지의 내용으로 두 가지 유형이 있다는 사실을 염두에 두고 수신한 메시지를 처리하는 코드를 작성해 보자. 아래 코드는 간단하게 수신한 메시지 내용을 Log로 표시하는 코드이다. 여기서 notification 메시지의 키값 Title, Body는 정해진 이름이지만, data 메시지의 키값 title, body는 메시지를 송신한 서버에서 자유롭게 정하는 것이므로 서버 측에서 정해둔 키값 네이밍을 알고 있어야 한다.

OnMessageReceived()

public void OnMessageReceived(object sender, MessageReceivedEventArgs e)
{
	string type = "";
	string title = "";
	string body = "";
    
	// 알림 메시지 수신시
	if (e.Message.Notification != null) 
	{
		type = "notification";
		title = e.Message.Notification.Title;
		body = e.Message.Notification.Body;
	}
	// 데이터 메시지 수신시
	else if (e.Message.Data.Count > 0)
	{
		type = "data";
		title = e.Message.Data["title"];
		body = e.Message.Data["body"];
	}
	Debug.Log("message type: " + type + ", title: " + title + ", body: " + body);
}

4. 알림 팝업창 띄우기

푸시 메시지를 받아서 유저에게 노출하지 않고 앱 안에서만 사용할 거라면 3까지만 진행해도 상관없다. 좀더 나아가서 푸시 메시지를 알림 팝업창으로 띄우고 싶다면 조금 더 수고를 들여야 한다. 이 알림 팝업창은 앱이 아니라 운영체제에서 표시하는 것이므로 안드로이드와 iOS에서 각각 다른 방법을 써야 한다. 다행히 유니티에서 제공하는 Mobile Notifications 패키지를 사용하면 안드로이드와 iOS 둘 다 알림 팝업창을 띄울 수 있다. 여기서는 안드로이드 알림 팝업창만 소개한다.

Mobile Notifications 패키지 설치

유니티 에디터 상단 메뉴의 Window > Package Manager 에서 Mobile Notifications 를 설치한다. 설치 과정 및 알림 팝업창에 쓸 아이콘 등의 설정은 이 글을 보고 진행하면 된다.

공식 문서
코드 참고

Start()

멤버로 apiLevel을 추가하고, InitializeFCM() 위에 InitializeAndroidLocalPush() 을 추가한다.

public class MainBehaviour : MonoBehaviour
{
	string CHANNEL_ID = "myChannel";
	int apiLevel; // 추가
    
    void Start()
    {
#if UNITY_ANDROID
		InitializeAndroidLocalPush(); // 추가
		InitializeFCM();
        
#elif UNITY_IOS
		// iOS 푸시 수신 코드
        
#endif 
    }
}

InitializeAndroidLocalPush()

안드로이드 알림은 api level 26 과 33 에서 변경점이 있다. 내 앱이 대상으로 삼은 Sdk Version 범위에 이 버전이 포함되어 있다면 버전에 따라 분기 처리를 해주는 편이 좋다.
API level 26 (Android 8.0 Oreo) 이상 변경점
API level 33 (Android 13 Tiramisu) 이상 변경점

public void InitializeAndroidLocalPush()
{
	// 디바이스의 안드로이드 api level 얻기
	string androidInfo = SystemInfo.operatingSystem;
	Debug.Log("androidInfo: " + androidInfo);
	apiLevel = int.Parse(androidInfo.Substring(androidInfo.IndexOf("-") + 1, 2));
	Debug.Log("apiLevel: " + apiLevel);

	// 디바이스의 api level이 33 이상이라면 퍼미션 요청
	if (apiLevel >= 33 &&
		!Permission.HasUserAuthorizedPermission("android.permission.POST_NOTIFICATIONS"))
	{
		Permission.RequestUserPermission("android.permission.POST_NOTIFICATIONS");
	}

	// 디바이스의 api level이 26 이상이라면 알림 채널 설정
	if (apiLevel >= 26)
	{
		var channel = new AndroidNotificationChannel()
		{
			Id = CHANNEL_ID,
			Name = "pubSdk",
			Importance = Importance.High, // 아래 참고
			Description = "for test",
		};
		AndroidNotificationCenter.RegisterNotificationChannel(channel);
	}
}

api level 얻기 코드 출처

apiLevel >= 26 코드 중 Importance라는 게 있는데, 이는 채널의 '중요도' 를 설정하는 것으로 이를 통해서 알림 팝업 표시여부나 알림음 여부 등이 결정되므로 알아두어야 한다. 공식 문서

  • IMPORTANCE_HIGH: 알림음이 울리며 헤드업 알림 표시
  • IMPORTANCE_DEFAULT: 알림음이 울리며 상태 표시줄에 아이콘 표시
  • IMPORTANCE_LOW: 알림음이 없고 상태 표시줄에 아이콘 표시
  • IMPORTANCE_MIN: 알림음이 없고 상태 표시줄에 표시되지 않음

주의할 점으로, 이미 해당 채널로 알림을 보낸 이력이 있어서 수신하는 기기에 채널이 등록된 상태라면 채널의 중요도를 변경해도 수신하는 기기에 반영되지 않는다.
그러므로 중요도를 수정했다면 수신하는 기기의 앱을 삭제해서 채널 목록을 초기화하거나, 채널 ID를 바꿔서 새로운 채널을 생성해야 중요도 변경이 반영된다.

OnMessageReceived()


공식 문서

위에서 FCM 푸시 메시지에는 알림 메시지와 데이터 메시지 유형이 있다고 했다. 이 두 유형에 따라 백그라운드에서 알림 팝업창을 띄울 수 있냐 없냐가 갈린다. 위의 표에서 알림 메시지 및 알림+데이터 메시지는 앱이 백그라운드 상태일 때 수신할 경우 Firebase SDK가 팝업창 없이 디바이스 상단의 작업표시줄에 아이콘만 띄우고 메시지 처리를 끝내버린다. 백그라운드에서도 우리가 작성한 OnMessageReceived() 콜백메서드로 메시지를 받아서 원하는 대로 처리하려면 서버 단에서 메시지를 데이터 유형으로 보내야 한다.

public void OnMessageReceived(object sender, MessageReceivedEventArgs e)
{
	... 
    
	var notification = new AndroidNotification();
	notification.SmallIcon = "icon_0";
	notification.Title = title;
	notification.Text = body;
	notification.FireTime = System.DateTime.Now;

	// 디바이스의 api level이 26 이상이라면 알림 표시
	if (apiLevel >= 26)
	{
		AndroidNotificationCenter.SendNotification(notification, CHANNEL_ID);
	}
    
    // SendNotification()이 무조건 채널ID를 인자로 받도록 되어 있어서, 
    // 채널 개념이 없는 API level 26 미만 기기는 충돌이 일어날 것이다 (테스트는 못해봄)
	else
	{
		Debug.LogError("Android 8.0 이상의 디바이스에서만 푸시 알림이 정상적으로 표시됩니다.");
	}
}

5. 테스트

Firebase 콘솔에서 테스트용 푸시메시지를 보내 볼 수 있다. 실제 푸시와 달리 발신 후 3~6분 정도 지나야 기기로 전송된다. 다만 알림 메시지만 보낼 수 있고 데이터 메시지는 지원하지 않기 때문에 백그라운드에서 정상적으로 팝업창이 띄워지는지는 여기서 테스트가 불가능하다. 데이터 메시지 테스트는 직접 푸시 요청 서버를 구축해서 진행하는 수밖에 없다. 푸시 요청 서버 구축은 공식 문서를 참고할 것.

부록. 전체 코드

public class LoginController : MonoBehaviour
{
	string CHANNEL_ID = "myChannel";
	int apiLevel;
    
    void Start()
    {
#if UNITY_ANDROID
		InitializeAndroidLocalPush();
		InitializeFCM();
#elif UNITY_IOS

#endif
    }

	public void InitializeAndroidLocalPush()
	{
		string androidInfo = SystemInfo.operatingSystem;
		Debug.Log("androidInfo: " + androidInfo);
		apiLevel = int.Parse(androidInfo.Substring(androidInfo.IndexOf("-") + 1, 2));
		Debug.Log("apiLevel: " + apiLevel);

		if (apiLevel >= 33 &&
			!Permission.HasUserAuthorizedPermission("android.permission.POST_NOTIFICATIONS"))
		{
			Permission.RequestUserPermission("android.permission.POST_NOTIFICATIONS");
		}

		if (apiLevel >= 26)
		{
			var channel = new AndroidNotificationChannel()
			{
				Id = CHANNEL_ID,
				Name = "test",
				Importance = Importance.High,
				Description = "for test",
			};
			AndroidNotificationCenter.RegisterNotificationChannel(channel);
		}
	}

	public void InitializeFCM()
	{
		FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
			var dependencyStatus = task.Result;
			if (dependencyStatus == DependencyStatus.Available)
			{
				Debug.Log("Google Play version OK");

				FirebaseMessaging.TokenReceived += OnTokenReceived;
				FirebaseMessaging.MessageReceived += OnMessageReceived;
				FirebaseMessaging.RequestPermissionAsync().ContinueWithOnMainThread(task => {
					Debug.Log("push permission: "+task.Status.ToString());
				});
			}
			else
			{
				Debug.LogError(string.Format(
					"Could not resolve all Firebase dependencies: {0}", 
					dependencyStatus
				));
			}
		});
	}

	public void OnTokenReceived(object sender, TokenReceivedEventArgs token)
	{
		Debug.Log("OnTokenReceived: " + token.Token);
	}
	public void OnMessageReceived(object sender, MessageReceivedEventArgs e)
	{
		string type = "";
		string title = "";
		string body = "";

		// for notification message
		if (e.Message.Notification != null) 
		{
			type = "notification";
			title = e.Message.Notification.Title;
			body = e.Message.Notification.Body;
		}
		// for data message
		else if (e.Message.Data.Count > 0)
		{
			type = "data";
			title = e.Message.Data["title"];
			body = e.Message.Data["body"];
		}
		Debug.Log("message type: " + type + ", title: " + title + ", body: " + body);

		var notification = new AndroidNotification();
		notification.SmallIcon = "icon_0";
		notification.Title = title;
		notification.Text = body;
		notification.FireTime = System.DateTime.Now;

		if (apiLevel >= 26)
		{
			AndroidNotificationCenter.SendNotification(notification, CHANNEL_ID);
		}
		else
		{
			Debug.LogError("Android 8.0 이상의 디바이스에서만 푸시 알림이 정상적으로 표시됩니다.");
		}
	}
}
profile
유니티 게임 클라이언트 개발자를 꿈꾸는 뉴비

0개의 댓글