2015년 2월 21일 토요일

Google Cloud Messaging (GCM) 구현하기 - 3/4

클라이언트 앱 만들기

"Google Cloud Messaging (GCM) 구현하기 - 1/4"에서 설명했듯이 GCM의 구성 요소는 서드 파티 서버와 GCM Connection Server, 클라이언트 앱으로 이루어진다. "Google Cloud Messaging (GCM) 구현하기 - 2/4"에서는 GCM Connection Server를 다뤘고 이번 포스팅에서는 클라이언트 앱을 다룬다.

GCM 클라이언트 앱 요구사항
  • 구글 플레이 스토어 앱이 설치된 안드로이드 2.2 이상의 디바이스
  • 구글 플레이 서비스가 제공하고는 GCM의 새로운 기능을 사용하려면 안드로이드 2.3 이상이어야 함.
  • GCM은 연결된 구글 서비스를 이용함. 따라서 3.0 이하 버전에서는 디바이스와 구글 계정이 연동되어 있어야 함. 4.0.4 이상부터는 구글 계정 연동이 필요 요소는 아님.


1. 프로젝트 생성하기

안드로이드 스튜디오에서 프로젝트를 생성한다.
프로젝트 이름은 AndroidGcmClient로 정하고 Blank Activity를 선택했다.


2. Google Play Service 프로젝트에 추가하기

build.gradle 파일을 열어 dependency에 구글 플레이 서비스를 추가한다.
dependencies {
    compile 'com.google.android.gms:play-services:6.5.87'
}


3. 애플리케이션 메니페스트 수정하기

클라이언트 앱에서 GCM을 이용하려면 퍼미션을 추가해야 한다. 안드로이드가 생성한 기본 템플릿 프로젝트에서 추가된 부분은 4~11번째 줄까지이다.

 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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.icelancer.androidgcmclient" >
    <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.icelancer.androidgcmclient.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="com.icelancer.androidgcmclient.permission.C2D_MESSAGE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

각 퍼미션의 의미
  • "android.permission.INTERNET": 인터넷 연결을 의미한다. 이는 서드 파티 서버와 GCM Connection Server 연결에 필요하다.
  • "android.permission.GET_ACCOUNTS": 안드로이드 4.0.4 이상에서는 필요 없지만, 그 이하 버전에서는 구글 계정과 반드시 연결되어 있어야만 푸쉬 메시지를 보낼 수 있기 때문에 필요한 퍼미션이다. 앱의 최소 버전을 4.0.4로 설정했다면 추가하지 않아도 된다.
  • "android.permission.WAKE_LOCK": 디바이스 화면이 꺼져 있거나, 잠겨 있을 때 화면을 켜고 푸쉬 메시지를 표시할 때 필요하다. wake lock이 필요 없다면 추가하지 않아도 된다.
  • "com.google.android.c2dm.permission.RECEIVE": 안드로이드 앱이 GCM Connection Server에 메시지를 받겠다고 등록하고 또 메시지를 받을 때 필요한 퍼미션이다.
  • "어플리케이션 패키지" + ".permission.C2D_MESSAGE": GCM Connection Server가 전송한 메시지를 다른 앱이 받아서 처리할 수 없도록 추가하는 퍼미션이다. 애플리케이션 패키지는 manifest의 package 속성에 입력한 값을 넣도록 한다.

4. 구글 플레이 서비스 체크하기 (최소 버전 4.0.4 이상 앱 에서는 패스~)

MainActivity에 구글 플레이 서비스 확인 로직을 추가한다.
 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
public class MainActivity extends ActionBarActivity {
    private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (checkPlayServices()) {

        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        checkPlayServices();
    }

    private boolean checkPlayServices() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
                GooglePlayServicesUtil.getErrorDialog(resultCode, this,
                        PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i("ICELANCER", "This device is not supported.");
                finish();
            }
            return false;
        }
        return true;
    }
}

checkPlayService 메서드
이 메서드에서는 구글 플레이 서비스를 이용할 수 있는지 확인하고 이용할 수 없다면 에러 다이얼로그를 띄워 사용자가 구글 플레이 스토어를 설치하거나 설정에서 활성화하도록 가이드한다.

만든 checkPlayService 메서드는 onCreateonResume에서 호출한다. onCreate에서는 구글 플레이 서비스 사용 가능 여부를 확인하지 않고 GCM을 사용하지 못하도록 방지하는 역할을 하며 onResume에서는 뒤로 가기 버튼 등을 통해 다시 MainActivity에 돌아왔을 때 다시 한 번 체크하는 역할을 한다.


5. GCM 등록하기

GCM 서버로 부터 메시지를 받기 전에 먼저 GCM 서버에 등록 과정을 거쳐야 한다. 등록이 완료되면 GCM 서버는 등록 아이디(registration ID)를 발급해주는데 추후 사용하려면 이를 저장해두어야 하고 반드시 외부에 등록 아이디가 유출되어서는 안된다. SENDER_ID의 값은 구글 API 콘솔의 "프로젝트 번호"를 넣는다.


 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
public class MainActivity extends ActionBarActivity {
    private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

    // SharedPreferences에 저장할 때 key 값으로 사용됨.
    public static final String PROPERTY_REG_ID = "registration_id";

    // SharedPreferences에 저장할 때 key 값으로 사용됨.
    private static final String PROPERTY_APP_VERSION = "appVersion";
    private static final String TAG = "ICELANCER";

    String SENDER_ID = "Your-Sender-ID";

    GoogleCloudMessaging gcm;
    SharedPreferences prefs;
    Context context;

    String regid;
    private TextView mDisplay;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        context = getApplicationContext();

        if (checkPlayServices()) {
            gcm = GoogleCloudMessaging.getInstance(this);
            regid = getRegistrationId(context);

            if (regid.isEmpty()) {
                registerInBackground();
            }
        } else {
            Log.i(TAG, "No valid Google Play Services APK found.");
        }
    }
#27: GoogleCloudMessaging 클래스의 인스턴스를 생성한다.
#28: 기존에 발급받은 등록 아이디를 가져온다.
#30: 기존에 발급된 등록 아이디가 없으면 registerInBackground 메서드를 호출해 GCM 서버에 발급을 요청한다.

getRegistrationIdregisterInBackground를 구현해야하는데 우선 getRegistrationId를 먼저 구현한다.
getRegistrationId 메서드는 SharedPreference에 등록 아이디가 저장되어 있는지 확인하고 없으면 빈 문자열을 있으면 기존에 등록된 등록 아이디를 반환하는 역할을 수행한다. 또한 앱 버전이 등록 아이디를 발급받은 시점과 달라도 빈 문자열을 반환한다.

 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
    private String getRegistrationId(Context context) {
        final SharedPreferences prefs = getGCMPreferences(context);
        String registrationId = prefs.getString(PROPERTY_REG_ID, "");
        if (registrationId.isEmpty()) {
            Log.i(TAG, "Registration not found.");
            return "";
        }
        
        // 앱이 업데이트 되었는지 확인하고, 업데이트 되었다면 기존 등록 아이디를 제거한다.
        // 새로운 버전에서도 기존 등록 아이디가 정상적으로 동작하는지를 보장할 수 없기 때문이다.
        int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE)
        int currentVersion = getAppVersion(context);
        if (registeredVersion != currentVersion) {
            Log.i(TAG, "App version changed.");
            return "";
        }
        return registrationId;
    }

    private SharedPreferences getGCMPreferences(Context context) {
        return getSharedPreferences(MainActivity.class.getSimpleName(),
                Context.MODE_PRIVATE);
    }

    private static int getAppVersion(Context context) {
        try {
            PackageInfo packageInfo = context.getPackageManager()
                    .getPackageInfo(context.getPackageName(), 0);
            return packageInfo.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            // should never happen
            throw new RuntimeException("Could not get package name: " + e);
        }
    }
#2: 이전에 저장해둔 등록 아이디를 SharedPreferences에서 가져온다.
#3: 저장해둔 등록 아이디가 없으면 빈 문자열을 반환한다.
#13: 이전에 등록 아이디를 저장한 앱의 버전과 현재 버전을 비교해 버전이 변경되었으면 빈 문자열을 반환한다.

등록 아이디가 없거나 앱 버전이 변경되면 registerInBackground 메서드를 호출한다. 메서드 이름에서 알 수 있듯이 백그라운드에서 호출하는 이유는 GCM의 register(), unregister() 메서드가 수행하는 중에 애플리케이션의 동작에 영향이 없도록 하기 위함이다.

 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
    private void registerInBackground() {
        new AsyncTask<Void, Void, String>() {
            @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;

                    // 서버에 발급받은 등록 아이디를 전송한다.
                    // 등록 아이디는 서버에서 앱에 푸쉬 메시지를 전송할 때 사용된다.
                    sendRegistrationIdToBackend();

                    // 등록 아이디를 저장해 등록 아이디를 매번 받지 않도록 한다.
                    storeRegistrationId(context, regid);
                } catch (IOException ex) {
                    msg = "Error :" + ex.getMessage();
                    // If there is an error, don't just keep trying to register.
                    // Require the user to click a button again, or perform
                    // exponential back-off.
                }
                return msg;
            }

            @Override
            protected void onPostExecute(String msg) {
                mDisplay.append(msg + "\n");
            }

        }.execute(null, null, null);
    }

    private void storeRegistrationId(Context context, String regid) {
        final SharedPreferences prefs = getGCMPreferences(context);
        int appVersion = getAppVersion(context);
        Log.i(TAG, "Saving regId on app version " + appVersion);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(PROPERTY_REG_ID, regid);
        editor.putInt(PROPERTY_APP_VERSION, appVersion);
        editor.commit();
    }

    private void sendRegistrationIdToBackend() {

    }
#10: gcm.register 메서드가 호출되면 등록이 완료된다.
#36: SharedPreferences에 발급받은 등록 아이디를 저장해 등록 아이디를 여러 번 받지 않도록 하는 데 사용한다.
#46: 등록 아이디를 서드 파티 서버(앱이랑 통신하는 서버)에 전달한다. 서드 파티 서버는 이 등록 아이디를 사용자마다 따로 저장해두었다가 특정 사용자에게 푸쉬 메시지를 전송할 때 사용한다.

이제 MainActivity에서 할 일은 끝났다.
완료된 소스 코드는 다음 링크에서 확인할 수 있다.
MainActivity: https://github.com/icelancer-blog/android_gcm_client/blob/master/app/src/main/java/com/icelancer/androidgcmclient/MainActivity.java
activity_main.xml: https://github.com/icelancer-blog/android_gcm_client/blob/master/app/src/main/res/layout/activity_main.xml

이제 다음으로 할 일은 메시지를 받기 위해 BroadcastReceiver와 IntentService를 구현하는 일이다.

6. 메시지 받기

GCM 서버로부터 메시지를 받아 사용자에게 보여주려면 BroadcastReceiver와 IntentService를 활용해야 한다.
BroadcastReceiver는 안드로이드 시스템에서 발생하는 수많은 액션(배터리 용량 부족, SMS 수신 등등)이 발생하면 이를 수신해 처리하는 역할을 한다. 여기에서는 안드로이드 기기가 GCM 메시지를 수신하면 이를 수신하는 데 사용한다.

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // Explicitly specify that GcmIntentService will handle the intent.
        ComponentName comp = new ComponentName(context.getPackageName(),
                GcmIntentService.class.getName());
        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}
startWakefulService 메서드를 호출해 GcmIntentService를 호출한다. GcmBroadcastReceiver는GCM 메시지를 수신하면 단순히 GcmIntentService를 실행하는 역할만 수행한다고 보면 된다.
다음은 IntentService를 구현한다. IntentService에서는 GCM 서버에서 받은 메시지를 실제 화면에 보여준다.


 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
public class GcmIntentService extends IntentService {
    public static final String TAG = "icelancer";
    public static final int NOTIFICATION_ID = 1;
    private NotificationManager mNotificationManager;
    NotificationCompat.Builder builder;

    public GcmIntentService() {
//        Used to name the worker thread, important only for debugging.
        super("GcmIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        // The getMessageType() intent parameter must be the intent you received
        // in your BroadcastReceiver.
        String messageType = gcm.getMessageType(intent);

        if (!extras.isEmpty()) {
           if (GoogleCloudMessaging.
                    MESSAGE_TYPE_MESSAGE.equals(messageType)) {
               // This loop represents the service doing some work.
               for (int i=0; i<5; i++) {
                   Log.i(TAG, "Working... " + (i + 1)
                           + "/5 @ " + SystemClock.elapsedRealtime());
                   try {
                       Thread.sleep(5000);
                   } catch (InterruptedException e) {
                   }
               }
               Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
               // Post notification of received message.
               sendNotification("Received: " + extras.toString());
               Log.i(TAG, "Received: " + extras.toString());
            }
        }
        // Release the wake lock provided by the WakefulBroadcastReceiver.
        GcmBroadcastReceiver.completeWakefulIntent(intent);
    }

    // Put the message into a notification and post it.
    // This is just one simple example of what you might choose to do with
    // a GCM message.
    private void sendNotification(String msg) {
        mNotificationManager = (NotificationManager)
                this.getSystemService(Context.NOTIFICATION_SERVICE);

        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, MainActivity.class), 0);

        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this)
                        .setSmallIcon(R.drawable.gcm)
                        .setContentTitle("GCM Notification")
                        .setStyle(new NotificationCompat.BigTextStyle()
                                .bigText(msg))
                        .setContentText(msg);

        mBuilder.setContentIntent(contentIntent);
        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
    }
}
24~31번째 줄은 이곳에서 필요한 작업을 하면 된다고 넣은 부분이고 실제로 사용자에게 알림을 주는 부분은 34번째 줄이다. 54번째 줄의 아이콘은 알림시 보여줄 아이콘을 넣으면 된다.
클라이언트 로직이 마무리 되었다. 이제 각자 구현하려는 기능에 맞게 sendNotification 메서드를 수정하면 된다.

마지막으로 남은 작업은 메니페스트 파일에 BroadcastReceiver와 IntentService를 추가해주기만 하면 된다.
<application ...>
        <receiver
            android:name=".GcmBroadcastReceiver"
            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=".GcmIntentService" />
    </application>

이제 테스트를 해보자.
"Google Cloud Messaging (GCM) 구현하기 - 2/4"에서 API를 발급받았는데, 테스트하려면 API 키가 필요하다. 그리고 앱을 실행하면 등록 아이디가 화면에 표시되는데 그 등록 아이디도 필요하다.



터미널에서 다음의 명령어를 실행하면 된다. API키에는 발급받은 API 키를 입력하고 등록 아이디는 클라이언트 앱이 GCM 서버로부터 받은 등록 아이디를 입력한다.

curl --header "Authorization: key=API키” --header Content-Type:"application/json" https://android.googleapis.com/gcm/send  -d "{\"registration_ids\”:[\”등록 아이디\”]}”


잠시 뒤 앱에서 확인해보면 푸쉬 알림이 온 것을 확인할 수 있다.

전체 소스는 github에서 확인할 수 있다.
https://github.com/icelancer-blog/android_gcm_client

Google Cloud Messaging (GCM) 구현하기 - 2/4

GCM Connection Server

"Google Cloud Messaging (GCM) 구현하기 - 1/4"에서 설명했듯이 GCM의 구성요소는 서드 파티 서버와 GCM Connection Server, 클라이언트 앱으로 이루어져 있다.

이번 포스팅에서는 그 중 GCM Connection Server 에 대해 살펴본다.

순서
1. 구글 API  프로젝트 생성하기
2. GCM 서비스 활성화하기
3. API 키 발급 받기

구글 API 프로젝트 생성하기

1. 구글 API Console 접속
https://code.google.com/apis/console

2. "프로젝트 만들기" 클릭


3. "프로젝트 이름" & "프로젝트 ID" 입력
프로젝트 ID는 기본값을 사용해도 되고, 원하는 이름으로 입력해도 된다.
여기서는 기본값을 사용했다.

4. "만들기" 클릭
프로젝트 생성중
"만들기" 버튼을 클릭하면 오른쪽 아래에 다이얼로그가 그림처럼 뜬다. 프로젝트 생성이 완료되면 다음 그림처럼 바뀌고 생성한 프로젝트 대시보드로 이동한다.
프로젝트 생성 완료
대시 보드 상단에 보면 프로젝트 ID와 프로젝트 번호가 있다.
프로젝트 번호는 나중에 GCM sender ID로 사용된다.

이제 프로젝트 생성이 완료됐다.

GCM 서비스 활성화하기

1. 왼쪽 사이드 바에서 "API 및 인증" -> "API"를 선택한다.
2. 오른쪽 API 목록에서 "Google Cloud Messaging for Android"의 사용 버튼을 클릭한다.
맨 위 "사용 설정된 API"에 다음처럼 추가된 것을 확인할 수 있다.

API 키 발급 받기

1. 왼쪽 사이드 바에서 "API 및 인증" -> "사용자 인증 정보"를 선택한다.

2. "공개 API 액세스" 의 "새 키 만들기" 버튼을 클릭한다.
3. 새 키 만들기 다이얼로그에서 "서버 키"를 클릭한다.

4. 만들기 버튼을 클릭한다.

"이러한 서버 IP 주소의 요청 허용"에 서버 아이피를 입력할 수도 있지만, 반드시 입력해야하는 것은 아니다.
여기에 서버 아이피를 입력하면 해당 아이피의 서버에서만 API 요청을 할 수 있다.
따로 입력하지 않으면 모든 아이피에서 API 요청이 가능하다.

만들기 버튼을 클릭하면 API 키가 발급된다.


Google Cloud Messaging (GCM) 구현하기 - 1/4

GCM 살펴보기

개요

GCM은 구글에서 무료로 제공하는 서비스로 안드로이드에 푸쉬 메시지를 보낼 때 사용한다.
이 서비스를 이용해 새로운 메일이 왔다는 알림을 제공할 수도 있고, 앱에 전달할 데이터가 4kb를 넘지 않는다면 카카오톡처럼 메시지를 주고 받는데 사용할 수도 있다.
이번 포스팅에서는 전체적인 구조를 설명한다.

GCM 아키텍처


GCM 구현은 서드 파티 앱 서버(3rd-Party App Server)와 GCM Connection Server, 클라이언트 앱으로 구성된다.
푸쉬 메시지를 보낼 때 서드 파티 앱 서버가 GCM Connection Server에 요청하면, GCM Connection Server는 그 요청을 받아 클라이언트 앱에 푸쉬 메시지를 전달한다.
또한 클라이언트 앱에서 서드 파티 앱 서버로 메시지를 전송할 수도 있는데, 이 때는 역으로 클라이언트 앱이 GCM Connection Server에 요청하면, GCM Connection Server가 서드 파티 앱 서버에 메시지를 전달한다.
서드 파티 앱 서버 -> GCM Connection Server -> Client App 방향으로 메시지를 보내는 것을 다운스트림 메시지(downstream message)라고 하고, 그 반대를 업스트림 메시지(upstream)라고 부른다.
우리가 구현해야하는 부분은 서드 파티 앱 서버와 클라이언트 앱이다.

전체 흐름


이 그림은 다운스트림 메시지의 흐름을 보여준다. 
1. 클라이언트 앱 등록
    - 클라이언트 앱은 GCM Connection Server에 메시지를 받고 싶다고 요청을 한다.
2. 등록 아이디 발급 (registration ID)
    - CGM Connection Server는 등록 아이디를 발급하는데 등록 아이디는 디바이스마다 다르다.
3. 등록 아이디 전달
    - 클라이언트 앱은 서드 파티 서버에 발급받은 등록 아이디를 전달해야하는데, 이는 서버에서
      GCM Connection Server에 푸쉬 메시지를 요청할 때 누구에게 보낼 것인지를 지정하는데 
      사용된다. 클라이언트 앱은 GCM Connection Server에 등록할 때만 서드 파티 서버에 등록
      아이디를 보내면 되고 서드 파티 서버는 푸쉬 메시지를 보낼 때마다 사용할 수 있도록 이를
      저장해 두어야 한다.
4. 푸쉬 메시지 이벤트 발생
    - 푸쉬 메시지를 보내야할 일이 생기면, 서드 파티 서버에 푸쉬 메시지를 보내라고 요청을 한다.
       ex. "관리자가 사용자에게 메시지 전송"
5. GCM Connection Server에 요청
     - 서드 파티 앱 서버는 메시지를 보내고 싶은 사용자의 등록 아이디 + 데이터를 
       GCM Connection Server에 전달한다.
6. GCM Connection Server가 앱에 푸쉬 메시지를 보낸다.

등록 상태 동기화

클라이언트 앱이 서드 파티 서버에 등록 아이디를 전달하면 서버는 이를 저장두어야 한다고 설명 했다. 하지만 클라이언트 앱이 한 번 등록했다고 해서 등록 상태가 계속 지속되는 것은 아니다. 이제부터는 등록 아이디를 갱신하고 해제해야하는 케이스를 다룬다.

등록 아이디를 갱신해야하는 케이스는 다음 2가지 이다.
- 클라이언트 앱 업데이트
- 백업 & 복구

클라이언트 앱 업데이트
클라이언트 앱의 버전이 업데이트 되면 새로운 버전에서도 제대로 동작한다고 보장할 수 없다. 따라서 앱 버전이 업데이트되면 다시 CGM Connection Server에서 새로 등록 아이디를 발급받아야 한다. (이는 "Google Cloud Messaging (GCM) 구현하기 - 3/4"에서 설명한다.)

백업 & 복구
앱이 백업될 때 등록 아이디는 저장하면 안된다. 앱이 복구될 때에는 등록 아이디가 유효하지 않을 수도 있기 때문이다. 그렇게 되면 영영 푸쉬 메시지를 받을 수 없게 된다.
즉, 저장해두지 않고 앱이 복구 될 때 다시 GCM Connection Server에 등록 절차를 밟게 해야한다.

그 다음으로 다룰 내용은 등록 해제이다.
등록 해제는 코드 상에서 명시적으로 수행할 수도 있고, 아니면 사용자가 앱을 삭제하면 자동으로 등록 해제 된다.

하지만, 명시적으로 등록을 해제할 일은 거의 없다고 보면 된다.
- 등록을 해제하고 다시 등록하면, 100%는 아니지만 대게 같은 등록 아이디를 발급받게 된다.
- 등록 해제하는데 5분 정도 걸린다.
- 등록이 해제 된 후에, 다시 등록하려면 5분 정도 걸린다.

따라서 등록을 해제하기 보다는 사용자에게 정확하게 메시지를 보내도록 해야한다.

사용자가 앱을 삭제하면 등록이 해제되는데 다음은 그 과정을 설명한다.
1. 사용자가 앱을 삭제한다.
2. 서드 파티 서버가 GCM 서버에 메시지를 전송한다.
3. GCM 서버는 해당 디바이스의 GCM 클라이언트에게 메시지를 전송한다.
4. GCM 클라이언트는 메시지를 받고 앱이 삭제되었음을 확인한다.
5. GCM 클라이언트는 서버에 앱이 삭제되었다고 알린다.
6. GCM 서버는 해당 등록 아이디가 삭제되었다고 표시한다.
7. 서드 파티 서버가 GCM 서버에 메시지를 전송한다.
8. GCM 서버는 NotRegistered 에러 메시지를 서드 파티 서버에 반환한다.
9. 서드 파티 서버는 등록 아이디를 삭제한다.

GCM 서버에서 등록 아이디가 삭제되는데 5분정도 걸릴 수 있어 7번 과정에서 메시지가 정상적으로 전달되었다고 반환될 수도 있다.

다음 포스팅에서는 Google API Console에서 GCM 서비스를 활성화하고 API 키를 발급받는 방법에 대해 다룬다.

참고 자료