Tag Archives: Apple

[iPhone/Java] 내가 만든 어플에 Push Notification 적용하기

사용자 삽입 이미지
오늘은 애플사에서 제공하는 Apple Push Notification Service 줄여서 APNS를 사용해서 나의 어플리케이션이 메시지를 푸싱하는것에 대해 간단하게 정리해 보겠습니다. 애플에서 제공하는 푸시의 경우 위의 프리젠테이션에서 볼 수 있듯이 써드파티의 서버에서 APNS서버로 메시지를 보내면 APNS에서 해당 디바이스에 직접적으로 메시지를 전달해주는 일을 합니다. 실제로 테스트 해본 결과 단 몇초안에 메시지가 전달이 되더군요. 와우!

1. 인증서 준비

사용자 삽입 이미지
위의 화면은 개발자 사이트의 Provisioning Portal – App IDs 에 들어가면 볼 수 있는 화면입니다. 두번째 부분이 Push Notification에 대한 설정인데요. 첫번째 App ID의 경우에는 현재 APNS설정을 할 수 있는 상태입니다. 그리고 두번쨰는 아예 설정이 불가능한 경우입니다. 이 경우는 App ID의 Identifier에 *가 들어간 경우에 해당됩니다. 꼭 어플과 App ID가 1:1매치가 되어야 하겠죠. 3번째는 예상하시다시피 이미 설정이 끝난경우 입니다.

사용자 삽입 이미지
Configure를 눌러 들어간 화면에는 위와같은 화면이 나옵니다. Enable을 눌러주시면 아래의 Configure 버튼이 활성화가 됩니다. 보시면 대충 Push를 보내기 위한 관련 인증서를 발급 받는 부분이라는것을 아실수 있으실텐데요 두가지가 있네요. 한가지는 개발용 하나는 실제 서비스용입니다. 우선은 시범적으로 개발용만 발급받아보겠습니다. 실제 서비스때는 밑의 것을 발급받아 사용하시면 됩니다.

사용자 삽입 이미지
위와 같은 화면이 나옵니다. 아마 대부분의 개발자분들이 이 화면을 보시면 어떤것이 필요한 것인지 감이 오실것이라 생각되네요. 잘 모르시겠다면 [Apple Program 이용가이드]의 인증서 등록 부분을 참고하시면 될것 같네요. 화면이 요즘과 좀 많이 달라지긴 했지만 참고하시는데에는 무리가 없을것입니다. 저때에 사용하셨던 CSR파일을 그대로 이용하시면 됩니다.

사용자 삽입 이미지
잠시후에 자동으로 인증서가 발급됩니다. 위와같이 화면이 뜨게 됩니다.

사용자 삽입 이미지
이제 인증서를 다운받을 수 있게 되는데요, 위에 보이는 Download버튼을 눌러 인증서를 다운 받습니다. 그리고 더블클릭하여 시스템의 인증서로 등록하여 줍니다.

aps_developer_identity.cer – 개발용 인증서
aps_production_identity.cer – 실서비스용 인증서

맥의 유틸리티 – 인증서 메뉴에 가보면 위와 같이 Push Service에 대한 인증서가 등록된것을 확인하실 수 있습니다. 이제 해당 인증서와 아래에 선택한것과 같이 CSR 발급에 사용되었던 개인키(Private Key)를 동시에 선택을 한후에 마우스 오른쪽 버튼을 눌러 “외부로 보내기”(기억이 확실치 않네요; 아무튼 Export입니다)를 하여 줍니다. p12라는 확장자의 파일로 저장이 가능합니다. 이때에 암호를 정할 수 있는데 적절한 암호를 입력하여 줍니다.

2. 아이폰 어플리케이션 수정

이제 아이폰 어플리케이션에 Push Service를 등록할 수 있도록 몇가지 추가를 해야 합니다. -AppDelegate.m 파일에 다음의 내용을 추가합니다.

- (void)application:(UIApplication *)application 
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 
{ 
 NSMutableString *deviceId = [NSMutableString string]; 
 const unsigned char* ptr = (const unsigned char*) [deviceToken bytes]; 

 for(int i = 0 ; i < 32 ; i++) 
 { 
  [deviceId appendFormat:@"%02x", ptr[i]]; 
 } 

 NSLog(@"APNS Device Token: %@", deviceId); 
}

위의 추가된 코드는 어플리케이션이 최초 실행될때에 어플리케이션이 푸시서비스를 이용함을 알리고 허용할지를 물어보게 하고 사용자의 동의를 얻었을 경우 실행됩니다. APNS에 디바이스 정보를 등록하고 64바이트의 문자열을 받아오게 됩니다. 마지막에 NSLog로 확인을 하게 되는 deviceId를 써드파티 서비스의 서버로 전송하여 관리하시면 됩니다.

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
{ 
 NSString *string = [NSString stringWithFormat:@"%@", userInfo]; 
 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil 
                             message:string delegate:nil
                             cancelButtonTitle:@"OK"
                             otherButtonTitles:nil]; 
 [alert show]; 
 [alert release]; 
}

위의 새로 추가된 메서드는 어플리케이션이 푸시를 받았을 경우 호출되는 메서드입니다. 그냥 받은 메시지를 그대로 찍도록 해보았습니다. 넘어오는 데이터에 대해서는 직접 한번 디버깅하여 보시길 추천해드립니다.

사용자 삽입 이미지

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
 NSDictionary *userInfo = [launchOptions objectForKey:
              UIApplicationLaunchOptionsRemoteNotificationKey]; 

 if(userInfo != nil) 
 { 
  [self application:application didFinishLaunchingWithOptions:userInfo]; 
 } 

 // APNS에 디바이스를 등록한다. 
 [[UIApplication sharedApplication] registerForRemoteNotificationTypes: 
  UIRemoteNotificationTypeAlert| 
  UIRemoteNotificationTypeBadge| 
  UIRemoteNotificationTypeSound]; 

 return YES; 
}

이제 하이라이트입니다. 위의 메서드는 어플리케이션이 실행될때에 호출이 됩니다. 요즘은 URI 스키마 혹은 푸시 서비스를 통해 어플리케이션을 실행할 수 있게 되었고 관련 옵션들이 launchOptions인자로 넘어오게 됩니다.

위의 경우에는 어플리케이션이 실행되어있지 않은 경우 푸시를 받아서 어플리케이션이 실행될때 관련 정보가 launchOptions에 담겨오게 되며 그 정보를 가지고 didFinishLaunchingWithOptions를 재호출하는 로직입니다. 그 뒤에 보면 나와있는 APNS등록 부분이 사용자에게 푸시를 허용할지 물어보는 부분이 되겠습니다.

이때에 알아두실 점은 위의 메서드가 구현된 시점부터 원래 사용되던 다음의 메서드는 사용되지 않습니다.

- (void)applicationDidFinishLaunching:(UIApplication *)application 
{ 
 // didFinishLaunchingWithOptions를 구현할 경우 사용되지 않는다. 
}

3. 푸시 서버 준비

이 블로깅에서는 푸시 서버를 Java기반에서 개발하는것으로 설명합니다. 검색해 보면 PHP, Ruby, Python등 매우 다양한 언어로 이미 포팅되어 잘 만들어진 라이브러리도 많이 있더군요. 저도 역시나 잘 만들어진(실제로 여러 포럼에서 추천받는) javapns를 사용하도록 하겠습니다.

필요한 JAR 파일은 다음과 같습니다.

javapns
log4j
bcprov-ext
commons-io
commons-lang

 

이제 해당 홈페이지에 나와있는 간단한 예제를 실행해 보도록 하겠습니다. 다음 소스에서 사용되는 iPhoneId의 경우 didRegisterForRemoteNotificationsWithDeviceToken 메서드에서 언급되었던 deviceId값이며 certificate의 경우 인증서 준비단계에서 만들었던 p12확장자 파일을 뜻합니다. passwd는 p12를 만들당시에 물어보았던 비밀번호입니다.

public class Push {
     // APNs Server Host & port
     private static final String HOST = "gateway.sandbox.push.apple.com";
     private static final int PORT = 2195;

     // Badge
     private static final int BADGE = 66;

     // iPhone's UDID (64-char device token)
     private static String iPhoneId = "2ed202ac08ea9...cf8d55910df290567037dcc4";
     private static String certificate = "/absolute/path/to/my/certificate";
     private static String passwd = "mycertificatepassword";

     public static void main( String[] args ) throws Exception {

        System.out.println( "Setting up Push notification" );

        try {
             // Setup up a simple message
             PayLoad aPayload = new PayLoad();
             aPayload.addBadge( BADGE );
             aPayload.addAlert("Hello World!");
             aPayload.addSound("default");
             System.out.println( "Payload setup successfull." );

             System.out.println ( aPayload );

             // Get PushNotification Instance
             PushNotificationManager pushManager = 
                PushNotificationManager.getInstance();

             // Link iPhone's UDID (64-char device token) to a stringName
             pushManager.addDevice("iPhone", iPhoneId);
             System.out.println( "iPhone UDID taken." );

             System.out.println( "Token: " +
                pushManager.getDevice( "iPhone" ).getToken() );

             // Get iPhone client
             Device client = pushManager.getDevice( "iPhone" );
             System.out.println( "Client setup successfull." );

             // Initialize connection
             pushManager.initializeConnection( HOST, PORT, certificate, passwd, 
                SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
             System.out.println( "Connection initialized..." );

             // Send message
             pushManager.sendNotification( client, aPayload );
             System.out.println( "Message sent!" );

             System.out.println( "# of attempts: " +
                pushManager.getRetryAttempts() );
             pushManager.stopConnection();

             System.out.println( "done" );

         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

사용자 삽입 이미지

예제코드에 이어 바로 결과 화면을 붙여보았습니다. 위와같이 메시지를 전송하면 잠자고 있던 아이팟이 알람을 울리게 됩니다.

여기서 한가지 더 알아두셔야 할 점으로 Feedback이라는 것이 있습니다. 위와 같은 Push의 경우에는 해당 사용자에게 제대로 메시지가 전달 되었는지 여부를 확인할 길이 없습니다. 만약에 10만명의 푸시 사용자가 가입을 했고 5만명이 해당 어플의 푸시를 받지 않겠다고 설정을 바꾸었거나 삭제를 했다면 서비스는 일일이 받지 않는 사용자에게 푸시를 날리느라 시간을 낭비하게 되겠죠. 그래서 이 Feedback을 이용하여 사용하지 않는 사용자의 리스트를 받아오게 됩니다.

public class Feedback {
     // APNs Server Host & port
     private static final String HOST = "feedback.push.apple.com";
     private static final int PORT = 2196;

     private static String certificate = "/absolute/path/to/certificate";
     private static String passwd = "certificatePassword";

     public static void main( String[] args ) throws Exception {

        try {
             // Get FeedbackServiceManager Instance
             FeedbackServiceManager feedbackManager = 
                FeedbackServiceManager.getInstance();

             // Initialize connection
             LinkedList<Device> devices = feedbackManager.getDevices( HOST, PORT, 
                certificate, passwd, SSLConnectionHelper.KEYSTORE_TYPE_PKCS12 );
             System.out.println( "Connection initialized..." );

             System.out.println( "Devices returned: " + devices.size() );

             ListIterator<Device> itr = devices.listIterator();
             while ( itr.hasNext() ) {
                 Device device = itr.next();
                 System.out.println( "Device: id=[" + device.getId() +
                " token=[" + device.getToken() + "]" );
             }

             System.out.println( "done" );

         } catch (Exception e) {
             e.printStackTrace();
         }
     }
}

사용자 삽입 이미지

테스트 몇번 해보았을 뿐이라 사용하지 않는 사용자는 존재하지 않는군요. 위와같은 명령을 하루 한번정도의 텀으로 주기적으로 실행하여 발송 목록에서 제거하는 등의 처리를 하시면 되겠습니다. 더 많은 관련 문서는 javapns의 WIKI 페이지에 잘 나와있습니다^^

[삽질] 아이폰3GS 유심을 아이폰4에 꼽아 개통 시도한 후기

어쩌다 보니 아이폰4가 생겼다. 최신형 아이팟터치로밖에 쓸수 없는 아이폰4지만 혹시라도 내 아이폰 3GS의 유심을 꼽아 켜보면 어떻게 될지 궁금증이 생겼다.

아마도 99.9999999% 개통이 안되리라. 하지만 실행에 옮기기로 했다.

사용자 삽입 이미지
왼쪽이 기존의 유심이고 오른쪽이 아이폰4에 들어가는 마이크로 유심이다. 일단 크기부터가 완전히 다르다. 하지만 익히 들어왔듯이 기존의 유심을 잘 잘라 사용하면 마이크로 유심으로 사용할 수 있다는 것을 알고 자르기를 시도하기로 했다.

실제로 잘라보고서 느낀점이지만 위쪽을 제외하면 좌우와 아래쪽은 저 칩부분이 간신히 남을만큼만 남기고 자르면 된다.

사용자 삽입 이미지
자르고 난 모습이다. 오른쪽이 잘려나간 나의 유심이다. 칩부분의 크기가 매우 달라 걱정스럽지만 자세히 보면 기존의 유심의 오른쪽 부분을 사용하지 않으면 마이크로 유심임을 알 수 있다.

사용자 삽입 이미지
크기를 잘 비교해 보았다. 내가 생각해도 손재주 없는편인데 완벽하게 잘 잘려나갔다.

사용자 삽입 이미지
두둥!! 대망의 아이폰4 부팅식….안테나게이지가 보이는가? 실패를 암시하고 있다…그나저나 아이폰4의 액정은 정말 놀라울정도로 환상적이다. 한 5초만 쳐다보고 아이폰 3GS를 보면 80년대로 회귀하는 느낌이다.

사용자 삽입 이미지
열심히 작업한 흔적이다…이제 아이폰4도 못쓰고 아이폰3GS도 못쓰고…ㅠㅠ 내일 KT대리점에 가서 유심 분실했다고 해야겠다.

사용자 삽입 이미지
시간이 지나니 안테나가 안뜨는것도 모잘랐는지 No Service가 뜬다. 어쩌라는거냐….최신형 아이팟아…


작업을 해보고 나니 느꼈는데 처음에 유심을 잘라서 꼽았을때는 USIM Failure가 떴었다. 1mm만 잘못 잘라도 인식이 제대로 안되는듯하다. 다시 조금 삐뚤어진 부분을 좀더 다듬었더니 인식은 잘된다. 하지만 역시나 개통은 되지 않는다.