[Xamarin] Push Notification in iOS

2016. 4. 4. 19:02Mobile

Apple Push Notification

애플의 Push Notification의 중심에는 Apple Push Nitification Gateway Service(APNS)가 있습니다. APNs는 Application Server와 iOS 장치 사이에서 Push Notification Message를 중계하는 역할을 합니다. Application Server에서 Push Message를 APNS에 전송하면, APNS는 iOS 장치에게 Push Message를 전달합니다.

Application Server(=Provider)는 Notification Message를 구성하여 APNS에 전송합니다. Notification Message는 적절한 JSON String으로 HTTP/2 보안 채널을 통해 APNs 서버에 전송됩니다.

Application Server가 전달하는 Notification Message는 크게 Message와 Device Token으로 구성됩니다. APNs는 Device Token을 통해 전송 받을 대상 Device를 식별하게 됩니다.

Apple는 Push를 위해 2가지 환경을 제공합니다.

  1. Sandbox: 개발환경에서 사용되며, TCP 2195 포트의 gateway.sandbox.push.apple.com을 사용합니다.
  2. Production: 실제 앱을 배포하는 환경에서 사용하며, TCP 2195포트의 gateway.push.apple.com을 사용합니다.

요구사항

Push Notification에는 다음과 같은 제한사항이 있습니다.

  • 256 byte 메시지 길이제한: Notification Message의 전체 크기는 256 byte를 넘을 수 없습니다.
    (한글은 6Byte로 인식하므로 대략 30자의 한글을 발송할 수 있음)
  • 전송 보장에 대한 어떤 장치도 없음: APNS에 전달된 Message가 단말기에 전달되었다는 응답을 제공하지 않습니다. 만약 iOS Device가 접근할 수 없는 경우이거나, 여러 번 연속적인 Notification을 전송한 경우 최종적으로 보낸 Message를 제외하고 소실될 것입니다.
  • Secure Certificate가 필요: APNS의 통신에는 반드시 SSL로 이루어져야 합니다.

Push 서비스 활성화 및 인증서받기

App이 Push Service를 사용하기 위해서는 https://developer.apple.com에서 해당 앱을 등록해야 하며 Push Notification을 활성화 해야 합니다. 이 작업은 각 앱마다 별도로 이루어져야 합니다. 이와 관련된 절차는 다음에서 확인해주세요. (https://developer.xamarin.com/guides/cross-platform/application_fundamentals/notifications/ios/remote_notifications_in_ios/)

Push Notification을 진행하면 Apple로부터 인증서를 내려받을 수 있는데, 이 인증서는 Application Server가 APNs에 접속하기 위해 필요한 인증서 입니다. 이 예제는 Application Server를 구축하기 위해 무료 오픈소스 모듈인 PushSharp를 사용하기 때문에, Apple로 부터 내려받은 .DER 인증서를 PKC12 인증서로 변경이 필요합니다. PKC12로 변경하는 방법은 다음과 같습니다.

  1. Push Service를 활성화하면서 내려받은 인증서는 Keychane에 등록되어 있을 것입니다. Keychane을 열고 해당 인증서를 선택합니다. (Apple Development IOS Push Service: 앱이름 형태로 저장되어 있습니다)
  2. 다음 그림과 같이 인증서 하위에 있는 Key를 선택하여 보내기를 클릭합니다
  3. 유형을 P12로 선택하여 저장합니다.

APNS 등록하기

APNs에 Push Notification Message를 보내기 위해서는 Device Token이 필요합니다. 이 정보는 각 APNS가 생성하여 각 iOS 장치에 설치된 App에게 전달해주는 정보입니다. 이렇게 App이 수령한 Device Token은 공급자가 관리하는 Application Server이 전달받아 관리해야 합니다.

이론적으로는 iOS 장치에 설치된 App이 APNS에 등록 될 때마다 바뀔 수 있습니다. 그러나 실제로는 그런 경우가 거의 일어나지 않습니다. 따라서 App은 Device Token을 지속적으로 확인하고, 이 값이 변경되었을 때 Application Server에 전달해주면 됩니다.

이 과정을 간략히 나타내면 아래 그림과 같습니다.

Device Token을 APNS로부터 수령받는 위치는 App의 AppDelegate.cs에서 진행됩니다. FinishedLauching method에서 RegisterForRemoteNotificationTypes를 호출하면, iOS App은 APNS에 등록하는 과정을 진행하며 그 결과를 반환하게 됩니다. APNS에 등록하는 과정에서 Push Notification Message의 형태를 지정할 수 있는데, Alert, Badge(앱 아이콘 옆에 나타나는 숫자), Sound 등으로 선택할 수 있습니다.

실제 코드는 다음과 같으며, LoadApplication 전에 위치시키시면 됩니다.

//Push Notification 등록
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
    var pushSettings = UIUserNotificationSettings.GetSettingsForTypes(
                            UIUserNotificationType.Alert 
                            | UIUserNotificationType.Badge 
                            | UIUserNotificationType.Sound,
                            new NSSet());

    UIApplication.SharedApplication.RegisterUserNotificationSettings(pushSettings);
    UIApplication.SharedApplication.RegisterForRemoteNotifications();
}
else
{
    UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert
                                                | UIRemoteNotificationType.Badge 
                                                | UIRemoteNotificationType.Sound;
    UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
}

APNS 등록과정은 Background Thread에서 진행되며, 이 과정이 끝나면 iOS는 RegisteredForRemoteNotifications 메서드를 호출하게 됩니다. Device Token은 NSData object를 통해서 확인하실 수 있습니다. 실제 코드로는 다음과 같습니다.

//Push Notification - DeviceToken 수령
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    //1. Device Token 수령
    var DeviceToken = deviceToken.Description;
    if (!string.IsNullOrWhiteSpace(DeviceToken))
        DeviceToken = DeviceToken.Trim('<').Trim('>');

    //2. 이전 Device Token 조회
    var oldDeviceToken = NSUserDefaults.StandardUserDefaults.StringForKey("PushDeviceToken");

    //3. Token이 변경되었으면, 서버에 저장
    if (string.IsNullOrEmpty(oldDeviceToken) || !oldDeviceToken.Equals(DeviceToken))
    {
        //서버에 저장하는 로직..
    }

    //4. Device Token을 앱에 저장
    NSUserDefaults.StandardUserDefaults.SetString(DeviceToken, "PushDeviceToken");
}

만약 인터넷이 연결되어 있지 않는 등, Device Token 수령을 실패하면 FailedToRegisterForRemoteNotifications 메서드를 호출하게 됩니다.

public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
    //Push Notification - DeviceToken 수령실패
    new UIAlertView("푸시 알람 등록이 실패하였습니다.", error.LocalizedDescription, null, "확인", null).Show();
}

Device Token 관리

Device Token은 만료되거나 시간이 지나면서 바뀔 수 있습니다. 따라서 Device Token을 관리하는 Application Server는 유효하지 않은 Device Token을 정리하고 새롭게 갱신하는 것이 필요합니다. 만약 만료된 Device Token으로 APNS에 Push를 보내면 APNS는 만료된 Token 정보를 저장해 둡니다. 따라서 Application Server는 APNS에 접속하여 만료된 Token이 무엇인지 확인하는 작업이 필요합니다. APNS는 Feedback Service를 제공하고 있으며, 이를 통해 만료된 토큰 정보를 제공합니다.

여기서는 PushShapr를 사용하기 때문에, PushSharp를 기준으로 설명합니다. PushSharp는 FeedbackService class를 통해서 만료된 Device Token정보를 쉽게 확인할 수 있습니다.

private void btnFeedbackApple_Click(object sender, EventArgs e)
{
    var config = new ApnsConfiguration(
        ApnsConfiguration.ApnsServerEnvironment.Sandbox,
        "P12인증서Path",
        "P12인증서Password");

    var fbs = new FeedbackService(config);
    fbs.FeedbackReceived += (string deviceToken, DateTime timestamp) => 
    {
        //만료된토큰을 조회하여, 처리필요
    };
    fbs.Check();
}

PKCS12인증서 키와 비밀번호를 FeedbackService에 제공하면, 그 응답으로써 만료된 Token을 조회하실 수 있습니다. FeedBackReceived Event에서는 만료된 Device Token과 TimeStamp를 확인하실 수 있습니다.

서버에서 Push 전송하기

Push Message를 보내기전 다음을 준수해야 합니다.

  1. Push Message를 보내는 서버는 Locahost(127.0.0.1)나 Local Network(192.168…)가 아닌 호스팅된 서버여야 합니다.
  2. Push Message를 보내는 서버는 다음과 같은 방화벽 포트를 해제해야 합니다.
    TCP: 2195, 2196, 443, 5223
  3. PushSharp Library로 발송할 경우, Device Token은 반드시 공백이 제거된 16진수 문자열로만 구성해야 합니다.
    예) bd18863c0xx78d2b1e4d2407c5591177206d664adb4d2a0b81e0061867d4a0dd

PushSharp를 통한 Apple Push Message를 보내는 코드는 다음과 같습니다.

public List<string> DeviceTokenList = new List<string>();
public List<string> ResultList = new List<string>();
private void btnSendPushApple_Click(object sender, EventArgs e)
{
    //1. 인증정보 입력
    var config = new ApnsConfiguration(ApnsConfiguration.ApnsServerEnvironment.Sandbox,
        "인증서Path.p12", "비밀번호");

    //2. Push 서비스 선언
    var apnsBroker = new ApnsServiceBroker(config);
    apnsBroker.OnNotificationFailed += (notification, aggregateEx) => 
    {
        //*) 오류 발생시 대응
        aggregateEx.Handle(ex => 
        {
            if (ex is ApnsNotificationException)
            {
                var notificationException = (ApnsNotificationException)ex;
                var apnsNotification = notificationException.Notification;
                var statusCode = notificationException.ErrorStatusCode;

                ResultList.Add($"[실패] ID={apnsNotification.Identifier}, DeviceToken={notification.DeviceToken}, Code={statusCode}");
            }
            else
            {
                Console.WriteLine($"[실패] 알수없는 오류\r\n {ex.InnerException}");
            }

            // true를 반환하여 오류처리완료 상태로 변경
            return true;
        });
    };
    apnsBroker.OnNotificationSucceeded += (notification) => 
    {
        //*) 성공 시 대응
        ResultList.Add($"[성공] ID={notification.Identifier}, DeviceToken={notification.DeviceToken}");
        Console.WriteLine("Apple Notification Sent!");
    };
            


    //3. 전송 시작
    apnsBroker.Start();
    foreach (var deviceToken in DeviceTokenList)
    {
        // Queue a notification to send
        apnsBroker.QueueNotification(new ApnsNotification
        {
            DeviceToken = deviceToken.Replace(" ", ""),//반드시 공백을 제거해야 한다
            Payload = Newtonsoft.Json.Linq.JObject.Parse("{\"aps\":{\"badge\":7}}")
        });
    }
    //전송이 끝나길 기다리고, 중지되면 종료합니다
    apnsBroker.Stop();
}

Push Message 수신하기

ReceivedRemoteNotification Method를 통해서 확인하실 수 있습니다. Method 매개변수로 전달되는 NSDictionary에서 전달된 데이터를 확인하실 수 있습니다.

public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
    NSObject inAppMessage;

    bool success = userInfo.TryGetValue(new NSString("inAppMessage"), out inAppMessage);
    if (success)
    {
        var alert = new UIAlertView("Push 수신!", inAppMessage.ToString(), null, "확인", null);
        alert.Show();
    }
}

기타 참고정보

  • p12인증서는 만료일 전에 갱신해야 합니다. 만료일은 보통 1년입니다. 앱의 인증서는 갱신과 무관하고 Push Notification을 위한 인증서만 갱신해주시면 됩니다. 애플 개발자 센터에서 인증서를 갱신한 후 키체인의 내보내기를 통해 생성한 p12 인증서를 Push 서버에 올려주셔야 갱신이 완료되게 됩니다.
  • PushSharp를 통해 Push Notification을 발송할 때 ConnectionError와 함께 "패키지에 제공된 자격 증명을 인식할 수 없습니다"라는 오류가 리턴되는 사례가 보고되고 있습니다. 이 경우 p12 인증서를 pfx 형식으로 변경한 후 발송하시면 해결될 수 있습니다. p12를 pfx로 변경하는 방법은 아래 링크를 확인해주세요.
    (p12를 pfx로 변환하기: http://nsinc.tistory.com/198)