본문 바로가기
JAVA

[ JAVA ] PUSH 3 rd Party Server 구현 ( C2DM - 안드로이드 기기와 통신 )

by 정윤재 2011. 9. 23.

자바로 C2DM을 이용하여  Push 메시지를 보내는 로직이다.

자바 어플리케이션으로 만들었고 (웹 아님) 스프링을 이용하였다.

=======================================================================
C2dmExecute.java (자바 어플리케이션을 실행시키는 곳)

package com.incross.c2dm.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.incross.dto.PushDTO;
import com.incross.dto.UserDTO;

public class C2dmExecute {
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  try {
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   C2dmService c2dmService     =   (C2dmService)context.getBean("c2dmService");
   //c2dmService.XmlParsingTest();
   System.out.println("***************************************");
   
   //REGISTRATION
   //구글 API 의 인증 토큰 얻기
   //System.out.println("auth_token="+c2dmService.getAuth_token(new PushDTO()).getGoogleLogin_auth());
   //get auth token (여기서 얻어서 property 로 설정해주고 c2dm 사용한다.
   System.out.println("***************************************");
   System.out.println("***************************************");
   //SEND MESSAGE
   PushDTO dto     =   new PushDTO();
   dto.setData("first_message::오케이");
   //보내는 메시지 셋팅
   System.out.println(c2dmService.message_push(dto));
   System.out.println("***************************************");
} catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } 
 }
}
===============================================================
C2dmService.java (메시지 전송을 위한 실제 전송 로직)

package com.incross.c2dm.service;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.incross.c2dm.dao.C2dmDAO;
import com.incross.dto.PushDTO;
import com.incross.util.XPathUtil;

@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = Exception.class)
public class C2dmService {

 @Autowired
 public C2dmDAO c2dmDAO;
 private static final Logger logger  =  Logger.getLogger(C2dmService.class);
/**
  * author  : J.Y.CH
  * date    : 2011. 7. 13.
  * purpose : get google open api auth token (구글 API 를 쓰기 위한 인증코드 받기)
  * @param dto
  * @return
  *
  */
 public PushDTO getAuth_token(PushDTO dto){
 StringBuffer resp = new StringBuffer();
try {
   // 구글 인증 토근 받아오기 위한 요청 데이터를 만든다
   String rdata = URLEncoder.encode("accountType", "UTF-8") + "=" + URLEncoder.encode(dto.getGoogle_account_type(), "UTF-8");//HOSTED_OR_GOOGLE
   rdata += "&" + URLEncoder.encode("Email", "UTF-8") + "=" + URLEncoder.encode(dto.getGoogle_email_id(), "UTF-8");
   rdata += "&" + URLEncoder.encode("Passwd", "UTF-8") + "=" + URLEncoder.encode(dto.getGoogle_email_pw(), "UTF-8");
   rdata += "&" + URLEncoder.encode("service", "UTF-8") + "=" + URLEncoder.encode(dto.getGoogle_service_id(), "UTF-8");//ac2dm
// Send data
   URL url = new URL(dto.getC2dm_client_login_url());
   URLConnection conn = url.openConnection();
   conn.setDoOutput(true);
   // Write post
   OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
   wr.write(rdata);
   wr.flush();
// Get the response
   BufferedReader rd;
   String line;
   rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
   while ((line = rd.readLine()) != null) {
    resp.append(line);
    System.out.println(line);
   }
   wr.close();
   rd.close();
  } catch (FileNotFoundException e1) {
   // Catch bad url
   logger.error("AndroidApp : Error: Bad url address!");
  } catch (IOException e1) {
   // Catch 403 (usually bad username or password
   if(e1.toString().contains("HTTP response code: 403"))
    logger.error("AndroidApp : Error: Forbidden response! Check username/password or service name.");
  }
String token = resp.toString().substring(resp.toString().indexOf("Auth=")+5);
  dto.setGoogleLogin_auth(token);
  return dto;
 }
 /**
  * author  : J.Y.CH
  * date    : 2011. 7. 13.
  * purpose : send push message (메시지 전송)
  * @param dto
  * @return
  *
  */
 public int message_push(PushDTO dto){
  int responseCode      =  0;
  StringBuffer param     =  new StringBuffer();
  try {
   //make parameter
   //구글 push 서비스에 보낼 메시지 데이터를 만든다.
   param.append("registration_id="+URLEncoder.encode(dto.getRegistration_id(),"UTF-8"));
   param.append("&collapse_key="+URLEncoder.encode(dto.getCollapse_key(),"UTF-8"));
   param.append("&delay_while_idle="+URLEncoder.encode(dto.getDelay_while_idle(),"UTF-8"));
   param.append("&data.message="+URLEncoder.encode(dto.getData(),"UTF-8"));
   System.out.println("param::"+param.toString());
   //URL google_url     =  new URL(dto.getC2dm_url()+"?"+param.toString());
   URL google_url     =  new URL(dto.getC2dm_url());
   byte[] postData = param.toString().getBytes("UTF8");
   //http connection connect
   //HttpsURLConnection conn   =  (HttpsURLConnection)google_url.openConnection();
   HttpsURLConnection conn   =  (HttpsURLConnection)google_url.openConnection();
   conn.setHostnameVerifier(new CustomizedHostNameVerifier());
   //https 방식이라 이런식으로 인증 로직이 있어야 하더라.
   //http header make
   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));
   //이렇게 byte 배열로 바꾸어서 길이를 넣어주어야 한다.
   conn.setRequestProperty("Authorization", "GoogleLogin auth=" + dto.getGoogleLogin_auth());
    OutputStream out = conn.getOutputStream();
    System.out.println("postData length::"+postData.length);
    out.write(postData);
    //post 방식이므로 http body 안에 이렇게 넣어준다.(byte로)
    out.flush();
    out.close();
   //get http response code
   responseCode      =   conn.getResponseCode();
if(responseCode == 401 || responseCode == 403){
    // 401 = unauthorized , 403 = forbidden
    logger.debug("AndroidApp : Unauthorized - need token");
   }
String updatedAuthToken = conn.getHeaderField("Update-Client-Auth");
   if(updatedAuthToken != null && !dto.getGoogleLogin_auth().equals(updatedAuthToken)){
    logger.debug("AndroidApp : Got updated auth token from datamessaging servers : " +  updatedAuthToken);
   }
String reponseLine = new BufferedReader(new InputStreamReader(conn.getInputStream())).readLine();
   System.out.println("reponseLine:::"+reponseLine);
   if(reponseLine == null || reponseLine.equals("")){
    logger.debug("AndroidApp : Got " +  responseCode + " response from Google AC2DM endpoint.");
    throw new IOException("Got empty response from Google AC2DM endpoint");
   }
String[] responseParts = reponseLine.split("=",2);
if(responseParts.length != 2){
    logger.debug("AndroidApp : Invalid message from google " +  responseCode + " " + reponseLine);
    throw new IOException("Invalid message from google " +  responseCode + " " + reponseLine);
   }
   if(responseParts[0].equals("id")){
    logger.debug("AndroidApp : Successfully sent data message to device : " + reponseLine);
   }
if(responseParts[0].equals("Error")){
    String err = responseParts[1];
    logger.debug("AndroidApp : Got error response from Google datamessaging endpoint : " + err);
    throw new IOException("Server error : " + err);
   }else{
    logger.debug("AndroidApp : Invalid response from google : " + reponseLine + " " + responseCode);
   }
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   logger.debug(e.getMessage());
  }
  return responseCode;
}
//구글 HTTPS 를 사용하기 위한 로직
private static class CustomizedHostNameVerifier implements HostnameVerifier {
  public boolean verify(String hostname, SSLSession session) {  return true; }
 }
 }

=============================================================
PushDTO.java (설정 정보를 불러오기 위한 Data Transfer Object)

package com.incross.dto;
import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.Properties;

public class PushDTO {
 
 private String c2dm_url;
 private String c2dm_client_login_url;
 private String registration_id;
 private String collapse_key;
 private String data;
 private String delay_while_idle;
 private String googleLogin_auth;
 private String google_email_id;
 private String google_email_pw;
 private String google_account_type;
 private String google_service_id;

 public PushDTO(){
  //c2dm_url update
     ClassLoader cl;
     cl = Thread.currentThread().getContextClassLoader();
     
     if(cl == null){
      cl = ClassLoader.getSystemClassLoader();
     }
     URL url = cl.getResource("info.property"); 
  
     File propFile      = new File(url.getPath());
     FileInputStream is;
    
     try {
         is = new FileInputStream(propFile);
         Properties props     = new Properties();
      props.load(is);
     
      this.c2dm_url        = props.getProperty("C2DM_URL").trim();
      this.c2dm_client_login_url     = props.getProperty("C2DM_CLIENT_LOGIN").trim();
      this.google_email_id   = props.getProperty("GOOGLE_EMAIL_ID").trim();  
      this.google_email_pw   = props.getProperty("GOOGLE_EMAIL_PASSWORD").trim(); 
      this.google_account_type  = props.getProperty("GOOGLE_ACCOUNT_TYPE").trim();  
      this.google_service_id   = props.getProperty("GOOGLE_PUSH_SERVICE_ID").trim(); 
      this.googleLogin_auth   = props.getProperty("GOOGLELOGIN_AUTH").trim();
      this.collapse_key    = props.getProperty("COLLAPSE_KEY").trim(); 
      this.delay_while_idle   = props.getProperty("DELAY_WHILE_IDLE").trim();
      this.registration_id   = props.getProperty("REGISTRATION_ID").trim();
     
     } catch (Exception e) {
         e.printStackTrace();
     } 
 }
public String getGoogle_account_type() {
  return google_account_type;
 }
 public void setGoogle_account_type(String google_account_type) {
  this.google_account_type = google_account_type;
 }
 public String getGoogle_service_id() {
  return google_service_id;
 }
 public void setGoogle_service_id(String google_service_id) {
  this.google_service_id = google_service_id;
 }
 
 public String getGoogle_email_id() {
  return google_email_id;
 }
 public void setGoogle_email_id(String google_email_id) {
  this.google_email_id = google_email_id;
 }
public String getGoogle_email_pw() {
  return google_email_pw;
 }
 public void setGoogle_email_pw(String google_email_pw) {
  this.google_email_pw = google_email_pw;
 }
 
 
 public String getC2dm_client_login_url() {
  return c2dm_client_login_url;
 }

 public void setC2dm_client_login_url(String c2dm_client_login_url) {
  this.c2dm_client_login_url = c2dm_client_login_url;
 }
 public String getC2dm_url() {
  return c2dm_url;
 }
 public void setC2dm_url(String c2dm_url) {
  this.c2dm_url = c2dm_url;
 }
 
 public String getRegistration_id() {
  return registration_id;
 }
 public void setRegistration_id(String registration_id) {
  this.registration_id = registration_id;
 }
 public String getCollapse_key() {
  return collapse_key;
 }
 public void setCollapse_key(String collapse_key) {
  this.collapse_key = collapse_key;
 }
 public String getData() {
  return data;
 }
 public void setData(String data) {
  this.data = data;
 }
 public String getDelay_while_idle() {
  return delay_while_idle;
 }
 public void setDelay_while_idle(String delay_while_idle) {
  this.delay_while_idle = delay_while_idle;
 }
 public String getGoogleLogin_auth() {
  return googleLogin_auth;
 }
 public void setGoogleLogin_auth(String googleLogin_auth) {
  this.googleLogin_auth = googleLogin_auth;
 }
  
}

===========================================================
info.property (설정 정보가 담겨야 하는 설정 정보 파일)

#push_send
C2DM_URL    =  
https://android.apis.google.com/c2dm/send
C2DM_CLIENT_LOGIN  =  https://www.google.com/accounts/ClientLogin
GOOGLE_ACCOUNT_TYPE  =  HOSTED_OR_GOOGLE

#push_google_auth
GOOGLE_EMAIL_ID   =  xxxxx
@gmail.com 
GOOGLE_EMAIL_PASSWORD =  사용자 Gmail 비밀번호
GOOGLE_PUSH_SERVICE_ID =  ac2dm
COLLAPSE_KEY   =  1
DELAY_WHILE_IDLE  =  2
GOOGLELOGIN_AUTH  =  DQAAALcAA~~~~~~~~~~~~~~~~~~
REGISTRATION_ID   =  APA91bGvFP3jrhi-~~~~~~~~~~~~~~~~~

==========================================================
여기서 부터는 안드로이드 에서 단말 registration 아이디와 
push 메시지 보는 로직을 적어 보겠다.
당연히 안드로이드 프로그래밍이다.

프로젝트 명 : HelloAdroid
==============================================================
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="
http://schemas.android.com/apk/res/android"
      package="com.incross.c2dm"
      android:versionCode="1"
      android:versionName="1.0">
    <!-- uses-sdk android:minSdkVersion="10" / -->
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".HelloAdroidActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
  <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="com.incross.c2dm"/>
   </intent-filter>
   <intent-filter>
    <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
    <category android:name="com.incross.c2dm"/>
   </intent-filter>   
  
  </receiver>
  <!-- activity android:name="ShowMsgActivity"></activity -->
</application>
   
 <permission android:name="com.incross.c2dm.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
 
 <uses-permission android:name="com.incross.c2dm.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.WAKE_LOCK" />
 
 <uses-permission android:name="android.permission.GET_ACCOUNTS" />
 
 <uses-permission android:name="android.permission.USE_CREDENTIALS" />
 
 <uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>

===============================================================
C2DMReceiver.java  (C2DM 서버로 부터 메시지를 받기 위한 리시버)

package com.incross.c2dm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

public class C2DMReceiver extends BroadcastReceiver {
@Override
 public void onReceive(Context context, Intent intent) {
  // TODO Auto-generated method stub
  Log.e("C2DM:::", "onReceive");
  
        //서버에 정상적으로 등록이 완료되었을 때        
  if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")){
   Log.e("C2DM_REGISTRATION", ":::::::::register start::::::::::::");
   handleRegistration(context, intent);
  }else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")){
   //서버에서 메시지가 도착했을 때]
   Log.e("C2DM_RECEIVE", ":::::push receive::::::::::::");
    handleMessage(context, intent);
  }
 }

 private void handleRegistration(Context context, Intent intent){

   Log.e("C2DM_handleRegistration:::", "handleRegistration");

  //서버에서넘어오는메시지의내용에key이름 "registration_id"에는이기기에만사용하는인증키값이담겨서넘어온다.
String registration = intent.getStringExtra("registration_id");
if (intent.getStringExtra("error") != null){

   Log.e("C2DM_registration_result:", "error");
  }else if (intent.getStringExtra("unregistered") != null){
   Log.e("C2DM_registration_result:", "::unregister success::");
  }else if (registration != null){
   Log.e("C2DM_registration_result:", "::registration_id complete::");
   Log.e("C2DM_registration_id=", registration);
   //등록 ID 가 구글로 부터 왔을 때 확인 할 수 있도록 로그로 보기 (로그캣으로 봄)
   //이 값을 push 메시지 보내줄 서버에서 알고서 이 값으로 보내주어야 함 (구글이 단말을 알 수 있는 키 값임)
   System.out.println("C2DM_registration_id="+registration);
  }

 }

 private void handleMessage(Context context, Intent intent){
        Log.e("C2DM_message::", "handleMessage");        
       // Toast.makeText(context, intent.getStringExtra("message"), 1000).show();
        Toast.makeText(context, intent.getExtras().getString("message"), 1000).show();
        //push 메시지가 온 것을 확인 하여 볼 수 있도록 설정
 }
 
}

==========================================================
HelloAdroidActivity.java

package com.incross.c2dm;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class HelloAdroidActivity extends Activity implements OnClickListener{
    /** Called when the activity is first created. */
 String tag;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        Button btnRegist = (Button)findViewById(R.id.c2dm_btnRegist);        
        Button btnUnregist = (Button)findViewById(R.id.c2dm_btnUnregist);        
        btnRegist.setOnClickListener(this);        
        btnUnregist.setOnClickListener(this); 

    }
    @Override  
    public void onClick(View v){
     switch(v.getId()){
      case R.id.c2dm_btnRegist:
       //등록 버튼 눌렀을때 이벤트
       Log.e("c2dm_btnRegist:::", "c2dm_btnRegist"); 
     //Android C2DM에Push메시지를 받겠다는메시지를보내는Intent
     //정상적으로등록이되면Android C2DM Server쪽에서 인증키를보내준다.
     //이인증키는해당어플리케이션과해당기기를대표하는인증키로서버에서메시지를보낼때사용되며
     //서버에등록을할때마다인증키는달라진다.
 //Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
       Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
//인텐트에함께보내는내용은"app"과"sender"가있는데이두Key는 모두google에서 제시한것이기때문에
//임의로변경이불가능하다.
//app에는 해당어플리케이션정보를담아서보내고
//sender에는 개발자의주소를담아서보낸다.
registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
registrationIntent.putExtra("sender",
xxxx@gmail.com);
       startService(registrationIntent);
    break;
    case R.id.c2dm_btnUnregist:
       Log.e("c2dm_btnUnregist:::", "c2dm_btnUnregist");
     //Android C2DM에Push메시지를 그만받겠다는메시지를보내는Intent
     //해지 버튼 누를때의 이벤트  
       Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
       unregIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
       startService(unregIntent);
      break;
     default;
     break;
      }
   }
}

 


댓글