C2DM을 사용한 PUSH 구현

2012. 1. 5. 09:44

* c2dm : Android Cloud to Device Messaging (C2DM)

* 단점

  1. 메시지의 누실 가능성
  2. 순차적 전달 보장 안됨
  3. 재전송 없음 
  4. 단말기 필요 사항 : Android OS 2.2 ( Proyo, Level8 ) 이상, 마켓 로그인 필수

* 제약 사항

  1. 한 메세지 사이즈 제약 : 1024 bytes
  2. 전송 가능한 할당량 : 디폴트 200,000개 / 일
      ( 참고 : http://code.google.com/intl/ko-KR/android/c2dm/quotas.html )

* 사전 준비

  1.  구글에 c2dm 서비스 신청 : http://code.google.com/intl/ko-KR/android/c2dm/signup.html
     - 패키지명
     - 마켓 등록 여부
     - 하루 이용량
     - 초당 대략적 이용량(QPS)
     - 추가 정보

  2. 승인 Mail을 받으면 신청 완료

 

 

 

단말기 구현


1. AndroidManifest.xml

<receiver android:name="C2dmReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="Package Name" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="Package Name" />
</intent-filter>
</receiver>
<permission android:name="Package Name.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="Package Name.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />


2. Register : 단말기 등록을 신청 하고 Registration ID 받기 ( 패키지명, 메일 주소가 키가 되는 듯 )

/**
* google C2DM - Registration ID
*/
public class C2dmRegistrator{

private Activity activity;

public C2dmRegistrator(Context context){
this.activity = (Activity)context;
}

//C2DM registation ID
public void requestRegistionId(){
Util.d(">>>>>> C2dmRegistrator");
Intent registrationIntent
= new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app",
PendingIntent.getBroadcast(activity, 0, new Intent(), 0));
registrationIntent.putExtra("sender", mail Address);
activity.startService(registrationIntent);
}

public void requestUnRegistion(){
Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
unregIntent.putExtra("app",
PendingIntent.getBroadcast(activity, 0, new Intent(), 0));
activity.startService(unregIntent);
}
}


3. Recevier

기능  :  
1). REGISTRATION ID 요청에 대한 처리
2). Push Messages에 대한 처리

/**
* C2DM Brodcast Receiver Class
*/
public class C2dmBroadcastReceiver extends BroadcastReceiver {

static String registration_id = null;
static String c2dm_msg = "";

@Override
public void onReceive(Context context, Intent intent) {

// Registration ID received via an Intent
if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")){

handleRegistration(context, intent);

} else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")){

handleMessage(context, intent);

}
}

private void handleRegistration(Context context, Intent intent) {

registration_id = intent.getStringExtra("registration_id");

if (intent.getStringExtra("error") != null) { // Registration Fail

// Registration failed, should try again later.
Log.d("c2dm", "registration failed");

String error = intent.getStringExtra("error");
if(error == "SERVICE_NOT_AVAILABLE"){
Log.d("c2dm", "SERVICE_NOT_AVAILABLE");
}else if(error == "ACCOUNT_MISSING"){
Log.d("c2dm", "ACCOUNT_MISSING");
}else if(error == "AUTHENTICATION_FAILED"){
Log.d("c2dm", "AUTHENTICATION_FAILED");
}else if(error == "TOO_MANY_REGISTRATIONS"){
Log.d("c2dm", "TOO_MANY_REGISTRATIONS");
}else if(error == "INVALID_SENDER"){
Log.d("c2dm", "INVALID_SENDER");
}else if(error == "PHONE_REGISTRATION_ERROR"){
Log.d("c2dm", "PHONE_REGISTRATION_ERROR");
}

} else if (intent.getStringExtra("unregistered") != null) {
Log.d("c2dm", "unregistered");
} else if (registration_id != null) { // Registration done

Editor editor =
context.getSharedPreferences
(Constants.C2DM_PREFERENCES_KEY, Context.MODE_PRIVATE).edit();
editor.putString(Constants.C2DM_REGISTRATION_ID, registration_id);
editor.commit();

// Send the registration ID to the 3rd party site that is sending messages.
// This should be done in a separate thread.
// When done, remember that all registration is done.

try {
String androidID = Secure.getString(
context.getContentResolver(), Secure.ANDROID_ID);

StringBuffer postDataBuilder = new StringBuffer();

postDataBuilder.append(URLEncoder.encode("device", "UTF-8"));
postDataBuilder.append("=");
postDataBuilder.append(URLEncoder.encode("token", "UTF-8"));
postDataBuilder.append("=");
postDataBuilder.append(URLEncoder.encode(androidID, "UTF-8"));
postDataBuilder.append("&");
postDataBuilder.append(URLEncoder.encode("reg_id", "UTF-8"));
postDataBuilder.append("=");
postDataBuilder.append(URLEncoder.encode(registration_id, "UTF-8"));

URL url = new URL(Constants.C2DM_URL);
URLConnection conn = url.openConnection();

conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(
conn.getOutputStream());
wr.write(postDataBuilder.toString());
wr.flush();
wr.close();

BufferedReader rd = new BufferedReader(new InputStreamReader(
conn.getInputStream(), "EUC-KR"));
String line;
while ((line = rd.readLine()) != null) {
Util.d("RECEIVE VALUE : " + line);
}
rd.close();

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

private void handleMessage(Context context, Intent intent)
{
Util.d(">>>>> PUSH RECEIVE MESSAGES : " + c2dm_msg + " <<<<<");
c2dm_msg = intent.getExtras().getString("msg");

Intent i = new Intent(context, ShowMsgActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("RECEIVE_PUSH_MSG", c2dm_msg);
context.startActivity(i);
}
}

image

 

 

서버(Third Party Server) Web 구현

1. Auth 인증을 통한 ClientLogin ( Auth Key 획득 )
    - URL parameter : https://www.google.com/accounts/ClientLogin
    - request parameter
image

    - request sample

private static void auth()
{
try
{
StringBuffer postDataBuilder = new StringBuffer();
postDataBuilder.append("Email=" + "개발자이메일주소");
postDataBuilder.append("&Passwd=" + "개발자이메일암호");
postDataBuilder.append("&accountType=GOOGLE");
postDataBuilder.append("&source=" + "어플정보 또는 디바이스 정보");
postDataBuilder.append("&service=ac2dm");
byte[] postData = postDataBuilder.toString().getBytes("UTF8");
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 in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String inputLine;
while((inputLine = in.readLine()) != null)
{
System.out.println(inputLine);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
      
- response sample
image

    * Push 메시지를 보내기 위해서는 Auth 값을 저장 하고 있다가 메시지를 보낼 때 사용된다.

image

 

2. PUSH SEND

- Post Data URL : https://android.apis.google.com/c2dm/send
- Request parameter
image

- Request Sample

/** 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();
    }

- Response
image

,