안드로이드

[Android] 안드로이드 GCM 이용과 Third party(jsp) 가이드 - 3부 [펌]

SojuMan 2013. 1. 22. 09:33

출처 : http://cusmaker.com/106

이번 강의에서는 안드로이드가 DB와 어떻게 연결되는지를 알아보는것과 전 강의에서 얻어온 등록ID를 DB에 삽입하는것, Third party가 구글로 푸쉬메시지 요청을 보내는것 그리고 푸쉬로 날라온 메시지를 핸들링하는법에 대해 알아보겠습니다.


안드로이드를 개발하면서 DB를 사용하지 않고 개발하는 프로젝트가 몇 없었습니다. 그렇다면 안드로이드가 사용하는 DB는 무었이 있는지 궁금하실텐데 일단 안드로이드가 자체적으로 갖고있는 SQLite가 있으며 외부 데이터베이스는 없습니다. 데이터 베이스의 목적은 여러 디바이스가 공유하여 데이터를 보여주는것에 큰 포커스를 갖고있는데 SQLite는 디바이스 내부에서 처리하는 데이터베이스이기때문에 독립적인 프로젝트라면 활용도가 높겠지만 누구나 동일한 데이터를 공유하는 프로젝트라면 SQLite는 별볼일없는 컴포넌트에 불과하지 않습니다.

그렇다면 외부데이터베이스가 없는데 "어떻게 데이터를 공유하는 프로젝트를 개발하느냐?" 라는 의문이 생기는데 한단계 과정만 더 거친다면 외부데이터베이스를 마음껏 사용할 수 있습니다. 하지만 그러기 위해선 웹 어플리케이션을 구동 할 수 있는 서버가 필요하게됩니다.

안드로이드가 외부데이터베이스에 접근하는 방식은 httpRequest를 하는것이 있고, httpRequest 이후 response된 객체를 json 또는 xml 또는 String 으로 파싱하여 사용하는 방법이 가장 대표적인 방법입니다.

말이 좀 거창한데 이후 진행될 강의를 따라 직접 해보시면 그 원리가 파악되실겁니다.


이 과정에서 기본적으로 알아둬야할것들을 나열해드리겠습니다. 선행학습이 필요하니 해당되는 기본기에 하나라도 부합되지 않으신분들은 해당 과정부터 알고오셔야 강의가 이해됩니다. 주로 웹 어플리케이션 부분입니다.

1. sql을 알고있어야한다.(기본적인 insert , select 까지만)

2. jsp를 활용한 데이터베이스 접속을 모델1 또는 모델2로 설계가 가능해야한다.



그럼 이제 본격적으로 안드로이드가 Third party 쪽에 요청하는 부분을 개발해보도록 하겠습니다. 물론 제가 미리 제작해놓은 클래스를 가져다 쓰셔도되고 독자적으로 개발해도되지만 재활용하는샘치고 제것을 가져다 패키지에 삽입해주시는게 여러모로 빠를겁니다.

ServerRequest.java

위 클래스는의 생성자는 요청url , map으로 구성된 파라미터 , responseHandler , handler 로 구성되어있습니다.

responseHandler와 handler를 추가적으로 준 이유는 요청후에 처리를 하는편이 예외처리에 좀더 도움이 되고, 언제 오류가 날지 모르는 안드로이드로부터 방어적인 프로그래밍을 가능케하려는 목적에 있습니다. 클래스를 패키지에 삽입하셨다면 이제 GCMIntentService 클래스에서 onRegistered 함수에 위 클래스를 인스턴스화하고 responseHandler와 handler를 구성해주시면 됩니다.

예제코드를 보도록 하겠습니다.

package com.example.mygcm;


import java.util.HashMap;


import org.apache.http.HttpEntity;

import org.apache.http.HttpResponse;

import org.apache.http.client.ResponseHandler;

import org.apache.http.util.EntityUtils;


import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.util.Log;

import android.widget.Toast;


import com.google.android.gcm.GCMBaseIntentService;


public class GCMIntentService extends GCMBaseIntentService{

public ServerRequest serverRequest_insert = null;

//GCM에 정상적으로 등록되었을경우 발생하는 메소드

@Override

protected void onRegistered(Context arg0, String arg1) {

// TODO Auto-generated method stub

Log.d("test","등록ID:"+arg1);

HashMap<Object , Object> param = new HashMap<Object , Object>();

param.put("regid", arg1);

serverRequest_insert = new ServerRequest("http://localhost:8080/gcmtest/insert.jsp", param, mResHandler, mHandler);

serverRequest_insert.start();

}

/**

* responseHandler가 mHandler에게 상태메시지를 보냄 해당 메시지를 살펴 토스트로 띄움

*/

private Handler mHandler = new Handler(){

public void handleMessage(Message msg) {

serverRequest_insert.interrupt();

String result = msg.getData().getString("result");

if(result.equals("success")){

Toast.makeText(MainActivity.mContext, "데이터베이스에 regid가 등록되었습니다.", 3000).show();

}else{

Toast.makeText(MainActivity.mContext, "데이터베이스에 regid 등록이 실패하였습니다.", 3000).show();

}

}

};

/**

* 요청후 response에 대한 파싱처리

*/

private ResponseHandler<String> mResHandler = new ResponseHandler<String>(){

public String handleResponse(HttpResponse response) throws org.apache.http.client.ClientProtocolException ,java.io.IOException {

HttpEntity entity = response.getEntity();

Message message = mHandler.obtainMessage();

Bundle bundle = new Bundle();

String result = EntityUtils.toString(entity).trim();

if(result.equals("success")){

bundle.putString("result", "success");

}else{

bundle.putString("result", "fail");

}

message.setData(bundle);

mHandler.sendMessage(message);

return null;

}

};

//GCM에 해지하였을경우 발생하는 메소드

@Override

protected void onUnregistered(Context arg0, String arg1) {

Log.d("test","해지ID:"+arg1);

}

//GCM이 메시지를 보내왔을때 발생하는 메소드

@Override

protected void onMessage(Context arg0, Intent arg1) {

// TODO Auto-generated method stub

}

//오류를 핸들링하는 메소드

@Override

protected void onError(Context arg0, String arg1) {

Log.d("test",arg1);

}

//서비스 생성자

public GCMIntentService() {

super(MainActivity.PROJECT_ID);

Log.d("test","GCM서비스 생성자 실행");

}

}

위 코드는 일단 서버로 요청을한뒤 서버에서 response하는 객체를 읽어 토스트로 표시만 할 뿐입니다. 그러나 서버측에서 어떻게 처리하느냐에 따라 표현하는 토스트도 달리지므로 의미없는 코드는 아닙니다. 또한 추가적으로 예외를 처리할 부분이 많으나 그러려면 여러가지 경우를 추가적으로 처리하고 체크박스에 대한 상태까지도 고려해야하므로 일단은 무시하고 이렇게 가도록 하겠습니다.


Third party에 요청하는 코드를 만들었으니 이제 Third party가 이를 어떻게 받아들여 처리하는지 아래 코드로 확인해보시면 되겠습니다. Third party는 jsp 웹어플리케이션 부분이 되겠습니다 .안드로이드가 아닙니다.

insert.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<%@ page import="java.sql.*" %>

<%

String regid = request.getParameter("regid");

try {

String driverName = "oracle.jdbc.driver.OracleDriver";

String url = "jdbc:oracle:thin:@localhost:1521:XE";

ResultSet rs = null;

Class.forName(driverName);

Connection con = DriverManager.getConnection(url,"gcmtest","gcm123");

Statement stmt = con.createStatement();

String sql = "insert into gcm (regid) values ('" + regid + "')";

rs = stmt.executeQuery(sql);

out.print("success");

con.close();

}catch (Exception e) {

out.print("fail");

}

%>

매우 간결한 코드입니다. 단순히 등록ID를 데이터베이스에 저장만 하는 기능입니다. 이런식으로 디바이스별 등록ID를 데이터베이스에 누적 할 수 있습니다.

위 코드들의 흐름을 보면 아래 그림과 같다고 볼 수있습니다.



insert.jsp 파일은 디비접속및 질의 실행후 그 결과를 페이지로 반환하는데 저는 간단하게 success 또는 fail을 적어 반환시켜보았습니다. 이렇게 할경우 안드로이드쪽에서는 요청한 결과 페이지를 response객체로 반환하는데 이때 이 객체를 간단히 파싱해서 조건 처리를 해보았습니다. 물론 json이나 xml등으로 페이지를 구성해서 반환한다면 저렇게 간단한 파싱은 되지 않지만 어디까지나 기본적인 사항들만을 표현하기 위해 이렇게 만들었습니다.


이제 데이터베이스에 등록ID를 누적하는것을 완성했으니 해당 등록ID와 일치하는 디바이스에 메시지를 날려보는 처리를 구현해보겠습니다. 이번엔 안드로이드가 아니라 서버측에서 구현을 하고 안드로이드측에서 메시지를 받아 핸들하는 처리순서대로 코드를 구성해보겠습니다.

시작하기전에, 라이브러리라는것이 정말 좋은게 막 가져다 조립을 하면 돌아간다는점에서, 안드로이드건 jsp건 둘다 java를 베이스로 둔 점이 정말 마음에 듭니다. 이런 얘기를 하는 이유는 바로 가이드1에서 내려받은 GCM 라이브러리가 jsp에서도 사용된다는 점입니다. 정말 세심하게 코딩하려면 구지 GCM라이브러리를 사용하지 않아도 되지만 저는 라이브러리를 사용한 전송을 구현하겠습니다.

일단 라이브러리의 위치를 찾기 귀찮으시다면 아래 파일을 받아서 라이브러리를 등록해주세요.

json-simple-1.1.1.jar

gcm-server.jar


라이브러리를 등록하셨다면 이제 푸쉬를 보내는 jsp페이지 코딩의 예제를 보여드리겠습니다.

push.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<%@ page import="java.sql.*" %>

<%@ page import="java.util.ArrayList" %>

<%@ page import="com.google.android.gcm.server.*" %>

<%

ArrayList<String> regid = new ArrayList<String>(); //메시지를 보낼 대상들

String MESSAGE_ID = String.valueOf(Math.random() % 100 + 1); //메시지 고유 ID

boolean SHOW_ON_IDLE = true; //기기가 활성화 상태일때 보여줄것인지

int LIVE_TIME = 1; //기기가 비활성화 상태일때 GCM가 메시지를 유효화하는 시간

int RETRY = 2; //메시지 전송실패시 재시도 횟수

String simpleApiKey = "AIzaSyCvu6Ka-rb5zm8EvZkwt6mkXhlerxxe2Qk2s"; //가이드 1때 받은 키

String gcmURL = "https://android.googleapis.com/gcm/send"; //푸쉬를 요청할 구글 주소

try {

String driverName = "oracle.jdbc.driver.OracleDriver";

String url = "jdbc:oracle:thin:@localhost:1521:XE";

ResultSet rs = null;

Class.forName(driverName);

Connection con = DriverManager.getConnection(url,"gcmtest","gcm123");

Statement stmt = con.createStatement();

String sql = "select * from gcm";

rs = stmt.executeQuery(sql);

//모든 등록ID를 리스트로 묶음

while(rs.next()){

regid.add(rs.getString("regid"));

}

con.close();

Sender sender = new Sender(simpleApiKey);

Message message = new Message.Builder()

.collapseKey(MESSAGE_ID)

.delayWhileIdle(SHOW_ON_IDLE)

.timeToLive(LIVE_TIME)

.addData("test","PUSH!!!!")

.build();

MulticastResult result = sender.send(message,regid,RETRY);

}catch (Exception e) {

}

%>

위 코드를 보면서 몇가지 상수와 이전에 얻어둔 simpleApiKey가 보일것입니다. 적당히 코드를 살펴본후 자기것에 맞춰 수정하면 되겠습니다. 중요한건 제가 여려명에게 푸쉬를 보내기위해 Multicast방식을 이용했습니다만, 단 한명이게 보내고자할때는 그냥 sender 방식을 사용해도되겠습니다.

중요시 봐야할점은 데이터를 어떻게 삽입하느냐가 관건인데 message 객체의 함수들은 모두 체이닝형식으로 this를 반환하도록 구현되어있나봅니다. 그래서 저렇게 체이닝의 메소드를 연결하고 build() 메소드가 들어기가 이전에 addData(String , String)의 메소드로 데이터를 삽입해주시면 되겠습니다. key / value의 쌍으로 파라미터를 추가하게 되어있으며 이 데이터는 후에 안드로이드 intent 로 넘어오게됩니다. 안드로이드는 intent에서 key를 갖고 value를 추적할 수 있는데 그래서 key이름을 잊지않도록 합니다.


혹시나 궁금해하시는 분들을 위해 Third party 쪽의 스크린샷을 담아봤습니다.

모델1로 개발한것이며 라이브러리3개와 페이지 2개가 전부입니다. 아 물론 데이터베이스가 같은 서버내에서 구동되는점도 있습니다.


이제 심심하니 푸쉬를 잠깐 날려보도록 하겠습니다. 아직 안드로이드쪽에서 푸쉬를 받는 처리를 하지않았지만 페이지의 요청만으로도 푸쉬가 날라갈 수 있다는점이 있습니다. 후에 푸쉬를 요청하는 방법은 개발자들이 알아서 표현해주시고 저는 일단 아무런 보안없이 푸쉬를 날려보도록 하겠습니다. 저같은경우는 이 주소를 입력하면 푸쉬가 날라갑니다.

http://localhost:8080/gcmtest/push.jsp

라고 주소창에 날리면 푸쉬가 날아갑니다. 물론 날아갔다고 표현하지 않았으니 페이지는 아무것도 뜨지않습니다. 그럼 이제 안드로이드쪽에서 푸쉬로 날라온 메시지를 어떻게 처리하는지 보겠습니다.

이전에 푸쉬로 날라온 메시지를 처리하는 부분은 GCMIntentService 클래스에서 onMessage 함수라고 말씀드렸습니다. 그러니 그쪽부분을 잠시 손봐주면 되겟지만 아쉽게도 이벤터처리메스도들에게는 뷰를 핸들할 권한이 없기때문에 따로 스레드를 포함한 핸들러를 돌려주셔야만 화면에서 받은 메시지를 표현할 수 있습니다.

아래 코드를 보면서 수정해주시면 되겠습니다.

package com.example.mygcm;


import java.util.HashMap;


import org.apache.http.HttpEntity;

import org.apache.http.HttpResponse;

import org.apache.http.client.ResponseHandler;

import org.apache.http.util.EntityUtils;


import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.util.Log;

import android.widget.Toast;


import com.google.android.gcm.GCMBaseIntentService;


public class GCMIntentService extends GCMBaseIntentService {

String gcm_msg = null;

public ServerRequest serverRequest_insert = null;


// GCM에 정상적으로 등록되었을경우 발생하는 메소드

@Override

protected void onRegistered(Context arg0, String arg1) {

// TODO Auto-generated method stub

Log.d("test", "등록ID:" + arg1);

HashMap<Object, Object> param = new HashMap<Object, Object>();

param.put("regid", arg1);

serverRequest_insert = new ServerRequest(

"http://localhost:8080/gcmtest/insert.jsp", param,

mResHandler, mHandler);

serverRequest_insert.start();

}


/**

* 요청후 핸들러에의해 리스트뷰 구성

*/

private Handler mHandler = new Handler() {

public void handleMessage(Message msg) {

serverRequest_insert.interrupt();

String result = msg.getData().getString("result");

if (result.equals("success")) {

Toast.makeText(MainActivity.mContext,

"데이터베이스에 regid가 등록되었습니다.", 3000).show();

} else {

Toast.makeText(MainActivity.mContext,

"데이터베이스에 regid 등록이 실패하였습니다.", 3000).show();

}

}

};


/**

* 요청후 response에 대한 파싱처리

*/

private ResponseHandler<String> mResHandler = new ResponseHandler<String>() {

public String handleResponse(HttpResponse response)

throws org.apache.http.client.ClientProtocolException,

java.io.IOException {

HttpEntity entity = response.getEntity();

Message message = mHandler.obtainMessage();

Bundle bundle = new Bundle();

String result = EntityUtils.toString(entity).trim();

if (result.equals("success")) {

bundle.putString("result", "success");

} else {

bundle.putString("result", "fail");

}

message.setData(bundle);

mHandler.sendMessage(message);

return null;

}

};


// GCM에 해지하였을경우 발생하는 메소드

@Override

protected void onUnregistered(Context arg0, String arg1) {

Log.d("test", "해지ID:" + arg1);

}


// GCM이 메시지를 보내왔을때 발생하는 메소드

@Override

protected void onMessage(Context arg0, Intent arg1) {

// TODO Auto-generated method stub

Log.d("test", "메시지가 왔습니다 : " + arg1.getExtras().getString("test"));

gcm_msg = arg1.getExtras().getString("test");

showMessage();

}


// 오류를 핸들링하는 메소드

@Override

protected void onError(Context arg0, String arg1) {

Log.d("test", arg1);

}


// 서비스 생성자

public GCMIntentService() {

super(MainActivity.PROJECT_ID);

Log.d("test", "GCM서비스 생성자 실행");

}


public void showMessage() {

Thread thread = new Thread(new Runnable() {

public void run() {

handler.sendEmptyMessage(0);

}

});

thread.start();

}


private Handler handler = new Handler() {

public void handleMessage(Message msg) {

Toast.makeText(MainActivity.mContext, "수신 메시지 : "+gcm_msg, 3000).show();

}

};

}


이것으로 안드로이드 GCM 가이드를 마치겠습니다. 결국 레이아웃에 textView 하나는 안쓴채로 진행됬는데요; 사실 토스트말고 textView에 표현해도 상관없는거였지만 잊고있어서^^;

푸쉬를 보내는 방법은 위에서 설명했듯이 페이지 요청으로 보내보았습니다. 하지만 푸쉬가 날라와야할 시점은 개발하는 목적에 따라 다르기때문에 "어느 한 시점에 꼭 푸쉬가 날라와야 정상이다" 라고 단정짓기 어렵습니다.


마지막으로 푸쉬가 날라온 모습입니다.


GCM에 관련된 궁금한 사항들은 댓글로 남겨주시면 같이 고민해 드리겠습니다.