REWIND I/O #1: 젤리빈, 그리고 검색의 미래

구글 I/O가 끝나갑니다. 일반인들의 관심이 높은 키노트는 벌써 두 차례 모두 지나갔죠. 저도 이제 짐을 쌀 시간입니다. 기사도 열심히 쓴다고 썼지만, 보고 듣고 배운 건 많은데 써놓은 내용은 턱없이 적었습니다. 지면 제약으로 못 다 적은 얘기들을 하나씩 나눠서 적어볼 생각입니다.

첫번째는 젤리빈입니다. 구글에서는 안드로이드 개발자들에게 직접 써보고 느끼면서 제대로 앱을 만들어보라는 취지로 참가자 모두에게 갤럭시넥서스 스마트폰과 넥서스7 태블릿, 넥서스Q 미디어허브를 모두 제공했죠. 그래서 저도 덕분에 폰과 태블릿에서 이틀 동안 열심히 젤리빈을 써봤습니다. 버전 번호로는 겨우 4.0에서 4.1로 업데이트됐을 뿐이지만, 체감하는 느낌은 이보다 훨씬 더 개선됐습니다. 기본적인 아이스크림샌드위치의 구조는 거의 손대지 않은채 사용자 편의성 부분만 대폭 개선한 덕분일 겁니다.

갤럭시넥서스가 처음 나왔을 때, 안드로이드폰의 고질적인 문제였던 카메라 기능이 대폭 개선됐다는 점에서 많은 분들이 높게 평가하셨죠. 셔터랙도 거의 없고, 잠금 화면에서 바로 카메라로 빠르게 접근 가능하다는 점이 유용했습니다. 물론 구글의 클라우드 저장공간에(구글플러스의 사진 앨범에) 찍은 사진들이 바로바로 올라가 저장된다는 점도 장점이었습니다. 애플의 사진스트림 기능처럼 1000장 제한 등이 있는 게 아니라, 저장공간 용량이 허락하는 한 사진은 계속 올라가 저장되는 것도 특징이었죠. 이번에는 키노트에서 밝힌 대로 사진 촬영 뒤의 확인 기능이 대폭 개선됐습니다. 촬영을 하다가 스크린을 좌우로 밀면 바로 촬영된 영상 확인이 가능하죠. 잠깐, 이거 어디서 많이 본 기능입니다. 아이폰의 카메라 촬영과 리뷰가 딱 이런 식으로 작동하거든요. 애플이 곧 구글에 디자인 침해 소송이라도 걸지 모를 일입니다. 알림 기능도 개선되어 알림에 뜬 내용을 탭하면 바로 다음 작업으로 넘어갑니다. 이 또한 iOS에서 진작에 되던 기능입니다. 하지만 애플도 이런 식으로 iOS5부터 안드로이드의 기능을 잔뜩 베껴오기 시작했죠. 위에서 아래로 쓸어내리는 제스처가 알림 화면을 보여주는 것 자체가 안드로이드에서 먼저 시작한 것이니까요. 서로가 서로를 베끼는 세상입니다. 이러면서 특허전쟁을 벌이는 걸 보면 거참…

하지만 젤리빈의 압권은 역시 ‘구글 나우’입니다. 딱 이틀, 그것도 영어로 썼을 뿐인데 정말 엄청나게 편리하더군요. 오른 쪽에 ‘열기’, 왼쪽에 ‘카메라’가 있던 기존의 잠금화면 동그라미가 젤리빈부터는 위쪽으로 ‘구글’ 로고가 하나 더 생긴 모양으로 바뀌었습니다. 잠긴 스마트폰을 위로 밀어 열 때마다 바로 구글 검색창이 나오는 방식입니다. 그리고 그렇게 자동으로 열리는 검색결과는 내가 입력한 내용에 대한 결과가 아닙니다. 구글 나우가 알아서 ‘내게 필요한 정보’를 판단해 검색해 놓은 결과가 담겨 있습니다. 기가 막히는 기능입니다. 제 경우를 보면, 구글 I/O 세션 가운데 듣고 싶은 세션을 캘린더에 저장해 놓았을 뿐이었는데도 스마트폰을 열었더니 바로 다음 일정 안내가 나오더군요. 아무 일도 없이 아침에 일어나 스마트폰을 열었을 땐 오늘의 날씨가 나왔습니다. 한 번은 조금 떨어진 곳으로 이동하려고 장소를 지도에서 검색하다가 잠시 다른 일이 생겨 지도 검색을 잊고서 다른 일을 했던 적이 있었습니다. 그 때 스마트폰을 무심코 집어들었더니 내가 직전에 찾아보던 장소까지 가는 길 가운데 막히지 않고 이동하는 경로를 찾아놓았던 겁니다. 구글 I/O앱을 쓰기 위해 임시로 갤럭시 넥서스를 쓰다가 다시 아이폰으로 돌아오려니까 가장 아쉬웠던 게 바로 이 구글 나우 기능입니다.

구글 나우에는 구글이 미국에서만 우선적으로 선보였던 ‘지식그래프’(Knowledge Graph) 검색 기능이 사용됐습니다. 전날 벤 곰즈 부사장과 인터뷰할 때 지식그래프는 모바일의 작은 화면에서 어떻게 사용하느냐 물어본 일이 있었습니다. 벤은 시침을 뚝떼고 아이폰 화면을 보여주면서 “어찌어찌 볼만하다”고 설명하더군요. 그런데 이날 왜 그랬는지 이유가 발표됐습니다. 모바일에선 데스크톱 형태를 줄여놓은 형태로 지식그래프 검색결과를 보여주는 대신, 카드 형태로 보여주는 형태를 디자인했던 겁니다. 알고리듬이 자동으로 찾아낸 정보이면서도 마치 사람이 손으로 답을 골라준 것처럼 보이게 말이죠. 여기에 음성검색을 결합시키니까 애플의 시리(Siri)와 비슷한 느낌입니다. 예를 들어 “누가 한국 대통령이지?”라고 물으면 이명박 대통령 사진이 담긴 카드를 보여줍니다.

구글 나우는 지식 그래프와 스마트폰의 정보, 새로운 디자인 등을 통합한 독창적인 모바일 검색입니다. 스마트폰은 기본적으로 내가 지금 어디 있는지를 늘 파악하고 있습니다. 안드로이드폰은 여기에 더해 사용자의 구글 계정과 구글이 통합 수집한 개인정보까지 이용할 수 있죠. 그러면 안드로이드폰은 나의 취향과 일반적인 이동경로 등을 다 파악하게 됩니다. 이외에도 지금이 몇시인지, 오늘 뉴스가 무엇인지, 주가는 어떻게 변하고 있으며 스포츠 중계는 어떻게 끝나는지 등이 함께 파악됩니다. 인터넷과 연결돼 있으니 사람이 신경쓸 필요 없이 기계가 미리 이런 정보들을 받아두면 되거든요. 구글 나우는 이 모든 정보를 활용해 개인별 맞춤형 정보를 알려줍니다. 집에 가는 길에 평소 퇴근길 길이 막히면 우회로를 알려준다거나, 낯선 길을 걷고 있으면 인근에 새로 가볼 만한 곳을 알려주는 식입니다.

환상적인 기능이긴 한데, 이거 약간 섬찟합니다. 기계가 나를 너무 잘 알고 있으니까요. 어찌 보면 우리 가족들보다도 핸드폰이 나를 더 잘 파악하게 되는 게 아닌가 싶기도 합니다. 왜, 일 중독인 사람들이 가족보다 비서와 훨씬 더 많은 삶을 공유하는 것처럼 말입니다. 어쨌든 구글 검색의 궁극적 목표는 검색이 필요없는 검색이라고 합니다. 과거에 이런 식의 검색에 대한 내용을 이 블로그에서 약간 다룬 적도 있습니다. 앰비언트 파인더빌리티(Ambient Findability)에 관한 얘기였습니다. 번역하자면’ 어디에나 존재하는 발견가능성’ 정도가 될까요? 아직도 적절한 한국어 번역을 못 찾겠습니다. 내용을 보자면, 미래의 인터넷은 우리가 무언가를 능동적으로 ‘찾아내는’ 방식으로 움직이는 대신 우리가 무언가를 ‘발견’할 수 있도록 도와주는 검색을 해준다는 얘기입니다. 그러니까 그냥 숨만 쉬어도 정보가 알아서 주위에서 발견되는 앰비언트 파인더빌리티의 미래가 다가오고 있다는 것이죠. 3년 전 이 시리즈를 연재할 때만 해도(그 땐 아이폰도 들어오기 전이었습니다.) 제가 이런 얘길 하면 무슨 허무맹랑한 소리처럼 들으시는 분들이 많았습니다. 하지만 3년 만에 구글은 공상과학을 현실로 만들어내기 시작했습니다. 진짜 대단한 회사입니다.

산업적인 의미도 큽니다. 휴고 바라 안드로이드총괄 이사는 구글 나우가 자동차와 결합하면 차가 얼마나 똑똑하게 진화할는지 기대하라고 얘기했습니다. 애플은 이미 주요 자동차 업체들과 손잡고 시리를 이용한 초기 단계의 스마트카 사업에 한 발을 담그고 있습니다. ‘Eyes Free’라는 이름으로. 구글도 당연히 이 수순으로 가게 될 겁니다. 구글이 검색 사업을 하면서 애플에게 밀린다? 구글 입장에서는 상상도 할 수 없는 일일 겁니다. 젤리빈은 그런 측면에서 구글이 얼마나 대단한 회사인지를 증명하는 서비스였습니다.

참, 또 한 가지. 구글의 푸시 서버 서비스인 C2DM도 함께 소개됐습니다. 메신저 업체들에게 맘껏 이용하라는 얘기죠. 애플은 벌써 몇년전부터 안정적으로 제공하던 기능인데, 이제야 본격적으로 무제한 서비스를 시작한다니 좀 늦었다는 생각입니다. 인프라는 세계 최고인 구글이 왜이리 늦었을까요. 아무래도 푸시에 대한 고민이 처음에 그리 깊지 않았으리라는 생각이 듭니다. 어쨌든 이제 카카오 같은 회사는 신났군요. 통신사 통하지 않고도 큰소리 치면서 메신저 사업을 벌일 수 있는 기반이 생긴 셈입니다.


출처 - http://interpiler.com/2012/06/29/rewind-io-1-%EC%A0%A4%EB%A6%AC%EB%B9%88-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EA%B2%80%EC%83%89%EC%9D%98-%EB%AF%B8%EB%9E%98/


'Android > Common' 카테고리의 다른 글

android - app 버전 관리  (0) 2012.10.07
service - Service API 변경  (0) 2012.09.28
android - SharedPreferences  (0) 2012.09.24
android - 이클립스 환경 설정  (0) 2012.09.21
android - 하이브리드 앱(웹)  (1) 2012.09.19
Posted by linuxism
,


Rails를 이용한 아이폰, 안드로이드 통합 푸시 서버(third party) 만들기

 

들어가기에 앞서

안녕하세요 @daramm.g 입니다. 이번 포스팅은 Ruby on Rails를 이용해 아이폰과 안드로이드를 통합 관리하는 푸시 서버를 만드는 과정에 대한 것입니다. 

‘푸시서버? 그게 뭐지? 그.. 메세지 보내기 위해 필요한.. 음, 뭐 그런거?’ 정도만 알고 있으셔도 이 글을 읽는데는 큰 무리가 없으실 거에요.

왜냐면.. 저도 아무것도 몰랐거든요. ㅎㅎ

내용에 들어가기에 앞서 구현에 많은 도움을 주신 태연님과 현학님, 그리고 재현님께 깊은 감사를 드립니다.

자 그럼 푸시서버가 무엇인지, 왜 필요한지에 대하여 알아보도록 하겠습니다.

 

푸시서버의 개념과 구현하려는 푸시서버의 요구사항

제가 구현하려고 하는 push server는 애플에서 제공하는 apn(ios), 구글에서 제공하는 c2dm(android) 서버에 푸시(메세지) 요청을 보내는 third-party 서버입니다.

apn에 대하여 살짝 부연설명을 덧붙이자면 apn은 애플에서 제공하는 푸시 서비스로 apn에 푸시 요청을 보내면 apn이 각 디바이스(아이폰, 아이패드등)에 푸시 알림을 보내는 형태입니다.

c2dm은 구글에서 제공하는 푸시 서비스로 기능은 대부분 apn과 같습니다.(다만 c2dm은 android 2.2 버전, 즉 프로요 이상의 디바이스에만 푸시 알림을 보낼 수 있고 apn과 조금씩 다르지만 글을 쓰면서 언급하도록 하겠습니다)

 

(그림으로 보면 위와 같습니다)

그럼 제가 만들 푸시서버의 요구사항을 알아보도록 하겠습니다.

1. 푸시는 웹페이지에서(urban airship과 유사한) 전송할수 있고 api 요청으로 전송되어야 한다.

2. 서비스별 os_type 별 통합 및 별도 관리 전송이 가능해야 된다.

3. 앱에 로그인한 user 기반으로 device가 관리되어야 된다.(‘a’라는 user는 아이폰도,겔럭시s를 동시에 가질수도 있다.)

4. 전체 device(로그인하지 않은 device 포함)에 보낼 수 있는 기능과 특정 user에게 혹은 user list를 받아 푸시를 보내는 것이 가능해야 된다.

그리고 이것을 테스트하기 위해 필요한 사항에 대해서 알아보도록 하겠습니다.

1. device를 등록할 수 있고 push 전송 받을 ios device 2개, android device 2개.

2. push를 테스트할 push기능이 가능한 test 앱 2개(ios,android 각각)

3. ios push test 앱을 만들고 나서 앱 인증서. android의 경우 해당 앱을 마켓에 등록한 개발자 id, 비밀번호, 앱 이름

※ 푸시가 잘 오는지만 확인하기 위해서는 앱과 device가 하나씩만 있어도 됩니다.

 

Gem을 이용한 푸시 테스트

Rails 개발 환경에 대하여 언급을 하자면 linux(ubuntu 10.04)에 ruby 1.92(p180), rails3를 사용하였음을 알려드립니다.

제가 푸시서버를 만들면서 제일 먼저 찾아본 것이 gem 입니다. 마침 apn_on_rails 과 c2dm_on_rails이란 이름으로 gem이 있었으나 통합하여 쓰려면 일단 많은 부분을 고쳐서 써야되고 c2dm 의 경우 rails3에서 돌아가게 하려면 많은 부분을 고쳐야 됩니다. 2011/07/20일 즈음에는 rails3로 개발된 c2dm_on_rails가 없었으니 날짜가 많이 지났다면 다시 찾아보시는 것도 좋습니다 ^^

제가 사용한 gem 의 링크를 아래 적어 놓겠습니다.

apn_on_rails : https://github.com/natescherer/apn_on_rails   (readme 에 써있는데로 따라하면 잘 됩니다)

c2dm_on_rails : https://github.com/mmassaki/c2dm_on_rails (rails2버전으로 제작된 것으로 보이며 여러 수작업을 해줘야 돌아갑니다)

c2dm_on_rails gem을 rails3에서 사용할 수 있게 고치는 과정은 아래의 링크를 클릭하시면 확인할 수 있습니다.

c2dm_on_rails gem을 rails3에서 동작하게 변환하는 작업

이렇게 apn_on_rails 와 c2dm_on_rails gem을 설치 후 해당 gem의 git 페이지 readme를 잘 보면서 따라하고 각 device에 푸시 테스트를 완료하였다면 일단 기본적인 기능은 완료된겁니다.^^(전 여기서 쬐금 감동 ㅠㅠ)

※ 모든 가입자에게 푸시를 보낸다는 요구사항만 있으면 여기까지만 테스트 후에 device 등록 및 푸시 전송하는 api만 구현하면 됩니다.

 

User 기반 통합 푸시 서버 구현

위의 테스트를 하였다면 이제 대충 어떤식으로 푸시가 디바이스에 전송되는지 아실거라고 생각됩니다.

간략하게 설명을 하자면 device 모델에는 각 device의 정보가 들어갑니다. 그리고 그 device모델을 참조하는 notification 모델에는 해당 device_id와 alert(보낼 메세지), properties(속성), sound 등등이 들어갑니다. 요 notification모델에서 apn이나 c2dm 서버로 푸시 요청을 하는 것이지요. 

따로따로 메세지를 보내는 것은 가능하지만 User기반으로 ios와 android 통합관리를 위해서는 아직 많이  부족합니다.  User란 login한 user명을 나타냅니다. 카카오톡으로 생각하면 핸드폰 번호가 user가 됩니다.
여튼 저는 통합관리를 위해 apn_on_rails와 c2dm_on_rails의 모델을 싹 다 갈아 엎었습니다.(갈아 엎으면서 코드 변경은 불가피 합니다 ㅠㅠㅠ)

먼저 User라는 모델을 새로 만들었고 device라고 각각 되어있는 모델도 android_user와 ios_user로 명칭을 바꾸어 다시 만들었습니다. 또 ios_device,android_device모델은 User 모델을 참조하여 통합관리가 가능하도록 하였습니다. 예를들어 ‘a’라는 User는 안드로이드든 아이폰이든 상관없이 가질 수 있게 되는거죠. 또 ‘a’한테 푸시를 보낸다고 하면 ‘a’  user와 연결되어 있는 ‘a’  user를 참조하고 있는 android device와 ios device에게 푸시를 보내게 되는것이구요.

notification을 만들어서 apn 및 c2dm에 보내는 과정은 apn_on_rails 와 c2dm_on_rails gem에 있는 함수를 그대로 이용합니다.(apn_on_rails 와 c2dm_on_rails gem에서 connection.rb 파일에 각 서버에 접속 및 전송 하는 부분 구현되어 있습니다 ^^)

 

웹 Interface를 통한 전송

 

rails의 scaffold로 모델및 컨트롤러를 만들면 기본적으로 create라던가 new 라던가 delete라던가 하는 부분들이 restful하게 자동 생성됩니다. 아래 보시는 화면도 결국 new액션으로 새로운 push 요청을 만드는 화면(form)입니다.

서비스를 선택할 수 있고 os_type도 정할 수 있으며 user list도 적을 수 있죠. 이렇게 되면 기존의 요구사항을 충족하게 되죠.(사실 요구사항에 대한 이해도 잘 못했고 부분적으로 바뀐부분이 많아 부시고 만들고의 과정을 많이 거친 인터페이스입니다 ^^) 예쁘다거나 ux친화적이지는 않지만 기본적인 기능은 모두 제공을 하고 있습니다.

 

(Web interface 를 통한 push 전송)

꼼꼼히 정하고 나서 마지막 push 보내기를 누르게 되면 push가 apn과 c2dm 서버로 전송되게 됩니다.

이 과정을 좀더 깊게 살펴 보면 push 보내기 버튼을 누르면 create 액션이 작동하게 되고 form에서의 정보를 바탕으로 service, os type 그리고 user list의 분기를 거쳐 각각 위에서 만들었던 전송 함수를 실행하게 됩니다.

 

기타 기능 및 주의해야 될 점

아래 화면은 푸시를 보내고 나서 보냈던 푸시 리스트를 확인하는 화면입니다. 또 기타 기능으로 실수로 보낸 푸시 멈추기, 실패한 푸시 다시 보내기 등의 기능이 있고, 전체 푸시 count 수와 success count, 실패한 fail count를 표시해 주고 있습니다.

(보낸 Push 리스트를 보여주는 화면)

푸시 멈추기의 경우 푸시를 전송하는 과정이 비동기로 처리가 되어 있어야 가능한데요.(보내기 버튼을 누르고 바로 전송되어 버리면 멈추기가 의미가 없겠죠? 또 천개 이상의 메세지를 보낸다고 했을때 다 보낼때까지 해당 웹 어플리케이션 전체가 상당시간 멈춰있겠죠? 그러면  안되기 때문에 비동기로 처리해 주는 것은 필수 입니다)

 

주의할 사항

전송과정의 background 처리

저의 경우 rake task를 통해 전송 과정을 백그라운드로 처리하였습니다. 관련해서 참고하실만한 rails cast를 링크로 걸어드릴게요 ^^
rails task 백그라운드 처리 http://railscasts.com/episodes/127-rake-in-background  (이거 찾고 엄청난 감동이.. 결국 & 하나 차이였다는 ㅠ)

Message size validate

apn에서 받을 수 있는 total message size는 256byte입니다. 그런데 device token 등을 빼고나면 231byte 정도만 message로 사용할 수 있게 되죠. 그런데 json으로 날라가기때문에 key 값은 또 허무하게 날라간다고 보시면 실제로 push 메세지로 보낼 수 있는 양은 상당히 줄어들게 됩니다. (왜 아이폰에서 몇십줄씩 메세지로 안오는지 아시겠죠? ㅎㅎ) 그래서 전송전에 미리 bytesize validate를 해주시는게 좋습니다.(gem에서 255byte이상일 경우 전송하지 않게 되어있습니다).

그에 비해 c2dm의 경우 1024byte의 제한이 있는데 1024byte라 함은 푸시전송으로 쓰기에는 충분히 남는 양이기 때문에 그다지 신경 안써주셔도 됩니다.

Device에서 앱이 삭제되어서 푸시가 공중으로 떠버리는 경우

만약 제 스마트폰에서 카카오톡을 지웠다고 하면 카카오톡은 저에게 계속 메세지를 보내게 될까요?..그게 나뿐만 아니고 수많은 사람의 푸시가 계속 다시 보내지게 된다면?.. 에 대한 문제입니다.

다행히 apn과 c2dm 모두 그에 대한 해결책을 주고 있는데요. apn의 경우 feedback service를 통해 지워진 device를 체크합니다. 이게 뭐냐면 앱이 해당 디바이스에서 지워지고 앱관리자가 push를 보냈다면 apn 서버가 이것을 기억하고 있다가 해당 앱으로 feedback 요청(apn_on_rails에서 rake task로 지원하고 있습니다)을 하면 앱에서 지워진 token(각 device마다 있는 고유의 키)을 보내주는 서비스입니다.

근데 정말 황당한 것은 그 device에 푸시가 가능한 다른 앱이 하나라도 있어야 된다는 것입니다. 즉 제 아이폰에 ‘푸시 가능한 앱이 하나있었는데 그것을 지웠다’ 라고 하면 아무리 feedback 요청을 해도 해당 token은 지워진 리스트는 넘어오지 않게 되죠. 사실 푸시 가능한 앱이 하나밖에 없다는게 거의 말이 안되지만 test앱의 경우 보통은 다  apn 실서버가 아닌 apn sandbox 서버로 푸시를 테스트하게 될텐데 이럴 경우 거의 무조건 이런일이 발생합니다.(저도 엄청 고생했었습니다).당황하지 말고 푸시가능한 다른 테스트 앱을 깔아서 테스트 해보시기 바랍니다.

c2dm의 경우 요청을 보내면 바로 응답을 주게 되어서 지워졌을경우 지워졌다고 알려줍니다. c2dm_on_rails gem에서 이런경우 해당 device를 지우도록 코딩되어 있습니다.

 

글을 마치면서..

사실 이 포스팅에는 제가 구현한 부분중에 몇가지 부분이 빠져있습니다.(api 요청을 통해 푸시 전송, device 등록과 user와의 관계라던가, logout했을 시에 처리 등등) 하지만 위의 내용을 꼼꼼히 보시면서 꾸준히 하시다 보면 해당 기능들은 충분히 혼자서 하실 수 있을겁니다.

rails를 통해 해본 첫 프로젝트이고, 코딩은 거의 혼자 해서 여러모로 막히는 부분이 많았지만 그때마다 큰 도움들을 주신 태연님, 재현님께 감사를 드리며 푸시 test 앱 및 api 요청관련해서 많이 알려주신 태연님, 현학님, 무익님께도 깊은 감사를 드립니다.


출처 - http://thinkreals.com/678








'Android > GCM' 카테고리의 다른 글

android - gcm-server.jar maven repository and dependency  (0) 2013.06.08
GCM 특징 및 설정  (0) 2013.01.18
android - C2DM 이용하기  (0) 2012.09.11
Posted by linuxism
,



C2DM(Cloud to Device Message)
 - 구글에서 제공하는 안드로이드 푸시 알림 서비스
 - 안드로이드 2.2(Proyo, Level8)부터 지원




원리
 1. App을 실행하면 구글 C2DM 서버로 접속해 Registration ID를 받아온다.
    - Registration ID : Device를 대표하는 고유 ID. 한 번만 발급받아서 사용하면 되므로 DB에 저장하는 것을 추천.
 2. C2DM 서버로 메세지를 푸시하기 위해서는 AuthToken이라는 인증키가 필요하다.
    이 인증키는 인증서와 비슷한 개념으로 생각하면 된다.
 3. 이제 AuthToken과 상대방 Registration ID를 가지고 C2DM 서버로 메세지를 보낸다.(이 부분은 앱서버가 담당)
 4. C2DM에서 해당 Registration ID를 가진 단말을 찾아서 메세지를 푸시해준다.




사용 순서
 1. C2DM 서비스 신청
   아래 사이트에서 C2DM 서비스 신청을 한다.



 2. 등록이 완료되면 아래처럼 메일이 온다.



 3. 아래 사항들은 C2DM을 이용하는데 필요한 요소들에 대한 설명이다.
    (원문 출처 : http://code.google.com/intl/ko-KR/android/c2dm/) 



4. 이제 직접 코드를 작성해보자.
    아래 소스는 '단말기A → 앱서버 → C2DM → 단말기B' 가 아닌
    '단말기A → 단말기A' 예제이다. 물론 아래 소스들을 응용한다면 충분히 위와 같은 로직을 만드는 것이 가능하다.

Talk.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
public class Talk extends Activity {
 
    private static String authToken = null;
 
    EditText msg_text;
    Button msg_send;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // C2DM 으로부터 Registration ID와 AuthToken을 발급 받는다.
        try {
            requestRegistrationId();
            authToken = getAuthToken();
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        msg_text = (EditText) findViewById(R.id.msg_text);
        msg_send = (Button) findViewById(R.id.msg_send);
 
        msg_send.setOnClickListener(new OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // 메세지를 보낸다.
                try {
                    sender(C2dmReceiver.registration_id, authToken,
                            msg_text.getText().toString());
                    Log.v("C2DM", "Send Message : "
                            + msg_text.getText().toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
 
            }
        });
    }
 
    /** C2DM으로 메세지를 보내는 메소드 */
    public void sender(String registration_id, String authToken, String msg)
            throws Exception {
 
        // collapse_key는 C2DM에서 사용자가 SEND 버튼을 실수로 여러번 눌렀을때
        // 이전 메세지 내용과 비교해서 반복전송되는 것을 막기 위해서 사용된다.
        // 여기서는 반복전송도 허용되게끔 매번 collapse_key를 랜덤함수로 뽑는다.
        String collaspe_key = String.valueOf(Math.random() % 100 + 1);
 
        // 보낼 메세지 조립
        StringBuffer postDataBuilder = new StringBuffer();
 
        postDataBuilder.append("registration_id=" + registration_id);
        postDataBuilder.append("&collapse_key=" + collaspe_key); // 중복방지 필터
        postDataBuilder.append("&delay_while_idle=1");
        postDataBuilder.append("&data.msg=" + URLEncoder.encode(msg, "UTF-8")); // 메세지                                                                          // 내용
 
        // 조립된 메세지를 Byte배열로 인코딩
        byte[] postData = postDataBuilder.toString().getBytes("UTF-8");
 
        // HTTP 프로토콜로 통신한다.
        // 먼저 해당 url 커넥션을 선언하고 연다.
        URL url = new URL("https://android.apis.google.com/c2dm/send");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 
        conn.setDoOutput(true); // 출력설정
        conn.setUseCaches(false);
        conn.setRequestMethod("POST"); // POST 방식
        conn.setRequestProperty("Content-Type",
                "application/x-www-form-urlencoded");
        conn.setRequestProperty("Content-Length",
                Integer.toString(postData.length));
        conn.setRequestProperty("Authorization", "GoogleLogin auth="
                + authToken);
 
        // 출력스트림을 생성하여 postData를 기록.
        OutputStream out = conn.getOutputStream();
 
        // 출력(송신)후 출력스트림 종료
        out.write(postData);
        out.close();
 
        // 소켓의 입력스트림을 반환
        conn.getInputStream();
    }
 
    /**
     * Request for RegistrationID to C2DM Activity 시작시 구글 C2DM으로 Registration ID
     * 발급을 요청한다. Registration ID를 발급받기 위해서는 Application ID, Sender ID가 필요.
     * Registration ID는 Device를 대표하는 ID로써 한번만 받아서 저장하면 되기 때문에 매번 실행시 체크.
     */
    public void requestRegistrationId() throws Exception{
 
        SharedPreferences shrdPref = PreferenceManager
                .getDefaultSharedPreferences(this);
        String registration_id = shrdPref.getString("registration_id", null);
        shrdPref = null;
 
        if (registration_id == null) {
            Intent registrationIntent = new Intent(
                    "com.google.android.c2dm.intent.REGISTER");
 
            // Application ID(Package Name)
            registrationIntent.putExtra("app",
                    PendingIntent.getBroadcast(this, 0, new Intent(), 0));
 
            // Developer ID
            registrationIntent.putExtra("sender", "개발자 Email");
 
            // Start request.
            startService(registrationIntent);
        } else {
            C2dmReceiver.registration_id = registration_id;
            Log.v("C2DM", "Registration ID is Exist!");
            Log.v("C2DM", "Registration ID : " + C2dmReceiver.registration_id);
        }
    }
     
    /**
     * C2DM을 이용하기 위해서는 보안상 authToken(인증키)이 필요하다.
     * authToken도 역시 한 번만 받아놓고 저장한다음 쓰면 된다.
     */
    public String getAuthToken() throws Exception {
 
        SharedPreferences shrdPref = PreferenceManager
                .getDefaultSharedPreferences(this);
        String authToken = shrdPref.getString("authToken", null);
 
        Log.v("C2DM", "AuthToken : " + authToken);
 
        if (authToken == null) {
            StringBuffer postDataBuilder = new StringBuffer();
 
            postDataBuilder.append("accountType=HOSTED_OR_GOOGLE");
            postDataBuilder.append("&Email=개발자 Email");
            postDataBuilder.append("&Passwd=비밀번호");
            postDataBuilder.append("&service=ac2dm");
            postDataBuilder.append("&source=앱의 정보(아무거나 적어도됨)");
 
            byte[] postData = postDataBuilder.toString().getBytes("UTF-8");
 
            URL url = new URL("https://www.google.com/accounts/ClientLogin");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded");
            conn.setRequestProperty("Content-Length",
                    Integer.toString(postData.length));
 
            // 출력스트림을 생성하여 서버로 송신
            OutputStream out = conn.getOutputStream();
            out.write(postData);
            out.close();
 
            // 서버로부터 수신받은 스트림 객체를 버퍼에 넣어 읽는다.
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    conn.getInputStream()));
 
            String sIdLine = br.readLine();
            String lsIdLine = br.readLine();
            String authLine = br.readLine();
 
            Log.v("C2DM", sIdLine);
            Log.v("C2DM", lsIdLine);
            Log.v("C2DM", authLine);
 
            authToken = authLine.substring(5, authLine.length());
 
            SharedPreferences.Editor editor = shrdPref.edit();
            editor.putString("authToken", authToken);
            editor.commit();
        }
 
        shrdPref = null;
        return authToken;
    }
}


C2dmReceiver.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class C2dmReceiver extends BroadcastReceiver {
 
    static String registration_id = null;
    static String c2dm_msg = "";
     
    @Override
    public void onReceive(Context context, Intent intent) {
        // 리시버로 받은 데이터가 Registration ID이면
        if (intent.getAction().equals(
                "com.google.android.c2dm.intent.REGISTRATION")) {
 
            handleRegistration(context, intent);
        }
        // 리시버가 받은 데이터가 메세지이면
        else if (intent.getAction().equals(
                "com.google.android.c2dm.intent.RECEIVE")) {
 
            // 추출
            c2dm_msg = intent.getStringExtra("msg");
 
            // 출력
            Log.v("C2DM", "C2DM Message : " + c2dm_msg);
            Toast toast = Toast.makeText(context, c2dm_msg, Toast.LENGTH_SHORT);
            toast.setGravity(Gravity.TOP | Gravity.CENTER, 0, 150);
            toast.show();
        }
    }
 
    public void handleRegistration(Context context, Intent intent) {
 
        registration_id = intent.getStringExtra("registration_id");
 
        Log.v("C2DM", "Get the Registration ID From C2DM");
        Log.v("C2DM", "Registration ID : " + registration_id);
 
        // 받은 메세지가 error일 경우
        if (intent.getStringExtra("error") != null) {
            Log.v("C2DM", "C2DM REGISTRATION : Registration failed,"
                    + "should try again later");
        }
        // 받은 메세지가 unregistered일 경우
        else if (intent.getStringExtra("unregistered") != null) {
            Log.v("C2DM", "C2DM REGISTRATION : unregistration done, "
                    + "new messages from the authorized "
                    + "sender will be rejected");
        }
        // 받은 메세지가 Registration ID일 경우
        else if (registration_id != null) {
            Log.v("C2DM", "Registration ID complete!");
 
            // Registration ID 저장
            SharedPreferences shrdPref = PreferenceManager
                    .getDefaultSharedPreferences(context);
 
            SharedPreferences.Editor editor = shrdPref.edit();
            editor.putString("registration_id", registration_id);
            editor.commit();
        }
    }
}


main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff" android:gravity="center_vertical|center">
    <EditText
        android:id="@+id/msg_text"
        android:layout_width="240dip"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/msg_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="보내기 "/>
</LinearLayout>


AndroidManifest.xml (매니페스트에 브로드캐스팅을 받을 클래스와 각종 퍼미션을 등록한다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="utf-8"?>
      package="앱의 패키지네임"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />
 
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".TalkActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- Only C2DM servers can send messages for the app.
            If permission is not set - any other app can generate it -->
        <receiver android:name=".C2dmReceiver"
                  android:permission="com.google.android.c2dm.permission.SEND" >
             <!-- Receive the actual message -->
            <intent-filter >
                <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
                <category android:name="앱의 패키지네임"/>
            </intent-filter>
            <!-- Receive the registration id -->
            <intent-filter >
                <action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
                <category android:name="앱의 패키지네임"/>
            </intent-filter>
        </receiver>
    </application>
 
    <!-- Only this application can receive the messages and
        registration result -->
    <permission android:name="앱의 패키지네임.permission.C2D_MESSAGE"
                android:protectionLevel="signature" />
    <uses-permission android:name="앱의 패키지네임.permission.C2D_MESSAGE"/>
     
    <!-- This app has permission to register and receive message -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
     
    <!-- Send the registration id to the server -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
</manifest>



5. 제대로 받은 키 값들은 아래와 같이 나온다.



6. 메세지 전송 확인



출처 - http://warmz.tistory.com/570


===================================================================================


최근에 알게 되었는데.. 구글에서도 애플의 푸쉬노티피케이션과 같은 서비스를 사용할 수 있게 되었다.
아직은 랩에서 활용되고 있는 것 같다. 일명. C2DM 이며, 안드로이드 버전 2.2에서 사용이 가능하다고 한다.

이것과 관련된 일부 기사를 링크한다.

[번역]안드로이드 C2DM 을 이용한 Chrome to Phone 에 관하여
[번역] 안드로이드 Android Cloud to Device Messaging(C2DM)

관련 기사와 정보를 토대로 가입을 시도해 보았다.
서비스 가입을 위한 사이트는 http://code.google.com/intl/ko-KR/android/c2dm/signup.html 이다.
C2DM 가입 화면
가입을 시도해 보았다.
정보들을 입력하는 과정에서 일 최대 메시지 수, 피크시의 메시지 수와 함께 제한되는 것으로 보내는 쪽의 총 메시지 수와 개별 장비로 전송되는 메시지 수를 검토해서 구글에서 제한 할 수도 있다는 문구가 보인다.

하지만, 가입을 절차를 완료하지 못했다. 아무래도 아직 완성도 있게 만들어 지지는 않은듯 한다.
일부 사용자 중에서는 6월부터 개발해서 테스트가 잘 되었으나 최근들어 서비스가 되지 않는다는 기사도 본적이 있다.
전체 스마트폰의 점유율 1위를 달리는 안드로이드의 푸쉬 서비스 환경에 기대를 해보지만 아직은 접근하기가 쉽지 않은것 같다.

최근 발표된 윈도우폰7에 이어 안드로이드까지 정식 서비스가 된다면 역시 실시간 메시지 전달 플랫폼은 더욱 부각이 될 것이다.


출처 - http://jongryong.wordpress.com/2010/10/29/%EA%B5%AC%EA%B8%80%EC%9D%98-%ED%91%B8%EC%89%AC-%EB%85%B8%ED%8B%B0%ED%94%BC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%9C%EB%B9%84%EC%8A%A4-c2dm/


Android Cloud to Device Messaging
[이 포스트는 이 기능을 구현하는데 기여한 Wei Huang 에 의해 작성되었습니다. — Tim Bray]


  새롭게 발표된 안드로이드 2.2 에서, 우리는 Android Cloud to Device Messaging (C2DM) 서비스를 추가하였습니다. 이 서비스는 개발자들이 서버와 모바일 어플리케이션간에 데이타를 쉽게 싱크할 수 있도록, 서버쪽 에서에서 안드로이드 폰 상의 어플리케이션으로 데이타를 손쉽게 전달할 수 있도록 도와줍니다.

 유용한 휴대폰 어플리케이션들은 대부분 사용자가 인터넷과 연결되도록 유지합니다. 이를 위해 전통적으로 많은 어플리케이션들은 주기적으로 데이터를 가져오기 위해 폴링 방식을 사용합니다. 예를들어 POP 이메일 클라이언트의 경우 15분 마다 이메일 서버와 연결되어 새로운 이메일을 가져오도록 구현될 수 있습니다. 이러한 폴링 방식은 비교적 구현하기가 용이하고, 대부분의 경우 잘 작동합니다. 하지만, 어떤한 주기로 폴링 작업을 수행할지 결정하는 것은 조금 애매한 구석이 있습니다. 폴링을 너무 자주하게 되면, 새로운 데이타가 없는데도 불구하고 불피요한 작업이 수행되며 서버와 네트워크에 부하를 줄 수 있습니다. 너무 드물게 폴링을 수행하면 어플리케이션이 갖고 있는 데이터가 전혀 갱신되지 않는 것 처럼 느껴질지도 모릅니다. 특히나 모바일 디바이스에서 효율적으로 폴링 작업을 수행하는 것은 중요한 문제입니다. 왜냐하면 폴링 작업은 귀중한 네트워크 밴드위스와 베터리를 소모시키기 때문입니다.

 폴링 방식을 사용하는 대신, 비동기적으로 클라이언트에 메세지를 푸쉬해주는 서버를 갖추는 것은 어플리케이션이 효율적으로 새로운 데이터를 전달 받을 수 있는 훨씬 좋은 선택이 될 수 있습니다. 하지만 훌륭한 푸쉬 솔루션을 구현하는 것은 어려운 일이며, 서버측과 특정한 연결을 유지하고 있어야 하는 오버헤드가 발생합니다. 특히 안드로이드폰과 같은 모바일 디바이스에서 이를 구현하는데는 네트워크 상태(고르지 못한 네트워크 커버리지, 무선망 상황이 좋지 않아 커넥션을 시도해도 타임아웃에 걸리고 마는 좀비 커넥션등) 에관한 고려가 필요하기 때문에, 교묘한 기술이 필요합니다. 

 G-메일, 주소록, 캘린더와 같은 안드로이드용 구글 어플리케이션은 데이터를 항상 최신으로 유지하기 위해 이미 푸시 방식을 사용하고 있습니다. 안드로이드 2.2 부터 C2DM 를 사용하면 서드파티 개발자들도 구글 어플리케이션과 동일한 서비스를 사용할 수 있습니다.

C2DM 에 대해서 몇 가지 기본적으로 알아야 하는 사항이 있습니다. 
  • 안드로이드 2.2 버전이 필요합니다. 
  • C2DM 은 '구글 서비스'를 사용합니다. 이 서비스는 안드로이드 마켓을 사용하는 모든 디바이스에 존재합니다.
  • C2DM 은 '구글 서비스' 를 위해 이미 존재하는 커넥션을 사용합니다.
  • C2DM 은 안드로이드 폰 상에서 사용자가 구글 계정으로 로그인 해야 사용이 가능합니다.
  • C2DM은 서드파티 서버가 간단한 메세지를 자신들의 어플리케이션으로 전달하는 것을 허용합니다.
  • C2DM 서비스는 대량의 컨텐츠를 푸쉬하도록 설계되지 않았습니다. 대신 특정 어플리케이션에게 새로운 데이타가 있음을 '쿡' 하고 알려 주고, 어플리케이션이 해당 서버에 접속해서 데이타를 다운로드 받을 수 있도록 하는데 사용되어야 합니다. 
  • 어플리케이션은 메세지를 받기 위해 작동중일 필요가 없습니다. 시스템은 전달해야할 데이타가 도착하는 경우에, 브로드캐스트 인텐트를 이용해 해당 어플리케이션을 깨울 것 입니다. 따라서, 어플리케이션은 적절하게 브로드캐스트 리시버와 Permission 을 설정해야 합니다.
  • 데이타 메세지를 전달 받기 위해 사용자 인터페이스가 필요하지는 않습니다. 물론 어플리케이션이 원한다면 알림창에 노티피케이션을 날릴 수도 있을 것 입니다.

C2DM API 를 사용하는 것은 쉽습니다. C2DM 은 아래와 같이 작동합니다.
  • C2DM 을 사용하기 위해 디바이스 상의 어플리케이션은 우선 구글에 등록해 Registration ID 를 발급 받아야 합니다. 해당 ID 를 자신의 서버에 전달해야 합니다. 
  • 만일 자신의 서버가 푸시하고 싶은 메세지가 있을 경우, 메세지를 HTTP 를 통해 구글의 C2DM 서버에 전달합니다. 
  • C2DM 서버는 메세지를 디바이스에 라우팅 하고, 디바이스는 브로드캐스트 인텐트를 어플리케이션에 전달 할 것 입니다. 
  • 타켓 어플리케이션은 브로드 캐스트 인텐트를 통해 깨어나고 메세지를 처리합니다. 
  • 어플리케이션은 사용자가 더이상 푸시 서비스를 받고싶지 않을 경우 등록을 취소할 수 있습니다. 
 거의 다 되었습니다. 개발자에게 필요한 것은 HTTP를 전달할 수 있는 서버와 Intent API 를 어떻게 사용해야하는지 알고 있는 안드로이드용 어플리케이션 뿐입니다. 아래는 간단한 예제 코드입니다.

// Use the Intent API to get a registration ID
// Registration ID is compartmentalized per app/device
Intent regIntent = new Intent(
        "com.google.android.c2dm.intent.REGISTER");
// Identify your app
regIntent.putExtra("app",
        PendingIntent.getBroadcast(this /* your activity */, 
            0, new Intent(), 0);
// Identify role account server will use to send
regIntent.putExtra("sender", emailOfSender);
// Start the registration process
startService(regIntent);

Registration ID 는 개발자의 어플리케이션으로 com.google.android.c2dm.intent. REGISTRATION 이라는 액션값을 갖는 브로드 캐스 인텐트를 통해 전달되어 집니다. 다음은 Registration ID 를 전달 받기위한 예제 코드 입니다.

// Registration ID received via an Intent
public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();
  if (“com.google.android.c2dm.intent.REGISTRATION”.equals(action)) {
    handleRegistration(context, intent);
  }
}

public void handleRegistration(Context context, Intent intent) {
  String id = intent.getExtra(“registration_id”);
  if ((intent.getExtra(“error”) != null) {
    // Registration failed.  Try again later, with backoff.
  } else if (id != null) {
    // Send the registration ID to the app’s server.
    // Be sure to do this in a separate thread.
  }
}

 서버측을 살펴보면, 개발자의 서버는 C2DM 서버와 통신하기 위해 ClientLogin Auth 토큰을 가져야합니다. 토큰을 이용해서, 서버가 디바이스에 메세지를 푸시하고 싶을 때, 다음과 같은 인증된 HTTP Post 를 통해 메세지를 전달 할 수 있습니다. 
  • Authorization: GoogleLogin auth=<auth token>
  • Registration ID 와 키/벨류 쌍으로 이루어진 데이타, Google C2DM 서버에서 동일한 키값을 갖고 있는 오래된 메세지를 가로채기 위해 사용되는 'Collapse Key' 등몇 가지 옵셔널한 파라매터들을 포함하도록 인코딩된 URL.
 개발자가 C2DM 서비스를 사용하면, 골치아픈 모바일 데이타 커넥션을 직접 처리할 필요가 없으며, 사용자가 인터넷과 연결되어있는지 신경쓸 필요도 없습니다. (Airplane 모드와 같이). C2DM 은 서버 스토어에 메세지들을 보관하고, 디바이스가 온라인 상태로 될 때 해당 메세지를 전달합니다. 기본적으로 개발자는 견고한 푸시서비스를 위해 온갖 복잡하고 어려운 일들을 모두 구글에게 맡길 수 있습니다. 어플리케이션은 구글이 이미 구축하고 검증한 푸시 서비스의 이점을 취하고, 인터넷에 연결되어진 상태로 유지될 수 있습니다. 무엇보다도 좋은 것은 여러분이  배터리 소모에대해 비난을 받지 않아도 됩니다.

어떻게 C2DM 이 가능한 어플리케이션을 만들 수 있는 가에 관한 정보는 Code Lab 에 있으며, 서비스 일반 릴리즈가 다가올 수록 보다 다양한 정보가 공개될 것 입니다. 


출처 - http://nuninaya.tistory.com/574







Posted by linuxism
,