티스토리 뷰

구글 GCM 사용

ExTrus Inc, Lab

 

 

아래 문서에 대한 번역과 HTTP 기반의 서버 푸쉬 코드 첨부

http://developer.android.com/intl/ko/google/gcm/gs.html#server

GCM 시작하기

목차

1.     Creating a Google API Project

2.     Enabling the GCM Service

3.     Obtaining an API Key

4.     Writing a Client App

5.     Writing the Server Code

참고

1.     Google APIs Console page

2.     CCS and User Notifications Signup Form

아래 섹션에서는 GCM 구현을 위한 세팅 작업을 안내한다. 시자하기 전에 구글 플레이 서비스 SDK 설정을 확인하라. SDK구글 클라우드 메세징을 사용하는데 필요하다.

 

완전한 GCM 구현을 위해서는 앱쪽의 구현외에도, 서버쪽의 구현이 필요하다는 것을 유념해라. 이 문서는 클라이언트와 서버쪽 모두를 포함하는 완전한 예제를 제공한다.

구글 API 프로젝트 만들기

 

 

구글 API 프로젝트를 만들려면:

1.    구글 API 콘솔 페이지 로 이동한다.

2.    아직 API 프로젝트를 만들지 않았다면, 이 페이지에서 만들도록 알려줄것이다.

3.   

4.    주의: 이미 프로젝트들이 있다면, 첫 페이지는 대쉬보드 페이지가 될 것이다. 그곳에서 새 프로젝트를 만들려면, 프로젝트 드롭다운 메뉴(좌측 상단에 있는)에서 다른 프로젝트 > 생성을 선택한다.

5.    프로젝트 생성을 클릭한다. 그러면 브라우저의 주소가 아래와 비슷하게 바뀔 것이다.

6.     https://code.google.com/apis/console/#project:4815162342

7.    #project: 다음에 있는 숫자를 적어둬라. (위의 예에서: 4815162342). 이 값은 프로젝트 숫자값이다. 나중에 GCM 보내기 ID(sender ID) 로 사용될 것이다.

GCM 서비스 활성화 하기

 

 

GCM 서비스 활성화 하려면:

1.    메인 구글 API 콘솔페이지에서, 서비스(Services)를 선택한다.

2.    구글 클라이우드 메세징(Google Cloud Messaging) 을 켠다(ON).

3.    서비스 약관 페이지에서 동의한다.

API 키 얻기

 

 

API 키를 얻을려면:

1.    메인 구글 API 콘솔 페이지에서, API 접근(API Access) 를 선택한다. 다음과 비슷한 화면이 표시될 것이다.

2.     

3.    새 서버키 생성(Create new Server key) 을 클릭한다. 서버키나 브라우저키 둘중 아무거나 사용할 수 있다. 서버키를 쓰는 추가 이점은 IP 주소 기반의 화이트리스트를 사용할 수 있는 것이다. 다음 화면이 표시될 것이다.

4.     

5.    생성(Create) 을 클릭한다.

6.     

API (이 예제에서:YourKeyWillBeShownHere)을 적어둔다, 나중에 사용할 것이다.

주의: 만약 키를 바꿀려면, 새 키 생성(Generate new key)을 누른다. 새 키가 만들어지고 이전키도 24시간 동안 여전히 유효하다. 만약 이전키를 당장 제거하고 싶다면(예를들어, 키가 노출되었다고 생각되면) 키 삭제(Delete key)를 누른다.

다음 섹션에서는 클라이언트와 서버쪽 코드를 자성하는 단계를 진행해 나갈 것이다.

클라이언트 앱 만들기

 

 

이 세션에서는 클라이언트쪽 앱을 만드는 것을 안내할 것이다. 그것은 안드로이드에서 구동되는 GCM 가능한 앱을 말한다. 이 클라이언트 샘플은 아래 서버 코드 작성하기에서 보여진 것과 같이 작동하도록 설계되었다.

단계 1: 앱의 Manifest 설정하기

     안드로이드 앱이 등록하고 메세지를 받을 수 있도록 하는 com.google.android.c2dm.permission.RECEIVE 퍼미션 설정

     안드로이드가 등록 아이디를 제3의 서버에 보낼 수 있도록 하는 android.permission.INTERNET 퍼미션 설정.

     GCM이 구글 계정을 필요로 함으로 android.permission.GET_ACCOUNTS 설정.(안드로이드 4.0.4 보다 낮은 버전일 경우에만 필요함)

     메세지를 받았을때, 프로세서를 잠드는 것을 방지하는 android.permission.WAKE_LOCK 퍼미션. 선택사항 - 잠드는 것으로 부터 방지하기를 원하는 경우에만 사용.

     다른앱에서 등록하고 메세지를 받는 것을 방지하기 위한 applicationPackage + ".permission.C2D_MESSAGE" 퍼미션. 퍼미션 이름은 반드시 이 패턴을 따라야 한다.—그렇지 않은 경우 안드로이드 앱은 메세지를 받지 못한다.

     com.google.android.c2dm.intent.RECEIVE 를 받는 카테고리 applicationPackage 를 가지는 리시버. 리시버는 com.google.android.c2dm.SEND 퍼미션을 요구해야 한다, 따라서 GCM 프레임워크만 그 리시버에 메세지를 보낼 수 있게 된다. 메세지를 받는 것은 intent 로 구현되는 것에 유념.

     브로드캐스트 리시버에의해 받은 인텐트를 처리하는 인텐트 서비스. 선택사항.

     만약 GCM 기능이 안드로이드 앱의 기능에서 크리티컬한 것이라면, 반드시 매니페스트에 android:minSdkVersion="8"을 설정하는 것을 확인해라. 이것은 안드로이드앱이 제대로 동작하지 않는 환경에 설치되는 것을 막는다.

GCM을 지원하는 매니페스트로 부터 발췌한 부분:

<manifest package="com.example.gcm" ...>

 

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>

    <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

 

    <permission android:name="com.example.gcm.permission.C2D_MESSAGE"

        android:protectionLevel="signature" />

    <uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />

 

    <application ...>

        <receiver

            android:name=".MyBroadcastReceiver"

            android:permission="com.google.android.c2dm.permission.SEND" >

            <intent-filter>

                <action android:name="com.google.android.c2dm.intent.RECEIVE" />

                <category android:name="com.example.gcm" />

            </intent-filter>

        </receiver>

        <service android:name=".MyIntentService" />

    </application>

 

</manifest>

단계 2: GCM을 사용하기 위한 등록

모바일 단말 구동되는 안드로이드 앱이 메세지를 받기 위해서는 GoogleCloudMessaging의 메쏘드 register(senderID...)를 호출하여 등록을 해야 한다. 이 메쏘드는 앱을 GCM 에 등록하고 등록 ID 를 반환한다. 이 간단한 접근은 이전의 GCM 등록 과정과 교체된다. 보다 자세한 사항은 아래 예제를 보라.

단계 3: 앱 작성하기

마지막으로 앱을 작성하자. GCM 은 여러 방법으로 하는 방법을 제공한다:

     메세징 서버를 위해서, 새로운 GCM Cloud Connection Server (CCS) 써도 되고, 이전의 GCM HTTP server, 써도 되고 두개를 직렬로 연결해도 된다.보다 자세한 토론은, GCM Server 를 보라.

     클라이언트 앱을 작성하려면, (안드로이드에서 구동되는 GCM가능한 앱), 아래 보여지는 GoogleCloudMessaging APIs 를 사용해라. Setup Google Play Services SDK 를 프로젝트에서 사용하도록 설정하는 것을 까먹지 마라.

예제

이 샘플 클라이언트 앱은 GoogleCloudMessaging APIs를 사용하는지 설명한다. 샘플은 메인 액티비티 (DemoActivity) 와 브로드캐스트 리시버(GcmBroadcastReceiver)로 구성된다. 아래 Writing the Server Code 에서 작성된 서버 코드와 같이 사용할 수 있다.

아래에 대해 주의:

     샘플은 기본적으로 두개를 설명함: 등록, 상향 메세징. 상향 메세징은 CCS 서버에 대해서만 가능하다. HTTP 기반의 서버는 상향 메세징을 지원하지 않는다.

     GoogleCloudMessaging 등록 APIs 는 기존 프로세스-지금은 폐기된 클라이언트 헬퍼 라이브러리-를 대체한다, 어떤 서버를 사용하던지 새로운 GoogleCloudMessaging 등록 APIs를 사용하기를 권장한다

등록

안드로이드앱은 메세지를 받기 전에 GCM 서버에 등록해야 한다. 따라서 onCreate()메쏘드에서DemoActivity 는 앱이 GCM 서버와 3자 서버에 등록되었는지 아닌지 체크한다.:

/**

 * Main UI for the demo app.

 */

public class DemoActivity extends Activity {

 

    public static final String EXTRA_MESSAGE = "message";

    public static final String PROPERTY_REG_ID = "registration_id";

    private static final String PROPERTY_APP_VERSION = "appVersion";

    private static final String PROPERTY_ON_SERVER_EXPIRATION_TIME =

            "onServerExpirationTimeMs";

    /**

     * Default lifespan (7 days) of a reservation until it is considered expired.

     */

    public static final long REGISTRATION_EXPIRY_TIME_MS = 1000 * 3600 * 24 * 7;

 

    /**

     * Substitute you own sender ID here.

     */

    String SENDER_ID = "Your-Sender-ID";

 

    /**

     * Tag used on log messages.

     */

    static final String TAG = "GCMDemo";

 

    TextView mDisplay;

    GoogleCloudMessaging gcm;

    AtomicInteger msgId = new AtomicInteger();

    SharedPreferences prefs;

    Context context;

 

    String regid;

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

 

        setContentView(R.layout.main);

        mDisplay = (TextView) findViewById(R.id.display);

 

        context = getApplicationContext();

        regid = getRegistrationId(context);

 

        if (regid.length() == 0) {

            registerBackground();

        }

        gcm = GoogleCloudMessaging.getInstance(this);

    }

...

}

앱은  getRegistrationId() 를 호출하여 shared preference 에 저장된 등록 ID가 있는지 살펴본다.:

/**

 * Gets the current registration id for application on GCM service.

 * <p>

 * If result is empty, the registration has failed.

 *

 * @return registration id, or empty string if the registration is not

 *         complete.

 */

private String getRegistrationId(Context context) {

    final SharedPreferences prefs = getGCMPreferences(context);

    String registrationId = prefs.getString(PROPERTY_REG_ID, "");

    if (registrationId.length() == 0) {

        Log.v(TAG, "Registration not found.");

        return "";

    }

    // check if app was updated; if so, it must clear registration id to

    // avoid a race condition if GCM sends a message

    int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);

    int currentVersion = getAppVersion(context);

    if (registeredVersion != currentVersion || isRegistrationExpired()) {

        Log.v(TAG, "App version changed or registration expired.");

        return "";

    }

    return registrationId;

}

 

...

 

/**

 * @return Application's {@code SharedPreferences}.

 */

private SharedPreferences getGCMPreferences(Context context) {

    return getSharedPreferences(DemoActivity.class.getSimpleName(),

            Context.MODE_PRIVATE);

}

 

만약 등록 ID가 존재하지 않거나 앱이 업데이트 되거나 등록 ID가 만료되면, getRegistrationId() 는 빈값을 반환하고 앱은 새로운 등록ID를 얻어야 한다. getRegistrationId() 아래 메쏘드를 호출하여 앱버전과 등록ID가 만료되었는지 체크한다:

/**

 * @return Application's version code from the {@code PackageManager}.

 */

private static int getAppVersion(Context context) {

    try {

        PackageInfo packageInfo = context.getPackageManager()

                .getPackageInfo(context.getPackageName(), 0);

        return packageInfo.versionCode;

    } catch (NameNotFoundException e) {

        // should never happen

        throw new RuntimeException("Could not get package name: " + e);

    }

}

 

/**

 * Checks if the registration has expired.

 *

 * <p>To avoid the scenario where the device sends the registration to the

 * server but the server loses it, the app developer may choose to re-register

 * after REGISTRATION_EXPIRY_TIME_MS.

 *

 * @return true if the registration has expired.

 */

private boolean isRegistrationExpired() {

    final SharedPreferences prefs = getGCMPreferences(context);

    // checks if the information is not stale

    long expirationTime =

            prefs.getLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, -1);

    return System.currentTimeMillis() > expirationTime;

}

유효한 등록ID가 없다면, DemoActivityregisterBackground()메쏘드를 호출하여 등록한다. GCM메쏘드는 블로킹되기 때문에 이 메쏘드는 백그라운드 쓰레드에서 호출되어야 하는 것에 주의해라. 이 예제는  AsyncTask 를 사용하여 그렇게 한다:

/**

 * Registers the application with GCM servers asynchronously.

 * <p>

 * Stores the registration id, app versionCode, and expiration time in the

 * application's shared preferences.

 */

private void registerBackground() {

    new AsyncTask() {

        @Override

        protected String doInBackground(Void... params) {

            String msg = "";

            try {

                if (gcm == null) {

                    gcm = GoogleCloudMessaging.getInstance(context);

                }

                regid = gcm.register(SENDER_ID);

                msg = "Device registered, registration id=" + regid;

 

                // You should send the registration ID to your server over HTTP,

                // so it can use GCM/HTTP or CCS to send messages to your app.

 

                // For this demo: we don't need to send it because the device

                // will send upstream messages to a server that echo back the message

                // using the 'from' address in the message.

 

                // Save the regid - no need to register again.

                setRegistrationId(context, regid);

            } catch (IOException ex) {

                msg = "Error :" + ex.getMessage();

            }

            return msg;

        }

 

        @Override

        protected void onPostExecute(String msg) {

            mDisplay.append(msg + "\n");

        }

    }.execute(null, null, null);

}

등록 후에는 앱은 setRegistrationId()호출하여 등록ID를 다음에 사용할 수 있도록 shared prefeneces에 저장한다:

/**

 * Stores the registration id, app versionCode, and expiration time in the

 * application's {@code SharedPreferences}.

 *

 * @param context application's context.

 * @param regId registration id

 */

private void setRegistrationId(Context context, String regId) {

    final SharedPreferences prefs = getGCMPreferences(context);

    int appVersion = getAppVersion(context);

    Log.v(TAG, "Saving regId on app version " + appVersion);

    SharedPreferences.Editor editor = prefs.edit();

    editor.putString(PROPERTY_REG_ID, regId);

    editor.putInt(PROPERTY_APP_VERSION, appVersion);

    long expirationTime = System.currentTimeMillis() + REGISTRATION_EXPIRY_TIME_MS;

 

    Log.v(TAG, "Setting registration expiry time to " +

            new Timestamp(expirationTime));

    editor.putLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, expirationTime);

    editor.commit();

}

메세지 보내기

사용자가 앱의 보내기(Send)버튼을 누르면 앱은 상향 메세지를 새로운 GoogleCloudMessagingAPIs를 통해 보낸다. 상향 메세지를 받기 위해서는, 메세지 처리 서버가 CCS 에 연결되어 있어야 한다. 에서 Writing the Server Code 보여지는 XMPP 클라이언트 샘플로 CCS 에 접속할 수 있다.

public void onClick(final View view) {

    if (view == findViewById(R.id.send)) {

        new AsyncTask() {

            @Override

            protected String doInBackground(Void... params) {

                String msg = "";

                try {

                    Bundle data = new Bundle();

                    data.putString("hello", "World");

                    String id = Integer.toString(msgId.incrementAndGet());

                    gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data);

                    msg = "Sent message";

                } catch (IOException ex) {

                    msg = "Error :" + ex.getMessage();

                }

                return msg;

            }

 

            @Override

            protected void onPostExecute(String msg) {

                mDisplay.append(msg + "\n");

            }

        }.execute(null, null, null);

    } else if (view == findViewById(R.id.clear)) {

        mDisplay.setText("");

    }

}

Step 1 에서 설명했듯이, 앱은 com.google.android.c2dm.intent.RECEIVE 인테트를 받는 브로드캐스트 리시버를 가진다. 이것은 GCM이 메세지를 전달하기 위해 사용하는 메카니즘이다. onClick() 이 호출되면 gcm.send() 이 호출된다. 그것은 브로드캐스트 리시버의 onReceive() 메쏘드를 호출되게 한다, 그것은 GCM메세지를 처리해야하는 책임을 갖는다. 이 예에서는 리시버의onReceive() 메쏘드가 sendNotification() 을 호출하여 메세지를 알림바에 두게 된다:

/**

 * Handling of GCM messages.

 */

public class GcmBroadcastReceiver extends BroadcastReceiver {

    static final String TAG = "GCMDemo";

    public static final int NOTIFICATION_ID = 1;

    private NotificationManager mNotificationManager;

    NotificationCompat.Builder builder;

    Context ctx;

    @Override

    public void onReceive(Context context, Intent intent) {

        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);

        ctx = context;

        String messageType = gcm.getMessageType(intent);

        if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {

            sendNotification("Send error: " + intent.getExtras().toString());

        } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {

            sendNotification("Deleted messages on server: " +

                    intent.getExtras().toString());

        } else {

            sendNotification("Received: " + intent.getExtras().toString());

        }

        setResultCode(Activity.RESULT_OK);

    }

 

    // Put the GCM message into a notification and post it.

    private void sendNotification(String msg) {

        mNotificationManager = (NotificationManager)

                ctx.getSystemService(Context.NOTIFICATION_SERVICE);

 

        PendingIntent contentIntent = PendingIntent.getActivity(ctx, 0,

                new Intent(ctx, DemoActivity.class), 0);

 

        NotificationCompat.Builder mBuilder =

                new NotificationCompat.Builder(ctx)

        .setSmallIcon(R.drawable.ic_stat_gcm)

        .setContentTitle("GCM Notification")

        .setStyle(new NotificationCompat.BigTextStyle()

        .bigText(msg))

        .setContentText(msg);

 

        mBuilder.setContentIntent(contentIntent);

        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());

    }

}

서버 코드 작성하기

 

 

파이썬으로 쓰여진 CCS 서버의 예이다. 위의 클라이어늩 예와 같이 사용할 수 있다. 이 샘플 에코 서버는 초기 메세지를 보내고, 모든 상향 메세지를 받았을 경우는 상향메세지를 보낸 앱에 대해 더미 메세지를 응답메세지로 보낸다. 이것은 어떻게 GCM 메세지를 XMPP를 사용하여 연결하고 보내고 받는지 보여준다. 이것은 이전의 운영 배포에서는 사용되지 말아야 한다. 예를 들면 HTTP 기반의 서버들 말이다 그것은 GCM Server 를 참고해라. (역주: 해당 페이지는 분리되어 있지만, 아래에 추가 했으며, 새로운 서버는 XMPP 를 이용해서 메세지를 보내고 받으며, 이전 방식은 HTTP 를 이용해서 메세지를 단말에 푸쉬했습니다. HTTP를 사용하는 방식은 아직도 유용하며 물론 새로운 XMPP 를 사용하는 것이 더 좋을 것입니다.)

#!/usr/bin/python

import sys, json, xmpp, random, string

 

SERVER = 'gcm.googleapis.com'

PORT = 5235

USERNAME = ''

PASSWORD = ''

REGISTRATION_ID = ''

 

unacked_messages_quota = 1000

send_queue = []

 

# Return a random alphanumerical id

def random_id():

  rid = ''

  for x in range(8): rid += random.choice(string.ascii_letters + string.digits)

  return rid

 

def message_callback(session, message):

  global unacked_messages_quota

  gcm = message.getTags('gcm')

  if gcm:

    gcm_json = gcm[0].getData()

    msg = json.loads(gcm_json)

    if not msg.has_key('message_type'):

      # Acknowledge the incoming message immediately.

      send({'to': msg['from'],

            'message_type': 'ack',

            'message_id': msg['message_id']})

      # Queue a response back to the server.

      if msg.has_key('from'):

        # Send a dummy echo response back to the app that sent the upstream message.

        send_queue.append({'to': msg['from'],

                           'message_id': random_id(),

                           'data': {'pong': 1}})

    elif msg['message_type'] == 'ack' or msg['message_type'] == 'nack':

      unacked_messages_quota += 1

 

def send(json_dict):

  template = ("<message><gcm xmlns='google:mobile:data'>{1}</gcm></message>")

  client.send(xmpp.protocol.Message(

      node=template.format(client.Bind.bound[0], json.dumps(json_dict))))

 

def flush_queued_messages():

  global unacked_messages_quota

  while len(send_queue) and unacked_messages_quota > 0:

    send(send_queue.pop(0))

    unacked_messages_quota -= 1

 

client = xmpp.Client('gcm.googleapis.com', debug=['socket'])

client.connect(server=(SERVER,PORT), secure=1, use_srv=False)

auth = client.auth(USERNAME, PASSWORD)

if not auth:

  print 'Authentication failed!'

  sys.exit(1)

 

client.RegisterHandler('message', message_callback)

 

send_queue.append({'to': REGISTRATION_ID,

                   'message_id': 'reg_id',

                   'data': {'message_destination': 'RegId',

                            'message_id': random_id()}})

 

while True:

  client.Process(1)

  flush_queued_messages()

 

 

-------------------------------------------------------------

GCM Server

This document gives examples of GCM server-side code for HTTP. For an example of an XMPP server (Cloud Connection Server), seeGetting Started. Note that a full GCM implementation requires a client-side implementation, in addition to the server. For a complete working example that includes client and server-side code, seeGetting Started.

Requirements

 

 

For the web server:

     Ant 1.8 (it might work with earlier versions, but it's not guaranteed).

     One of the following:

     A running web server compatible with Servlets API version 2.5, such as Tomcat 6 or Jetty, or

     Java App Engine SDK version 1.6 or later.

     A Google account registered to use GCM.

     The API key for that account.

For the Android application:

     Emulator (or device) running Android 2.2 with Google APIs.

     The Google API project number of the account registered to use GCM.

Setting Up GCM

 

 

Before proceeding with the server and client setup, it's necessary to register a Google account with the Google API Console, enable Google Cloud Messaging in GCM, and obtain an API key from the Google API Console.

For instructions on how to set up GCM, see Getting Started.

Setting Up an HTTP Server

 

 

This section describes the different options for setting up an HTTP server.

Using a standard web server

To set up the server using a standard, servlet-compliant web server:

1.    From the open source site, download the following directories: gcm-server, samples/gcm-demo-server, andsamples/gcm-demo-appengine.

2.    In a text editor, edit the samples/gcm-demo-server/WebContent/WEB-INF/classes/api.key and replace the existing text with the API key obtained above.

3.    In a shell window, go to the samples/gcm-demo-server directory.

4.    Generate the server's WAR file by running ant war:

5.    $ ant war

6.     

7.    Buildfile:build.xml

8.     

9.    init:

10.     [mkdir] Created dir: build/classes

11.     [mkdir] Created dir: dist

12.   

13.  compile:

14.     [javac] Compiling 6 source files to build/classes

15.   

16.  war:

17.       [war] Building war: dist/gcm-demo.war

18.   

19.  BUILD SUCCESSFUL

20.  Total time: 0 seconds

21.  Deploy the dist/gcm-demo.war to your running server. For instance, if you're using Jetty, copy gcm-demo.warto the webapps directory of the Jetty installation.

22.  Open the server's main page in a browser. The URL depends on the server you're using and your machine's IP address, but it will be something like http://192.168.1.10:8080/gcm-demo/home, where gcm-demo is the application context and /home is the path of the main servlet.

Note: You can get the IP by running ifconfig on Linux or MacOS, or ipconfig on Windows.

You server is now ready.

Using App Engine for Java

To set up the server using a standard App Engine for Java:

1.    Get the files from the open source site, as described above.

2.    In a text editor, edit samples/gcm-demo-appengine/src/com/google/android/gcm/demo/server/ApiKeyInitializer.java and replace the existing text with the API key obtained above.

3.    Note: The API key value set in that class will be used just once to create a persistent entity on App Engine. If you deploy the application, you can use App Engine's Datastore Viewer to change it later.

4.    In a shell window, go to the samples/gcm-demo-appengine directory.

5.    Start the development App Engine server by ant runserver, using the -Dsdk.dir to indicate the location of the App Engine SDK and -Dserver.host to set your server's hostname or IP address:

6.    $ ant -Dsdk.dir=/opt/google/appengine-java-sdk runserver -Dserver.host=192.168.1.10

7.    Buildfile: gcm-demo-appengine/build.xml

8.     

9.    init:

10.      [mkdir] Created dir: gcm-demo-appengine/dist

11.   

12.  copyjars:

13.   

14.  compile:

15.   

16.  datanucleusenhance:

17.    [enhance] DataNucleus Enhancer (version 1.1.4) : Enhancement of classes

18.    [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=28 ms, enhance=0 ms, total=28 ms. Consult the log for full details

19.    [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

20.   

21.  runserver:

22.       [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.jetty.JettyLogger info

23.       [java] INFO: Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLogger

24.       [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml

25.       [java] INFO: Successfully processed gcm-demo-appengine/WebContent/WEB-INF/appengine-web.xml

26.       [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml

27.       [java] INFO: Successfully processed gcm-demo-appengine/WebContent/WEB-INF/web.xml

28.       [java] Jun 15, 2012 8:46:09 PM com.google.android.gcm.demo.server.ApiKeyInitializer contextInitialized

29.       [java] SEVERE: Created fake key. Please go to App Engine admin console, change its value to your API Key (the entity type is 'Settings' and its field to be changed is 'ApiKey'), then restart the server!

30.       [java] Jun 15, 2012 8:46:09 PM com.google.appengine.tools.development.DevAppServerImpl start

31.       [java] INFO: The server is running at http://192.168.1.10:8080/

32.       [java] Jun 15, 2012 8:46:09 PM com.google.appengine.tools.development.DevAppServerImpl start

33.       [java] INFO: The admin console is running at http://192.168.1.10:8080/_ah/admin

34.  Open the server's main page in a browser. The URL depends on the server you're using and your machine's IP address, but it will be something like http://192.168.1.10:8080/home, where /home is the path of the main servlet.

35.  Note: You can get the IP by running ifconfig on Linux or MacOS, or ipconfig on Windows.

You server is now ready.

 

첨부 GCM 서버에 HTTP 기반으로 푸쉬하는 예제 코드(Java)

 

 

 

/**

 */

@SuppressWarnings("serial")

public class C2DMessaging {

            

             /**

              * the key value of the gcm apk key system property.

              */

             public static final String SYSTEM_GCM_API_KEY = "mobikit.gcm.apikey";

 

    private static final String UPDATE_CLIENT_AUTH = "Update-Client-Auth";

   

    private static final Logger log = Logger.getLogger(C2DMessaging.class.getName());

 

    public static final String PARAM_REGISTRATION_ID = "registration_id";

 

    public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";

 

    public static final String PARAM_COLLAPSE_KEY = "collapse_key";

 

    private static final String UTF8 = "UTF-8";

 

    /**

     * Jitter - random interval to wait before retry.

     */

    public static final int DATAMESSAGING_MAX_JITTER_MSEC = 3000;

 

    static C2DMessaging singleton;

 

    final C2DMConfigLoader serverConfig;

 

    // Testing

    protected C2DMessaging() {

        serverConfig = null;

    }

   

    private C2DMessaging(C2DMConfigLoader serverConfig) {

        this.serverConfig = serverConfig;

       

        SchemeRegistry schemeRegistry = new SchemeRegistry();

        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

        schemeRegistry.register(new Scheme("https", new TrustAllSSLSocketFactory(), 443));

    }

 

    public synchronized static C2DMessaging get(String id, String pwd) {

        if (singleton == null) {

            C2DMConfigLoader serverConfig = new C2DMConfigLoader(id, pwd);

            singleton = new C2DMessaging(serverConfig);

        }

        return singleton;

    }

 

    /**

     *

     * @param registerationId

     * @param collapse

     * @param params

     * @param values

     * @return

     * @throws HttpException

     * @throws IOException - the error message has a error code comes from C2DM server

     *  Error=[error code]

QuotaExceeded — Too many messages sent by the sender. Retry after a while.

DeviceQuotaExceeded — Too many messages sent by the sender to a specific device. Retry after a while.

MissingRegistration — Missing registration_id. Sender should always add the registration_id to the request.

InvalidRegistration — Bad registration_id. Sender should remove this registration_id.

MismatchSenderId — The sender_id contained in the registration_id does not match the sender id used to register with the C2DM servers.

NotRegistered — The user has uninstalled the application or turned off notifications. Sender should stop sending messages to this device and delete the registration_id. The client needs to re-register with the c2dm servers to receive notifications again.

MessageTooBig — The payload of the message is too big, see the limitations. Reduce the size of the message.

MissingCollapseKey — Collapse key is required. Include collapse key in the request.

     */

    public boolean sendNoRetryHttp(String registerationId, String collapse, String[] params, String[] values) throws HttpException, IOException {

        HttpClient client = new HttpClient();

        client.getParams().setParameter("http.useragent", "mobikitclient");

 

        BufferedReader br = null;

 

        PostMethod method = new PostMethod("https://android.googleapis.com/gcm/send");

        method.addParameter("registration_id", registerationId);

        method.addParameter("collapse_key", collapse );

        //method.addParameter("delay_while_idle", "1");

 

        for(int i = 0, l = params.length; i < l; ++i) {

            String param = params[i];

            String value = values[i];

            method.addParameter("data."+param, value);

        }

 

        //String authToken = serverConfig.getToken();

        String gcmApiKey = System.getProperty(SYSTEM_GCM_API_KEY);

       

        String authToken = gcmApiKey != null ? gcmApiKey : "AIzaSyC98MMogAvSrEmLNN5BIB6-7kbciB9coM8";// gcm api-key.

        method.addRequestHeader("Authorization", "key="+authToken);

 

        try{

            int responseCode = client.executeMethod(method);

 

            if(responseCode == HttpStatus.SC_NOT_IMPLEMENTED) {

                System.err.println("The Post method is not implemented by this URI");

                // still consume the response body

                method.getResponseBodyAsString();

            } else {

                br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));

                String responseLine;

                StringBuffer sb = new StringBuffer();

                while(((responseLine = br.readLine()) != null)) {

                    System.err.println("ResponseLine=["+responseLine+"]");

                    sb.append(responseLine);

                }

                responseLine = sb.toString();

                /////////////

                if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED ||

                        responseCode == HttpURLConnection.HTTP_FORBIDDEN) {

                    // The token is too old - return false to retry later, will fetch the token

                    // from DB. This happens if the password is changed or token expires. Either admin

                    // is updating the token, or Update-Client-Auth was received by another server,

                    // and next retry will get the good one from database.

                    log.warning("Unauthorized - need token");

                    serverConfig.invalidateCachedToken();

                    return false;

                }

 

                // Check for updated token header

                Header header = method.getResponseHeader(UPDATE_CLIENT_AUTH);

                String updatedAuthToken = header != null ? header.getValue() : null;

                if (updatedAuthToken != null && !authToken.equals(updatedAuthToken)) {

                    log.info("Got updated auth token from datamessaging servers: " +

                            updatedAuthToken);

                    serverConfig.updateToken(updatedAuthToken);

                }

 

                // NOTE: You *MUST* use exponential backoff if you receive a 503 response code.

                // Since App Engine's task queue mechanism automatically does this for tasks that

                // return non-success error codes, this is not explicitly implemented here.

                // If we weren't using App Engine, we'd need to manually implement this.

                System.err.println("ResponseLine=["+responseLine+"]");

                if (responseLine == null || responseLine.equals("")) {

                    log.info("Got " + responseCode + ", [" + responseLine+"] " +

                            " response from Google AC2DM endpoint.");

                    throw new IOException("Got empty response from Google AC2DM endpoint.");

                }

 

                String[] responseParts = responseLine.split("=", 2);

                if (responseParts.length != 2) {

                    log.warning("Invalid message from google: " +

                            responseCode + " " + responseLine);

                    throw new IOException("Invalid response from Google " +

                            responseCode + " " + responseLine);

                }

 

                if (responseParts[0].equals("id")) {

                    log.info("Successfully sent data message to device: " + responseLine);

                    return true;

                }

 

                if (responseParts[0].equals("Error")) {

                    String err = responseParts[1];

                    log.warning("Got error response from Google datamessaging endpoint: " + err);

                    // No retry.

                    // TODO(costin): show a nicer error to the user.

                    throw new IOException(err);

                } else {

                    // 500 or unparseable response - server error, needs to retry

                    log.warning("Invalid response from google " + responseLine + " " +

                            responseCode);

                    return false;

                }

                //////////////////////////

            }

        } finally {

            method.releaseConnection();

            if(br != null) try { br.close(); } catch (Exception fe) {}

        }

        return false;

    }

 

    public boolean sendNoRetry(String registrationId,

            String collapse,

            Map<String, String[]> params,

            boolean delayWhileIdle)

        throws IOException {

       

        log.info("sendNoRetry");

 

        // Send a sync message to this Android device.

        StringBuilder postDataBuilder = new StringBuilder();

        postDataBuilder.append(PARAM_REGISTRATION_ID).

            append("=").append(registrationId);

 

        if (delayWhileIdle) {

            postDataBuilder.append("&")

                .append(PARAM_DELAY_WHILE_IDLE).append("=1");

        }

        postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=").

            append(collapse);

 

        for (Object keyObj: params.keySet()) {

            String key = (String) keyObj;

            if (key.startsWith("data.")) {

                String[] values = params.get(key);

                postDataBuilder.append("&").append(key).append("=").

                    append(URLEncoder.encode(values[0], UTF8));

            }

        }

 

        byte[] postData = postDataBuilder.toString().getBytes(UTF8);

 

        // Hit the dm URL.

        URL url = new URL(serverConfig.getC2DMUrl());

        log.info("url="+url);

       

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setDoOutput(true);

        conn.setUseCaches(false);

        conn.setRequestMethod("POST");

        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");

        conn.setRequestProperty("Content-Length", Integer.toString(postData.length));

        String authToken = serverConfig.getToken();

        conn.setRequestProperty("Authorization", "GoogleLogin auth=" + authToken);

 

        log.info("postData...="+postData);

        OutputStream out = conn.getOutputStream();

 

        log.info("postData888="+postData);

        out.write(postData);

        out.close();

 

        log.info("postData="+postData);

        int responseCode = conn.getResponseCode();

        log.info("responseCode="+responseCode);

 

        if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED ||

                responseCode == HttpURLConnection.HTTP_FORBIDDEN) {

            // The token is too old - return false to retry later, will fetch the token

            // from DB. This happens if the password is changed or token expires. Either admin

            // is updating the token, or Update-Client-Auth was received by another server,

            // and next retry will get the good one from database.

            log.warning("Unauthorized - need token");

            serverConfig.invalidateCachedToken();

            return false;

        }

 

        // Check for updated token header

        String updatedAuthToken = conn.getHeaderField(UPDATE_CLIENT_AUTH);

        if (updatedAuthToken != null && !authToken.equals(updatedAuthToken)) {

            log.info("Got updated auth token from datamessaging servers: " +

                    updatedAuthToken);

            serverConfig.updateToken(updatedAuthToken);

        }

 

        String responseLine = new BufferedReader(new InputStreamReader(conn.getInputStream()))

            .readLine();

 

        // NOTE: You *MUST* use exponential backoff if you receive a 503 response code.

        // Since App Engine's task queue mechanism automatically does this for tasks that

        // return non-success error codes, this is not explicitly implemented here.

        // If we weren't using App Engine, we'd need to manually implement this.

        if (responseLine == null || responseLine.equals("")) {

            log.info("Got " + responseCode +

                    " response from Google AC2DM endpoint.");

            throw new IOException("Got empty response from Google AC2DM endpoint.");

        }

 

        String[] responseParts = responseLine.split("=", 2);

        if (responseParts.length != 2) {

            log.warning("Invalid message from google: " +

                    responseCode + " " + responseLine);

            throw new IOException("Invalid response from Google " +

                    responseCode + " " + responseLine);

        }

 

        if (responseParts[0].equals("id")) {

            log.info("Successfully sent data message to device: " + responseLine);

            return true;

        }

 

        if (responseParts[0].equals("Error")) {

            String err = responseParts[1];

            log.warning("Got error response from Google datamessaging endpoint: " + err);

            // No retry.

            // TODO(costin): show a nicer error to the user.

            throw new IOException(err);

        } else {

            // 500 or unparseable response - server error, needs to retry

            log.warning("Invalid response from google " + responseLine + " " +

                    responseCode);

            return false;

        }

    }

 

    /**

     * Helper method to send a message, with 2 parameters.

     *

     * Permanent errors will result in IOException.

     * Retriable errors will cause the message to be scheduled for retry.

     */

    public void sendWithRetry(String token, String collapseKey,

            String name1, String value1, String name2, String value2,

            String name3, String value3)

                throws IOException {

 

        Map<String, String[]> params = new HashMap<String, String[]>();

        if (value1 != null) params.put("data." + name1, new String[] {value1});

        if (value2 != null) params.put("data." + name2, new String[] {value2});

        if (value3 != null) params.put("data." + name3, new String[] {value3});

 

        boolean sentOk = sendNoRetry(token, collapseKey, params, true);

        if (!sentOk) {

            //retry(token, collapseKey, params, true);

        }

    }

 

    public boolean sendNoRetry(String token, String collapseKey,

            String name1, String value1, String name2, String value2,

            String name3, String value3)

                throws IOException {

 

        Map<String, String[]> params = new HashMap<String, String[]>();

        if (value1 != null) params.put("data." + name1, new String[] {value1});

        if (value2 != null) params.put("data." + name2, new String[] {value2});

        if (value3 != null) params.put("data." + name3, new String[] {value3});

 

        try {

            return sendNoRetry(token, collapseKey, params, true);

        } catch (IOException ex) {

            return false;

        }

    }

 

    public boolean sendNoRetry(String token, String collapseKey,

            String... nameValues)

                throws IOException {

 

        Map<String, String[]> params = new HashMap<String, String[]>();

        int len = nameValues.length;

        if (len % 2 == 1) {

            len--; // ignore last

        }

        for (int i = 0; i < len; i+=2) {

            String name = nameValues[i];

            String value = nameValues[i + 1];

            if (name != null && value != null) {

                params.put("data." + name, new String[] {value});

            }

        }

 

        try {

            return sendNoRetry(token, collapseKey, params, true);

        } catch (IOException ex) {

            return false;

        }

    }

 

 

             public static void main(String[] args) throws HttpException, IOException {

                           String rid = "APA91bFIEkT5CPP4uq76bR_rjxt0p4IzAcvU_kWv0cNb0Xrm1MFzuv7tx59IlgksFtBfvTqfap_aJYEJaLdOy18sTvhiVIJQPNq3n7hxa3NcBztiYnXF9C7aO2qE5Tx05efrslLjsiXx1b8GtCFW9NeTJRXkKSIufne3AvrLTZ26k7sGrcx5oJU";

 

                           C2DMessaging push = C2DMessaging.get("","");

 

 

                           String vcode = UUID.randomUUID().toString();

                           String msg = CommandMSG.rtnCMDMessage(vcode, "SET", "PWD_3792");

                           String pingCommand = new StringBuffer("mobikit.lab@gmail.com").append(' ').append(msg).toString();

                           push.sendNoRetryHttp(rid, String.valueOf(System.currentTimeMillis()), new String[] {"msg"}, new String[] {pingCommand});

             }

 

 

}

 

 

공지사항
최근에 올라온 글