본 포스팅은 java 서버 기반으로 apns, gcm을 활용해 모바일 기기로 PUSH 메세지를 전송하는 방법에 대해 설명합니다.

모바일 기기 단에서 처리 코드는 포함하지 않습니다 :)


1. APNS 사용하기

애플의 아이폰 등의 기기에 PUSH 메세지를 전송하기 위해서는 다음과 같은 준비물이 필요합니다.


* javaPNS 라이브러리 : http://code.google.com/p/javapns/

* javaPNS에서 사용하는 Bouncy Castle 라이브러리 : http://www.bouncycastle.org/

* PUSH 인증서/인증서 비밀번호

* 디바이스 UDID

* 모바일앱


javaPNS는 Apple Push Notification Service(APNS)를 사용하기 쉽게 만들어진 오픈소스 라이브러리로 GNU Lesser GPL 라이선스를 따릅니다. 


본 포스팅에서 사용되는 javaPNS 버전은 2.2 입니다. 

JVM 버전은 1.5 이상을 쓰면 되지만 현재 1.7 버전에서는 SSL connection오류가 발생할 수 있음에 주의합니다. 

또한 javaPNS는 BouncyCastle에서 제공하는 암복호화 라이브러리에 의존성이 있습니다. BouncyCastle를 온전히 사용하기 위해서는 JCE Policy 파일 패치가 필요할 수 있습니다.


이상 위 준비과정을 마쳤다면, APNS는 아주 쉽게 사용할 수 있습니다.

기본 코드는 아래와 같습니다.

public boolean send(String certificate, String password, boolean production, String udid, String message, Map<String, String> extra) {
	try {
		PushNotificationPayload payload = PushNotificationPayload.complex();
		payload.addAlert(message);
		payload.addBadge(-1);
		payload.addSound("default");
			
		if( extra != null ) {
			Iterator<String> keys = extra.keySet().iterator();
			while( keys.hasNext() ) {
				String key = keys.next();
				String value = extra.get(key);
				
				payload.addCustomDictionary(key, value);
			}
		}

		PushedNotifications notifications = Push.payload(payload, 
				certificate, password, production, udid);
			
		return (notifications != null && notifications.size() > 0 && notifications.get(0).isSuccessful());
			
	} catch (Exception e) {
		logger.error(e, e);
	} 
		
	return false;
}

certificate는 인증서 경로, password는 인증서 비밀번호, udid는 디바이스 고유키 입니다.

extra는 전송 메세지 외에 커스텀 딕셔너리에 넣을 데이터를 처리합니다.


APNS 전송이 수 만건 단위로 많아질 경우 위 코드를 그대로 사용하는 것은 매우 비효율적입니다.  


APNS의 경우 한번 커넥션이 맺어질 경우 해당 커넥션을 통해 다수의 PUSH 데이터를 한번에 전송하는 방식을 사용해야 합니다. 필요에 따라 멀티쓰레드 방식을 통해 PUSH 데이터를 나누어 전송할 수도 있습니다. 


Message Broadcasting, payloadPerDevice, multithread, connection pool과 같은 고급 주제들 또한 javaPNS 문서와 소스에서 힌트를 얻을 수 있으니 반드시 참고하기를 권장합니다.



2. Google Cloud Messaging for Android(GCM) 사용하기

GCM을 사용하기 위해서는 다음과 같은 준비물이 필요합니다.


* Google API Console을 통해 프로젝트 생성

* GCM 활성화

* API Key 및 프로젝트 ID 얻기

* 디바이스 토큰

* 모바일 앱

* gmc-server 라이브러리

* json-simple 라이브러리


먼저 https://code.google.com/apis/console/ 에 처음 방문하면 다음 그림과 같이 프로젝트를 개설하기 위한 과정이 나타납니다.


프로젝트를 생성한후 좌측 메뉴의 Services에서 "Google Cloud Messaging for Android"를 활성화 해줍니다.



그 다음 좌측 메뉴의 API Access에서 아래 그림과 같이 Create New Server Key를 생성합니다.



서버 키를 생성하면서 접근 가능한 서버의 IP를 입력하는 과정이 있습니다. 

정상적으로 서버 키가 생성됬다면 아래와 같이 API키와 허용 IP목록을 확인할 수 있씁니다.



프로젝트 ID는 다음 그림과 같이 URL의 project: 다음의 일련번호를 통해 확인할 수 있습니다.



gcm-server 라이브러리는 안드로이드 SDK의 extras > google > gcm > gcm-server > dist 에서 찾을 수 있씁니다.

만약 gcm이 없다면 안드로이드 SDK 매니저를 통해 설치해야합니다.


gcm-server는 json-simple 라이브러리에 의존성이 있습니다. 

json-simple은 http://code.google.com/p/json-simple/ 에서 다운로드 받을 수 있습니다.


기본 코드는 다음과 같습니다.

public boolean send(String apiKey, String token, String message, Map<String, String> extra) {
	Sender sender = new Sender(apiKey);
		
	try { message = URLEncoder.encode(message, "UTF-8"); } 
	catch (UnsupportedEncodingException ignore) {}
		
	Message.Builder messageBuilder = new Message.Builder();	
	messageBuilder.delayWhileIdle(false);
	messageBuilder.timeToLive(1800); // 30min
	messageBuilder.addData("msg", message);
		
	if( extra != null ) {
		Iterator<String> keys = extra.keySet().iterator();
		while( keys.hasNext() ) {
			String key = keys.next();
			String value = extra.get(key);
				
			messageBuilder.addData(key, value);
		}
	}
		
	try {
		Result result = sender.send(messageBuilder.build(), token, 5);
		String messageId = result.getMessageId();
			
		return (messageId!=null);
	} catch(Exception e) {
		logger.error(e, e);
	}			
		
	return false;
}

APNS와 마찬가지로 많은 양의 PUSH 메세지를 위 코드를 통해 처리하는 것은 매우 비효율 적입니다.


C2DM에서 GCM으로 넘어오며 얻을 수 있는 큰 장점중의 하나는 Message Broadcasting이 가능해졌다는 것입니다.

아직 경험적/통계적으로 GCM 에서의 적절한 connection과 multithread 갯수는 파악하지 못했지만, Message Broadcasting 기술과 delayWhileIdle, timeToLive의 설정값을 통해 PUSH 메세지 도달율을 C2DM보다 높힐 수 있을것이라 기대합니다. 


Posted by devop

댓글을 달아 주세요

요즘 널리 쓰이는 구글 안드로이드 스마트폰.
다들 잘 알고 있다시피 안드로이드 앱 개발은 자바(JAVA)로 합니다.

JAVA로 개발된 프로그램은 C와 같은 언어와 달리 바이트 형태의 class파일을 생성하는데 'Write Once, Run Anywhere"라는 Java의 패러다임에서 알수 있듯이, JavaVM이 존재하는 모든 시스템에서 실행될수 있는 시스템 독립적인 코드입니다.
따라서, 바이트 코드와 같은 경우는 바이너리 코드와 달리 손쉽게 역컴파일이 가능합니다.

* 참고 : http://lyb1495.tistory.com/entry/Java-Decompiler-jad

안드로이드 앱 역시 여기서 예외일 수 없는데, apk 파일만 가지고 있으면 손쉽게 디컴파일을 통해 소스코드를 확인할 수 있습니다.

준비물

1. dex2Jar
apk파일의 압축을 해제하면 classes.dex 라는 파일을 발견할 수 있는데, 이 파일은 안드로이드 가상 머신인 dalvik이 인식할 수 있도록 class파일을 바이트 코드로 변환 된 파일입니다.

  
dex2jar는 dex파일로부터 class 파일을 추출하는 도구 입니다.
dex2jar로부터 추출된 class파일은 JAVA 디컴파일러를 통해 소스코드로 복원해 낼 수 있습니다.

다운로드 : http://code.google.com/p/dex2jar/downloads/list

2. jad
class 파일을 java파일로 복원하는 툴

다운로드 : http://www.varaneckas.com/jad


준비물이 갖추어졌으니 본격적으로 apk 파일을 디컴파일 해보겠습니다.

먼저 아래 그림과 같이 apk파일을 압집을 통해 압축을 해제합니다. 


 apk파일의 압축을 해제하면 classes.dex파일을 확인할 수 있는데 이 파일을 dex2jar를 통해 다시 한번 압축을 해제하게 됩니다. 사용법은 다음과 같이 간단합니다.

 (dex2jar가 설치된 폴더에서) dex2jar 대상dex파일 


dex2jar를 통해 dex파일 압축을 해제하면 다음 그림과 같이 classes_dex2jar.jar 파일을 확인할 수 있습니다.

 
이 파일은 class파일을 압축하고 있는 파일로 압집등을 통해 간단히 압축을 해제하면 class파일들을 확인할 수 있습니다.


이제 jad를 통해 class파일들을 java파일로 복원합니다.
아래 명령은 -dsrc 로 지정한 디렉토리(C:\android\decompiler\parceltrace-2\classes_dex2jar)의 모든 class파일을 디컴파일하는 명령입니다.


위 명령을 수행한뒤 jad가 설치된 폴더를 보면 다음 그림과 같이 java파일들이 생성된 것을 확인할 수 있습니다.
(jad의 -d 옵션을 통해 소스가 생성되는 디렉토리 위치를 변경할 수 있습니다. 자세한 사용법은 다운로드 받은 jad 에 첨부된 readme.txt 파일을 참조)

  

Posted by devop

댓글을 달아 주세요

  1. 문의

    디컴파일 하나만 해주시면 안될까요 ㅠㅠ?

    2012.05.25 00:54 [ ADDR : EDIT/ DEL : REPLY ]

출처: http://blog.sopt.org/category/동아리이야기/개발파트


AVD(Android Virtual Device)를 하나 만듭니다. Virtual Devices탭에서 New버튼을 클릭하세요.


아래와 같은 창을 볼 수 있는데 Name에 자신이 원하는 아무 이름이나 입력하고 Target에는 Android 2.1이나 Google APIs 둘 중에 하나를 선택합니다. (Google APIs는 지도를 이용한 어플리케이션을 개발할때 필수) 

Create AVD 클릭~~!!!!


아래와 같이 하나의 AVD(Android Vritual Device)가 생성된 것을 볼 수 있습니다. 
Start버튼을 클릭해 에뮬레이터를 구동시킵니다.


에뮬레이터가 구동되면 아래와 같은 부팅화면을 볼 수 있습니다.
아직 에뮬레이터가 최적화가 많이 안 되어있는지 많이 느리네요 ㅠ.ㅠ


아래와 같은 초기화면이 나올때까지 기다리다가 초기화면이 나오면 아래에 탭을 클릭합니다.



다음과 같은 아이콘들을 볼 수 있는데 여기서 Settings를 클릭합니다.


다음과 같은 리스트가 나오는데 리스트를 약간 아래로 내려서 Language&keyboard를 클릭합니다.



다음과 같은 화면이 나오는데 여기서 Select locale을 클릭한 후 나오는 목록중에서 한국어를 선택합니다.



다음과 같이 메뉴가 한글로 변경된 것을 볼 수 있습니다. 
여기까지하면 안드로이드 에뮬레이터의 언어가 한글로 변경됩니다.


다음으로는 현재 에뮬레이터에 있는 키보드로는 한글을 입력할 수 없으니 한글키보드를 추가로 설치하셔야 합니다.

첨부된 HangulKeyboard.apk 파일을 다운로드 받고 커맨드창을 실행하여 다운로드받은 폴더에서 "adb install HangulKeyboard.apk"라고 입력한 후 실행하면 에뮬레이터안에 한글키보드가 설치됩니다.

★ 주의 : 이 명령을 수행할때는 에뮬레이터가 실행되어 있는 상태에서 수행해야 한글키보드가 설치됩니다.


위와 같은 명령을 수행하면 아래와 같이 한글 키보드가 설치된 것을 확인할 수 있습니다.
여기까지하면 우리가 사용하는 한글환경으로 에뮬레이터 설정이 끝나게 됩니다.


Posted by devop

댓글을 달아 주세요

 안드로이드 어플리케이션은 자신의 프로세스 수명을 직접 제어하지 않으며, 안드로이드 런타임이 각 어플리케이션의 프로세스와 그 안에 있는 각 activity를 관리한다.
 런타임이 프로세스와 activity의 종료와 관리를 다루는 동안, activity의 상태는 자신의 부모 어플리케이션 프로세스의 우선순위를 결정하는데 사용된다. 어플리케이션 우선순위는 런타임이 어플리케이션과 그 안에 실행중인 activity를 종료시킬 가능성에 영향을 미치게된다.
 
1. activity stack
 각 activity의 상태는 현재 실행 중인 모든 activity의 stack구조에서 activity가 차지하는 위치에 의해 결정된다. 새로운 activity가 시작되면 현재 foreground화면이 stack의 맨 꼭대기로 옮겨진다. 사용자가 뒤로 가기 버튼을 사용해 뒤쪽을 탐색하거나 foreground activity가 종료될 경우에는 이 stack의 최상위에 있는 activity가 활성상태가 된다.


 어플리케이션의 우선순위는 어플리케이션이 가진 activity가운데 가장 높은 우선순위를 가진 것에 의해 영향을 받는다. 안드로이드 런타임이 자원 해제를 위해 어떤 어플리케이션을 종료시켜야 하는지 결정할 때, 어플리케이션의 우선순위를 그들이 가진 activity에 기반을 두고 결정하기 위해 위 stack이 사용된다.


2. activity 생명주기
activity가 생성되고 종료되는 과정 동안 activity는 4가지의 상태를 전이한다.

활성(Active)
activity가 stack의 최상위에 있을 경우 이 activity는 현재 화면에 보이고 사용자 입력을 받는다. 안드로이드는 무슨 일이 있어도 활성 상태에 있는 activity가 살아있도록 노력하며, 이 activity가 필요로 하는 리소스를 확보하기 위해 필요에 따라 stack의 아래쪽에 있는 activity들을 종료시킬 수 있다. 다른 activity가 활성화되면 기존의 활성 activity는 일시 중지(Pause)된다.

일지 중지(Pause)
경우에 따라 activity는 화면에는 보이지만 포커스를 지니지 않을 수 있다. 이 상태는 투명한 activity나 화면 전체를 사용하지 않는 activity가 그 앞에 활성화되어 있는 경우이다. 일시 중지 상태가 되는 경우 activity는 활성 상태인것처럼 다뤄지지만 사용자 입력을 받지 않는다. 극단적인 경우, 안드로이드는 활성 activity를 위한 리소스 확보를 위해 일시 중지 상태의 activity를 종료시킬 수 있을것이다. 만약 activity가 완전이 가려지게 되면 그 activity는 중지상태가 된다.

중지(Stop)
activity는 화면에 보이지 않는다. 이 activity는 모든 상태 및 멤버 정보를 메모리에 남기지만, 만약 시스템이 활성 activity를 위해 메모리를 요청할 경우 리소스 확보를 위한 정리 후보 1순위가 된다. activity가 중지될 때는 데이터와 현재 UI상태를 저장하는 것이 중요하다. activity가 화면 밖으로 나가거나 닫히고 나면 그 activity는 비활성 상태가 된다.

비활성(Inactive)
activity는 종료되고 난 이후와 시작되기 이전 비활성 상태에 머문다. 비활성 activity는 activity stack에서 제거되며, 화면에 다시 나타내기 위해서는 재시작되어야 한다.


 activity가 시작되고 실행중일 때 다른 activity가 시작되면 그때까지 실행중이던 activity는 pause 상태로 전이된다. pause 상태로 존재하다 다른 어플리케이션에서 메모리를 요구할 경우 pause상태의 activity는 종료될 수 있다.
 만약 pause상태에 있던 activity를 사용자가 선택해서 다시 foreground로 되돌아가면 resume상태가 된다. 또 pause 상태에 있던 activity가 더이상 보이지 않게 되면 stop상태로 전이된다.
 stop상태에 있던 activity가 다시 foreground로 되면 restart에 의해 start상태로 돌아간다. stop상태에 있는 activity 역시 다른 어플리케이션이 메모리를 요구할 경우 종료되며 destroy로 전이될수도 있다.

다음은 activity에서 사용할 수 있는 각 상태변화에 대한 핸들러 메소드이다.


 activity의 상태전이는 비결정적이며 전적으로 안드로이드 런타임에 의해 다루어 진다. 안드로이드는 비활성 activity를 가진 어플리케이션을 종료하는 것으로 시작해, 이어서 중지된 것들, 그리고 극단적인 경우 일시 중지된 것들을 제거할 것이다.
 심리스한 사용자 인터페이스를 보장하기 위해서는 이들 상태 간의 전이가 사용자의 눈에 보여서는 안된다. 일시 중지나 중지 혹은 종료된 상태에서 다시 활성 상태로 이동하는 activity 간에는 차이점이 없어야 하므로, activity가 일시 중지 되거나 중지될 때는 모든 UI상태 변화를 저장하고 모든 데이터를 지속시키는 것이 중요하다. 만약 중지되거나 비활성된 activity가 다시 활성화되고나면 저장된 이들 값을 이용해 activity를 복구해야 하기 때문이다.
Posted by devop

댓글을 달아 주세요

 안드로이드 어플리케이션은 자신의 생명 주기를 제어할 수 없다. 대신, 어플리케이션 컴포넌트는 반드시 어플리케이션의 상태변화에 귀기울여 그에 따라 적절히 반응해야 하며, 불시 종료에 대비하도록 신경 써야 한다.
 각 안드로이드 어플리케이션은 기본적으로 별도의 Dalvik 인스턴스를 실행하고 있는 자기 자신만의 프로세스 내에서 실행된다. 각 어플리케이션의 메모리와 프로세스 관리는 런타임에 의해 배타적으로 처리된다.
 안드로이드는 자신의 리소스를 공격적으로 관리하며, 장치가 좋은 반응성을 가진 상태로 남아있도록 보장하기 위한 것이라면 무엇이든 한다. 이는 우선순위가 높은 어플리케이션을 위해 리소스 확보가 필요한 경우, 우선순위가 낮은 프로세스가 경고없이 종료될 수 있음을 뜻한다.

1. 활성 프로세스(foreground activity)
 활성 프로세스는 사용자가 현재 상호작용하고 있는 컴포넌트를 가진 어플리케이션이다. 안드로이드가 리소스 회수를 통해 좋은 반응성을 가진 채로 유지하려는 프로세스가 바로 이 프로세스다. 이들 프로세스는 일반적으로 매우 적은 수가 존재하며, 최후의 수단으로서만 종료될 것이다.

  • 활성상태에 있는 Activity. 즉, 이들은 foreground 에 있으면서 사용자 이벤트에 반응한다.
  • 현재 onReceive 이벤트 핸들러를 실행 중인 Activity나 서비스 또는 broadcast 수신자.
  • onStart나 onCreate 또는 onDestroy 이벤트 핸들러를 실행 중인 서비스

2. 화면에 보이는 프로세스(visible activity)
 화면에 보이지만 비활성 프로세스는 화면에 보이는 Activity를 가지고 있는 것들이다. 화면에 보이는 Activity는 눈에 보이긴 하지만 foreground에 있지 않거나 사용자 이벤트에 반응하지 않는다. 이는 Activity가 부분적으로 가려지는 경우 발생한다. 이러한 프로세스는 일반적으로 매우 적은 수가 존재하며, 활성 프로세스가 계속 진행되도록 허용하기 위한 극한 상황에서만 종료될 것이다.

3. 시작된 서비스 프로세스
 서비스는 화면에  보이는 인터페이스 없이 계속실행되어야 하는 처리를 지원한다. 서비스는 사용자와 직접 상호작용하지 않기 때문에, 화면에 보이는 Activity에 비해 낮은 우선순위를 부여 받는다. 하지만 시작된 서비스 프로세스는 foreground 프로세스로 간주되며, 활성 프로세스나 화면에 보이는 프로세스를 위해 리소스가 필요한 경우가 아니라면 종료되지 않을 것이다.

4. Background 프로세스
 화면에 보이지 않는 Activity를 가지고 있으며 시작된 서비스를 가지지 않는 프로세스이다. 안드로이드가 foreground 프로세스를 위한 리소를 얻기 위하여 last-seen-first-killed pattern을 이용해 종료시킨다. 일반적으로 많은 수가 존재한다.
 만약 background 프로세스를 제거한 후 사용자가 그 activity로 다시 돌아가길 원한다면 해당 화면을 다시 보여주어야 하는데, 이때는 activity의 onCreate(Bundle saveInstanceState) 메소드의 saveInstanceState 파라미터를 이용한다.
 background activity를 제거할때 onSaveInstanceState(Bundle saveInstanceState)메소드가 호출되는데, 이 때 중요한 데이터를 saveInstanceState에 저장할 수 있다. saveInstanceState 파라미터에 저장한 내용은 onCreate() 매소드의 파라미터로 전달된다. 따라서 이 파라미터를 이용하면 background activity가 제거되기 전의 상태로 다시 시작할 수 있다.

5. 빈(Empty) 프로세스
안드로이드는 전체적인 시스템 성능 향상을 위해, 어플리케이션이 수명을 다한 뒤에도 종종 이들을 메모리에 존속시킨다. 안드로이드는 이들이 다시 띄어질때 어플리케이션 구동 시간을 향상 시키기  위하여 이 Cache를 이용한다. 이들 프로세스는 메모리가 부족해지면 시스템에 의해 즉시 제거된다.

Posted by devop

댓글을 달아 주세요