Tag Archives: GCM

GCM Topic을 이용해서 한번의 발송으로 단체 푸시를 발송하기

google_developers_logo

이 문서는 Google Cloud Messaging (GCM) 이라는 서비스를 사용하여 Android와 iOS에 푸시 노티피케이션을 손쉽게 대량 발송하는 방법에 대해 정리한 글입니다. 만약에 이 GCM에 대해 이해가 부족하시다면 다음의 글을 먼저 읽어보시길 권장 해 드립니다.

많은 중소 개발사들이 푸시 시스템을 구축하면서 고민하게 되는 부분이 대용량 푸시 노티피케이션 발송일 것입니다. 수많은 유료 서비스들도 존재하고 단체 푸시를 발송했을 때 도달율도 시원찮고 뭐가 문제인지도 모르겠고 자체 구축을 하자니 비용과 운영에 걱정이 앞서게 됩니다. 보내야 하는 유저가 백만명이라면? 천만명이라면? 자체 푸시 시스템을 구축해서 운영하기에는 벅찬 부분이 있을것입니다.

사실 구글이 제공하는 GCM만으로도 누구나 손쉽게 단체 푸시를 발송 할 수 있습니다. 대용량의 발송 시스템을 갖출 필요도 없고 심지어 서버 한대도 필요없습니다. GCM이 제공하는 메시지의 종류로는 크게 4가지가 있습니다.

gcm_downstream_messaging

첫번째로 다운스트림 메시지(Downstream Message)가 있습니다. 이것이 우리가 일반적으로 말하는 푸시 메시지입니다. 서버가 GCM 커넥션 서버에 메시지를 전달하면 GCM 커넥션 서버는 적당한 시점에 혹은 그 즉시 디바이스에 전달을 하게 됩니다.

gcm_upstream_messaging

두번째로 업스트림 메시지(Upstream Message)가 있습니다. 이것은 반대의 개념입니다. 클라이언트 앱에서 서버로 메시지를 발송하게 됩니다. 이때에 서버는 기존에 흔하게 사용하는 방식인 HTTP를 이용하지 않고 XMPP라는 프로토콜을 이용하여 구축하여야 합니다. 이 방식은 XMPP를 구현한 인스턴스가 지속적으로 GCM서버에 연결되어 메시지를 수신하는 형태로 동작합니다.

세번째로 디바이스 그룹 메시지(Device Group Message)가 있습니다. 각각의 디바이스들은 특정 그룹에 가입하고 서버는 해당 그룹의 키로 메시지를 발송하면 해당 그룹에 가입되어있는 모든 디바이스들이 그 메시지를 수신받게 됩니다. 이것만 해도 정말 많은것을 구현할 수 있을것 같습니다.

gcm_device_group_messaging

마지막으로 토픽 메시지(Topic Message)가 있습니다. 이 글에서 말하고 싶은 내용입니다. 일명 Publish – Subscribe 패턴을 가지는 방식으로 디바이스가 특정 주제를 구독하며 해당 주제에 푸시를 발송하면 구독하는 모든 디바이스가 메시지를 수신하게 됩니다.

이미 느낌이 오셨겠지만 모든 디바이스가 같은 하나의 토픽을 구독하게 하고(예: 공지사항) 해당 토픽에 푸시를 발송하면 모든 디바이스에 해당 푸시 메시지가 도착하게 됩니다. 단지 전체 공지를 하기 위해서 대용량 발송 시스템을 구축하는것보다는 이쪽이 더 좋은 방법일 것입니다. 심지어 Android뿐만 아니라 iOS푸시까지 GCM이 지원을 해줍니다. 정말 말도 안되는것 같지만 어떻게 하는것인지 한번 알아보겠습니다.

Android 디바이스 대상 푸시 메시지 발송을 위한 구글 서비스 가입

사실 구글의 개발자 콘솔 웹사이트는 복잡하고 어렵습니다. 그걸 의식했는지 굉장히 이쁜 서비스를 만들었네요. GCM 푸시 서비스를 이용하기 위해서 우선 기존에 이미 발급한 키가 있으시다면 그것을 사용하시면 됩니다만 여기서는 이 이쁜 사이트를 이용하여 새로운 키를 발급 받아 보도록 하겠습니다. [이곳]을 방문하도록 합니다.

gcm_topic_usage_01

위의 스크린샷과 같은 화면이 등장하게 됩니다. 여기서 “Pick a platform” 버튼을 눌러 원하는 플랫폼을 선택하는 화면으로 이동해 보겠습니다.

gcm_topic_usage_02

우선 여기서는 Android 앱부터 구현을 해볼 예정입니다. 그러므로 “Android App”을 선택해 주도록 합시다.

gcm_topic_usage_03

그럼 기존에 이미 만들어져 있는 프로젝트를 선택할 수도 있고 새로 만들 수도 있는 창이 등장합니다. 일단 저는 새로운 프로젝트를 생성해 보도록 하겠습니다. 새로 만들 안드로이드 프로젝트의 이름은 “My GCM Example”이로 패키지네임은 “kr.pe.theeye.gcm.example.android”로 하겠습니다. 밑에 구글의 개발자 서비스를 이용하는 데이터를 구글에게 제공할 것인가 체크하는 부분이 있는데 생각하시는데로 하시면 될 것 같습니다. 그 밑으로 아래의 버튼을 누르면 약관에 동의가 된다고 하는데요 상관없이 “Choose and configure services” 버튼을 눌러 계속 진행하도록 하겠습니다.

gcm_topic_usage_04

활성화 가능한 서비스들의 목록이 나옵니다. 실제로 제공되는 서비스가 훨씬 많지만 이 이쁜 사이트에서는 이정도만 설정 가능한 모양입니다. GCM을 사용하기 위해서는 “Cloud Messaging”을 선택해 주시면 됩니다.

gcm_topic_usage_05

이렇게 화면이 확장되는데 “ENABLE GOOGLE CLOUD MESSAGING”을 선택해 주도록 합니다.

gcm_topic_usage_06

이런식으로 새로운 “Server API Key”와 “Sender ID”가 발급된 것을 확인할 수 있습니다. 이 부분 밑으로 다음과 같은 버튼이 등장합니다.

gcm_topic_usage_07

기존의 GCM을 구현할 때와 달리 이제는 Play Services 들에 대해 단체로 설정 파일을 내려주는 방식으로 변경되었습니다. “Generate configuration files”를 선택하여 계속 진행하도록 합시다.

gcm_topic_usage_08

위와 같이 다운로드 버튼이 보여지게 됩니다. 해당 버튼을 누르게 되면 google-services.json 파일을 다운받게 됩니다. 설명에 보면 이 파일을 app/ 또는 mobile/ 디렉토리 이하로 복사해서 사용하라고 하는군요.

메시지 수신을 위한 Android 앱 구현하기

gcm_topic_usage_09

이번엔 안드로이드 스튜디오로 넘어와보겠습니다. 프로젝트 하이라키에서 app을 선택하고 마우스 오른쪽 클릭을 해보면 “Reveal in Finder”라는 메뉴가 나옵니다. 저것을 눌러서 어찌되었던 app 디렉토리로 이동합니다.

gcm_topic_usage_10

아까 다운로드 받은 파일을 이곳으로 복사해 줍니다. 프로젝트의 app 디렉토리 안쪽인것을 유의 해 주세요. 복사를 정상적으로 하였다면 다시 안드로이드 스튜디오로 돌아옵니다. 이제부터 필요한 구글의 라이브러리들을 추가해 주도록 하겠습니다.

gcm_topic_usage_11

먼저 build.gradle 파일을 수정해야 하는데 위와 같이 두개의 build.gradle 파일이 존재하는것을 볼 수 있습니다. 자세히 보시면 하나는 Project 용이고 하나는 Module용입니다. 프로젝트용 파일을 먼저 수정하겠습니다.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
        classpath 'com.google.gms:google-services:1.5.0-beta2'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

위의 내용을 보시면 루트에 “classpath ‘com.google.gms:google-services:1.5.0-beta2′” 가 추가되어있는것을 확인하실 수 있습니다. 현재 1.5.0-beta2가 최신인데 이 글을 읽는 시점에 맞는 적절한 버전을 선택하시면 됩니다.

이번에는 Module용 build.gradle 파일을 설정 해 보겠습니다.

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "kr.pe.theeye.gcm.example.android"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile "com.google.android.gms:play-services:8.3.0"
    compile 'com.android.support:appcompat-v7:23.2.0'
}

여기서는 다음의 추가 작업을 해주었습니다.

  • 루트에 apply plugin: ‘com.android.application’ 추가
  • dependencies 에 compile “com.google.android.gms:play-services:8.3.0” 추가
  • dependencies 에 compile “com.android.support:appcompat-v7:23.2.0” 추가

이 부분 역시 시대에 맞는 적절한 버전을 선택 해 주시면 됩니다. 이번에는 AndroidManifest.xml 파일의 내용을 한번 보시겠습니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest package="kr.pe.theeye.gcm.example.android"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <permission android:name="kr.pe.theeye.gcm.example.android.permission.C2D_MESSAGE"
                android:protectionLevel="signature" />
    <uses-permission android:name="kr.pe.theeye.gcm.example.android.permission.C2D_MESSAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <receiver
            android:name="com.google.android.gms.gcm.GcmReceiver"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="kr.pe.theeye.gcm.example.android" />
            </intent-filter>
        </receiver>
        <service
            android:name=".MyGcmListenerService"
            android:exported="false" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            </intent-filter>
        </service>
        <service
            android:name=".MyInstanceIDListenerService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.android.gms.iid.InstanceID" />
            </intent-filter>
        </service>
        <service
            android:name=".RegistrationIntentService"
            android:exported="false">
        </service>
    </application>

</manifest>

이 설정을 보시고 알아두셔야 하는 부분은 다음과 같습니다.

  • 이 프로젝트의 패키지 네임은 kr.pe.theeye.gcm.example.android 입니다.
  • <패키지 네임> + permission.C2D_MESSAGE 퍼미션은 다른 앱이 디바이스를 등록하거나 메시지를 수신하는것을 막아줍니다. 이 퍼미션 이름은 정확하게 위와 같은 형태여야 합니다. 그렇지 않을 경우 메시지를 수신받을 수 없게 됩니다.
  • GCM이 발송한 메시지를 처리하기 위해서 GcmReceiver를 선언하게 됩니다. 이 서비스가 GCM 으로부터 메시지를 수신받기 위한 퍼미션이 필요하므로 com.google.android.c2dm.permission.SEND 퍼미션을 이 리시버에게 추가해 주어야 합니다. (라고 레퍼런스에 써있긴한데 왜 수신을 하기 위해서 송신 퍼미션을 추가하는지는 저도 이해가 잘 안됩니다)
  • GcmListenerService를 상속하여 구현한 MyGcmListenerService는 다양한 형태의 다운스트림 메시지를 처리한다거나 업스트림 메시지의 발송 상태를 지정하거나 앱을 대신에서 자동으로 보여지게 될 노티피케이션을 직접 받아서 처리할 수 있게 해줍니다.
  • InstanceIDListenerService를 상속하여 구현한 MyInstanceIDListenerService는 Registration Token의 신규 발급, 순환, 업데이트를 처리해 줍니다.
  • 부가적으로 android.permission.WAKE_LOCK 퍼미션을 추가함으로 써 디바이스가 슬립 상태에서도 메시지를 받았을 때 깨우는등의 작업을 할 수 있게 됩니다.
  • 만약 이 GCM 기능이 어플리케이션에 중요한 요소라면 반드시 android:minSdkVersion=”8″ 또는 그 이상의 값을 설정하여 주십시오. 이 값이 설정되지 않을 경우 GCM이 정상적으로 동작할 것이라는 보장을 할 수 없게 됩니다.

먼저 메시지를 수신할 때 노티피케이션을 등록하는 코드를 직접 구현한 MyGcmListenerService를 보겠습니다. 노티피케이션은 iOS의 APNS 규격에 Android가 맞춰가는 방향으로 진행하기 위해서 title, body를 읽어서 쓰는것으로 진행하겠습니다.

public class MyGcmListenerService extends GcmListenerService {

    private static final String TAG = "MyGcmListenerService";

    @Override
    public void onMessageReceived(String from, Bundle data) {
        Log.i(TAG, "GCM Message Received From: " + from);
        Log.i(TAG, "GCM Message Bundle: " + data.toString());

        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_setting_light)
                .setContentTitle(data.getString("gcm.notification.title"))
                .setContentText(data.getString("gcm.notification.body"))
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(0, notificationBuilder.build());
    }
}

다음은 Registration 발급, 업데이트를 담당하는 MyInstanceIDListenerService를 보겠습니다. 토큰이 갱신될 때 마다 onTokenRefresh가 호출되어 토큰의 교체를 담당하게 됩니다.

public class MyInstanceIDListenerService extends InstanceIDListenerService {

    @Override
    public void onTokenRefresh() {
        startService(new Intent(this, RegistrationIntentService.class));
    }
}

위의 코드를 보면 단지 RegistrationIntentService 서비스를 실행하는것으로 끝나는것을 보실 수 있습니다. 이 RegistrationIntentService 에서 토큰의 발급을 진행하게됩니다.

public class RegistrationIntentService extends IntentService {

    private static final String TAG = "RegistrationIntentService";
    private static final String GCM_DEFAULT_SENDER_ID = "813017546046";
    private static final String GCM_TOPIC_FOR_NOTICE = "notice-for-all";

    public RegistrationIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        try {
            InstanceID instanceID = InstanceID.getInstance(this);
            String token = instanceID.getToken(GCM_DEFAULT_SENDER_ID, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

            Log.i(TAG, "GCM Registration Token: " + token);

            GcmPubSub pubSub = GcmPubSub.getInstance(this);
            pubSub.subscribe(token, "/topics/" + GCM_TOPIC_FOR_NOTICE, null);

        } catch (IOException e) {
            Log.e(TAG, e.getLocalizedMessage());
        }
    }
}

이 RegistrationIntentService를 보시면 단지 InstanceID.getToken(…) 메소드를 호출함으로써 Registration Token을 발급하는것을 보실 수 있습니다. 또한 여기서 굉장히 중요한 부분으로 GcmPubSub을 통해서 토큰을 발급받자 마자 “/topics/notice-for-all” 이라는 이름의 토픽을 등록하는 것을 확인하실 수 있습니다.

RegistrationIntentService는 MyInstanceIDListenerService를 통해서 토큰이 갱신될때마다 자동으로 실행되어 새로운 토큰을 다운받게 됩니다. 여기서 발급한 토큰을 잘 보관하거나 개발사의 서버로 전송하는 기능을 구현해야 할 것입니다. 하지만 토픽 메시지의 경우 구독을 하는것만으로 푸시 메시지를 수신받을 수 있으므로 과감하게 토큰정보를 저장하지 않겠습니다.

하지만 MyInstanceIDListenerService는 이미 GCM의 토큰을 한번 발급받은 상태에서 돌아가는 경우에 동작하게 될 서비스입니다. 최초에 한번은 GCM의 토큰을 수작업으로 발급 받아야 합니다. 가장 기본이 될 MainActivity를 한번 보겠습니다.

public class MainActivity extends AppCompatActivity {

    private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (checkPlayServices()) {
            Log.d(TAG, "Start RegistrationIntentService");
            Intent intent = new Intent(this, RegistrationIntentService.class);
            startService(intent);
        }
    }

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

checkPlayServices 메소드는 이 디바이스가 Google Play Services 가 동작하는 환경인지를 확인하는 역할을 합니다. 가령 중국의 폰들은 Google Play가 설치되어있지 않은데 여기서 오류가 날 것입니다. 문제가 없다면 RegistrationIntentService를 정상적으로 호출하는것을 보실 수 있습니다.

이곳에서 최초 한번 RegistrationIntentService를 호출해 주면 MyInstanceIDListenerService 를 통해서 토큰이 관리되게 됩니다. 그리고 메시지의 수신은 MyGcmListenerService에서 처리할 수 있습니다. 한번 토픽 메시지 발송을 해보겠습니다.

POST https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

{
  "to": "/topics/notice-for-all",
  "notification" : {
      "body" : "This is a GCM Topic Message!",
      "title" : "GCM Title"
    }
}

RegistrationIntentService에서 토큰을 발급 받자 마자 /topics/notice-for-all 이라는 토픽을 구독하도록 하였는데요. 이 토픽으로 메시지를 발송해 보면 잘 도착하는것을 확인하실 수 있습니다.

iOS 디바이스 대상 푸시 메시지 발송을 위한 구글 서비스 추가

이번에는 Android, iOS 구분 없이 전체 디바이스에 푸시 발송을 할 수 있는지 알아보겠습니다. 언제부터인지 모르겠지만 GCM은 애플의 APNS를 통해서 푸시 메시지를 발송하는것도 가능하게 되었습니다. 처음에는 구글이 오버한다고 생각했었습니다만 생각해 보니깐 개발자 입장에서는 GCM 대응만 하면 애플의 디바이스에게도 메시지를 보낼 수 있기 때문에 굉장히 좋은 기능이라 생각되었습니다.

이경우 Android와 달리 애플의 APNS 발송용 인증서를 발급 받아야 합니다. 여기서는 푸시 노티피케이션(Push Notifications)용 인증서 발급과 프로비저닝 프로필(Provisioning Profile) 발급 과정은 생략하겠습니다.

gcm_topic_usage_13

정상적으로 등록을 하였다면 App IDs 메뉴에서 위와 같이 정상적으로 인증서가 발급된것을 확인할 수 있습니다. Development용 Production용 인증서를 모두 발급하였습니다.

gcm_topic_usage_14

이렇게 발급받은 인증서를 다운받아서 더블클릭 하면 키체인에 등록이 됩니다. 이와 같이 키체인 어플리케이션에서 정상적으로 인증서가 발급되었는지 확인해 봅니다. 문제가 없으면 다음과 같이 P12 형식으로 보내기를 해야 합니다.

gcm_topic_usage_16

본인이 생각하는 적절한 비밀번호를 설정하셔서 적당한 이름의 파일로 저장하시면 됩니다. Development, Production 둘 모두 보내기를 합니다. 이제 아까 Android 테스트를 위한 구글 서비스 가입을 했던 사이트에 방문해 보겠습니다. [이곳]으로 가시면 됩니다.

gcm_topic_usage_15

이번에는 iOS 플랫폼을 선택하여 새로운 프로젝트를 생성하지 말고 이미 존재하는 “My GCM Example”을 선택하도록 하겠습니다. “iOS Bundle ID”의 경우에는 “kr.pe.theeye.gcm.example.ios”로 하였습니다.

gcm_topic_usage_17

아까 보내기로 저장했던 두개의 P12 푸시 인증서를 모두 등록합니다. 등록 과정에서 인증서 비밀번호를 물어오게 됩니다. 정상적으로 등록 되었다면 Android 때와 마찬가지로 설정 파일을 다운로드 받도록 합니다.

gcm_topic_usage_18

다운로드 화면이 Android와 조금 다른것을 볼 수 있습니다. 구글이 제공하는 SDK를 사용하기 위해서 CocoaPods를 사용하라는 언급이 있습니다. iOS 개발자 분들에게 CocoaPods은 익숙한 툴일 것입니다. 이번에 다운로드 받을 설정 파일은 JSON 형태가 아닌 GoogleService-Info.plist 파일입니다.

메시지 수신을 위한 iOS 앱 구현하기

이번에는 iOS 앱을 개발해 보도록 하겠습니다. Swift로 구현하면 좋겠지만 아직까지 Objective C로 구현된 프로젝트가 많다고 생각되어 일단 Objective C로 구현해 보도록 하겠습니다. 만들어진 프로젝트의 모습은 다음과 같습니다.

gcm_topic_usage_19

이 프로젝트에서 뷰는 중요한것이 아니므로 대충 넘어가겠습니다. 번들 아이디가 kr.pe.theeye.gcm.example.ios 로 설정 되어있는것을 확인하실 수 있습니다. 위에서 발급받은 GoogleService-Info.plist 파일을 이 프로젝트에 추가해 주시기 바랍니다.

여기서 이 프로젝트를 그대로 개발하는 것이 아니라 CocoaPods 프로젝트로 변경해야 합니다. 터미널을 열어 이 프로젝트가 설치된 디렉토리로 이동합니다. 프로젝트의 루트 디렉토리에서 다음의 명령을 사용하여 기본 Podfile을 생성합니다.

$ pod init

GCM을 iOS 프로젝트에서 사용하기 위해서 생성된 Podfile을 열어 다음을 추가해 줍니다.

$ pod 'Google/CloudMessaging'

마지막으로 필요한 라이브러리를 자동으로 설치하기 위해서 다음의 명령을 수행합니다.

$ pod install

이 명령의 수행 결과로 .xcworkspace 파일이 생성됩니다. Xcode로 기존의 프로젝트 파일이 아닌 이 파일을 열어 보도록 합니다.

$ ls
GcmTopicExample/           GcmTopicExample.xcodeproj/ Podfile
$ pod install
Updating local specs repositories
Analyzing dependencies
Downloading dependencies
Installing GGLInstanceID (1.1.6)
Installing Google (1.3.2)
Installing GoogleCloudMessaging (1.1.3)
Installing GoogleIPhoneUtilities (1.1.1)
Installing GoogleInterchangeUtilities (1.1.0)
Installing GoogleNetworkingUtilities (1.0.0)
Installing GoogleSymbolUtilities (1.0.3)
Installing GoogleUtilities (1.1.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `GcmTopicExample.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 8 total pods installed.

기존의 프로젝트가 아닌 Pods 프로젝트를 포함한 워크스페이스가 열린것을 확인하실 수 있습니다. iOS의 디바이스가 비활성화 상태일 때 GCM이 푸시 메시지를 전송하기 위해서는 iOS의 Silent Push 라는 기능을 사용합니다. 앱이 활성화 상태가 아닐때라도 백그라운드 프로세스에서 처리할 수 있게 됩니다.

Info.plist 파일에 다음과 같은 설정을 추가해 줍니다.

<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

정상적으로 추가가 되었다면 다음과 같이 추가가 된 것을 확인하실 수 있습니다.

gcm_topic_usage_20

iOS의 코드는 확인하기 용이하도록 필요한 GCM 코드들을 AppDelegate.m 파일에 모두 정리해 보았습니다.

#import "AppDelegate.h"
#import <Google/CloudMessaging.h>

@interface AppDelegate () <GGLInstanceIDDelegate, GCMReceiverDelegate>

@property(nonatomic, strong) NSDictionary *options;
@property(nonatomic, strong) GGLInstanceIDTokenHandler registrationHandler;
@property(nonatomic, strong) NSString* registrationToken;

@end

NSString * const GCM_DEFAULT_SENDER_ID = @"813017546046";
NSString * const GCM_TOPIC_FOR_NOTICE = @"/topics/notice-for-all";

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // APNS 노티피케이션 토큰 발급 요청
    UIUserNotificationType allNotificationTypes = (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
    
    // GCM Registration Token 발급시 결과 처리 핸들러
    __weak typeof(self) weakSelf = self;
    self.registrationHandler = ^(NSString *registrationToken, NSError *error) {
        if (registrationToken != nil) {
            NSLog(@"Registration Token: %@", registrationToken);
            weakSelf.registrationToken = registrationToken;
        } else {
            NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
        }
    };
    
    // GCM 시작
    GCMConfig *gcmConfig = [GCMConfig defaultConfig];
    gcmConfig.receiverDelegate = self;
    [[GCMService sharedInstance] startWithConfig:gcmConfig];
    
    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    
    // APNS에 등록된 디바이스 정보를 GCM에 재등록
    self.options = @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
                     kGGLInstanceIDAPNSServerTypeSandboxOption:@YES};
    
    GGLInstanceIDConfig *instanceIDConfig = [GGLInstanceIDConfig defaultConfig];
    instanceIDConfig.delegate = self;
    [[GGLInstanceID sharedInstance] startWithConfig:instanceIDConfig];
    [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:GCM_DEFAULT_SENDER_ID
                                                        scope:kGGLInstanceIDScopeGCM
                                                      options:self.options
                                                      handler:self.registrationHandler];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    
    // 메시지 수신시의 처리
    NSLog(@"Notification received: %@", userInfo);
    [[GCMService sharedInstance] appDidReceiveMessage:userInfo];
    
    NSDictionary *aps = [userInfo objectForKey:@"aps"];
    NSDictionary *alert = [aps objectForKey:@"alert"];
    if (alert) {
        NSString *title = [alert objectForKey:@"title"];
        NSString *body = [alert objectForKey:@"body"];
        
        [[[UIAlertView alloc] initWithTitle:title message:body delegate:self
                          cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    
    // 앱이 유휴 상태일 때 메시지를 받을 경우의 처리
    [self application:application didReceiveRemoteNotification:userInfo];
    completionHandler(UIBackgroundFetchResultNoData);
}

- (void)onTokenRefresh {
    
    // GCM Registration Token이 갱신되었을 때
    NSLog(@"The GCM registration token needs to be changed.");
    [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:GCM_DEFAULT_SENDER_ID
                                                        scope:kGGLInstanceIDScopeGCM
                                                      options:self.options
                                                      handler:self.registrationHandler];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    
    // 앱이 활성화 되었을 때 GCM에 연결
    [[GCMService sharedInstance] connectWithHandler:^(NSError *error) {
        if (error) {
            NSLog(@"Could not connect to GCM: %@", error.localizedDescription);
        } else {
            NSLog(@"Connected to GCM");
            
            // 토픽 구독 등록
            [[GCMPubSub sharedInstance] subscribeWithToken:self.registrationToken topic:GCM_TOPIC_FOR_NOTICE options:nil handler:^(NSError *error) {
                if (error) {
                    if (error.code == 3001) {
                        NSLog(@"Already subscribed to %@", GCM_TOPIC_FOR_NOTICE);
                    } else {
                        NSLog(@"Subscription failed: %@", error.localizedDescription);
                    }
                } else {
                    NSLog(@"Subscribed to %@", GCM_TOPIC_FOR_NOTICE);
                }
            }];
        }
    }];
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    
    // 앱이 비활성화 되었을 때 GCM 연결 해제
    NSLog(@"Disconnected from GCM");
    [[GCMService sharedInstance] disconnect];
}

위의 코드는 iOS 환경에서 GCM이 동작하는것을 보여주기 위한 간단한 예제일뿐이며 본인의 앱에 맞게 커스터마이징 하여 사용하시면 됩니다. 이부분에서 특히 중요한 부분은 application:didRegisterForRemoteNotificationsWithDeviceToken: 내부에 있는 옵션을 설정하는 부분입니다.

  • kGGLInstanceIDRegisterAPNSOption : APNS에 디바이스 등록이 성공하고 발급받은 deviceToken을 이곳에 넣어줍니다. 이것을 가지고 GCM에서 자신들의 Registration Token과 매핑한 뒤 발급하는 과정을 거치게 됩니다.
  • kGGLInstanceIDAPNSServerTypeSandboxOption : 구글 개발자 콘솔에 두개의 APNS 인증서를 등록했던것을 기억하실 것입니다. 어떤 인증서를 사용하여 발송할 것인지 결정하는 옵션입니다. Development 인증서를 통해 메시지를 수신받기 위해 YES로 설정하였습니다.

코드를 보면 앱이 활성화 되었을 때와 비활성화 되었을 때 GCM과 연결했다가 끊는것을 반복하는것을 볼 수 있습니다. 앱이 백그라운드에 진입하면 기존에 연결되었던 네트워크 커넥션이 OS에 의해 강제로 끊어지게 되지만 애플이 이것이 나쁜 경우라고 판단하여 심사과정에서 리젝을 하는 사례가 있다고 합니다. 구글에서는 disconnect를 구현할 것을 권장하고 있습니다.

이제 최종적으로 Android와 iOS 두개의 디바이스에 토픽 메시지를 한번만 사용하여 메시지 발송이 되는지 확인해 보겠습니다. 기존에 사용하였던 POST 요청을 재활용 해보겠습니다.

POST https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

{
  "to": "/topics/notice-for-all",
  "notification" : {
      "body" : "This is a GCM Topic Message!",
      "title" : "GCM Title"
    }
}

GCM을 통해서 Android에 발송하는것은 자유로운 편입니다. 토픽 메시지를 통해서도 노티피케이션(Notification)뿐만 아니라 데이터(Data) 역시 자유롭게 보낼 수 있고 핸들링 할 수 있다는것을 확인하였습니다. 하지만 iOS의 경우에는 APNS의 룰에 맞춘 노티피케이션 환경에서 정상적으로 동작하는것을 확인하였습니다.

앱이 GCM에 연결된 상태에서는 데이터의 전송에도 무리가 없었지만 앱이 유휴 상태일 경우 노티피케이션만이 전달되는것을 확인하였습니다. 물론 앱이 GCM에 연결되는 순간 데이터의 값들도 수신이 되었습니다만 iOS와 Android에서 둘다 동일한 동작을 하도록 보이기 위해서는 노티피케이션과 데이터를 적절히 혼합하여 사용하면 될 것 같습니다.

단순히 공지 메시지를 푸시로 전달하고자 하는것이라면 위와 같이 notification: { “body”: “…”, “title”: “…” } 정도로만 사용하셔도 전혀 문제가 안될 것 같습니다.

gcm_topic_usage_results

토픽 /topics/notice-for-all 를 구독중인 모든 디바이스에 한번의 발송으로 Android, iOS 구분없이 도착하는것을 확인할 수 있었습니다.

끝으로 아마 이렇게 한번에 보낼 수 있는 디바이스에 제한이 있다거나 토픽 구독 인원에 제한이 있을까 궁금하실텐데요 토픽 메시지가 처음에 나왔을때는 토픽당 100만명만 구독이 가능한 제한이 있었지만 지금은 무제한입니다.

참고 : https://developers.google.com/cloud-messaging/gcm

Google Cloud Messaging (GCM) 메시지 컨셉과 옵션

google_developers_logo

Google Cloud Messaging API들은 다양한 옵션과 넓은 범위의 기능을 제공합니다. 이 문서는 GCM 메시지와 일반적으로 사용되는 메시지 옵션들을 통해 GCM API들을 사용해서 무엇을 할 수 있는지에 대한 설명을 할 예정입니다. GCM의 기본적인 개념에 대해서는 [Google Cloud Messaging (GCM) 개요]를 먼저 읽으시길 권장드립니다.

메시지의 컴포넌트들

앱서버는 타겟, 메시지 옵션, 페이로드와 같은 기본 컴포넌트들로부터 다운스트림 메시지를 생성합니다. 이 컴포넌트들은 GCM HTTP 및 XMPP 커넥션 서버의 프로토콜들 사이에서 일반적으로 사용되는 컴포넌트들입니다.

타겟 (Target)

필수사항입니다. 당신의 앱 서버가 메시지를 발송할 때 메시지가 도착할 목적지 타겟의 식별자들을 특정 지어주어야 합니다. 타겟을 지정하는데에는 to 필드를 사용합니다. 이 필드에는 하나의 Registration Token 또는 Topic 또는 Notification Key (디바이스 그룹 단위에 발송하는데에 사용)를 포함할 수 있습니다.

옵션 (Options)

앱 서버는 클라이언트 앱에 다운스트림 메시지를 발송할 때 가령 이 메시지가 다음 메시지로 교체될 수 있는 여부를 설정한다던지 등, 다양한 옵션을 설정할 수 있습니다. 이러한 메시지 옵션의 전체 목록을 보기위해서는 아래에 언급될 당신이 사용중인 서버 프로토콜에 적합한 레퍼런스를 참고하시기 바랍니다.

페이로드 (Payload)

다운스트림 메시지 발송에서, GCM은 노티피케이션(Notification)과 데이터(Data) 두가지 형태의 페이로드를 제공합니다. 노티피케이션은 더 가벼운 방법으로써 2KB의 제한을 가진 유저에게 보여질 사전 정의된 키입니다.데이터는 개발자가 최대 4KB 크기의 커스터마이징 가능한 키/값 쌍을 전송할 수 있게 해줍니다. 노티피케이션 메시지는 유저가 노티피케이션을 클릭했을 때 전달되게 되는 Data 페이로드도 포함할 수 있습니다.

  • 노티피케이션 : GCM은 클라이언트 앱을 대신해서 자동으로 메시지를 유저에게 보여줍니다. 노티피케이션은 미리 정의된 사용자에게 보여질 키들로 구성되어있습니다. notification 페이로드를 설정하면 되며 추가로 data 페이로드를 설정하는것도 가능합니다. 항상 최종 메시지만이 유저에게 노출됩니다. (기존의 푸시 메시지를 덮어 씀)
  • 데이터 : 클라이언트 앱은 데이터 메시지를 처리하기 위해 응답해야 합니다. 이 데이터 메시지는 개발자가 임의 지정한 키와 값의 쌍으로 이루어져있습니다. data 페이로드만을 사용해야 합니다. 최종 메시지만 노출되게 할지 모든 메시지를 노출할지 선택할 수 있습니다.

GCM이 당신의 앱을 대신하여 노티피케이션을 유저에게 보여 주길 원할 경우 노티피케이션 페이로드를 사용하면 되며 당신의 앱이 직접 그 노티피케이션을 보여주거나 조작하길 원한다면 데이터 페이로드를 활용하면 됩니다. 만약 당신이 GCM과 다이렉트 커넥션을 맺고 있는 상태의 iOS 디바이스에 메시지를 발송하길 원할때도 데이터 페이로드를 사용할 수 있습니다.

앱 서버는 노티피케이션과 데이터 페이로드 모두를 함께 발송할 수 있습니다. 이런 경우에 GCM이 노티피케이션 페이로드의 정보를 처리하고 클라이언트 앱이 데이터 페이로드를 처리하게 됩니다. 더 자세한 정보와 예제는 이후의 내용을 참고하시기 바랍니다.

gcm-versatile-messaging-targets

메시지 페이로드의 노티피케이션과 데이터

노티피케이션은 개발자에게 몇가지 미리 정의된 키들과 커스터마이징된 키/값 쌍을 사용하여 유저에게 보여줄 수 있는 메시지를 발송할 수 있는 쉬운 방법을 제공합니다. 데이터 페이로드는 개발자가 임의 정의한 커스텀 키/값 쌍만을 사용하며 클라이언트 앱은 반드시 이것을 어떻게 처리할지 구현이 되어있어야 합니다. 당신은 노티피케이션과 데이터 페이로드 둘을 모두 동시에 사용하여 발송 할 수 있습니다.

노티피케이션

노티피케이션을 발송하기 위해서는 유저에게 보여줄 노티피케이션을 이루는 미리 정의된 키들로 이루어져 있는 notification 을 설정해야 합니다. 예를 들어 인스턴트 메세징 어플리케이션의 노티피케이션 메시지를 JSON 포맷으로 구성해보면 다음과 같을 것입니다. 유저의 디바이스에 제목이 “Portugal vs. Denmark”이고 메시지는 “great match!” 라고 출력될 것을 예상할 수 있습니다.

{
  "to": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
  "notification": {
    "body": "great match!",
    "title": "Portugal vs. Denmark",
    "icon": "myicon"
  }
}

앱이 비활성화되어있는 상태에 메시지가 도착하게 되면 디바이스의 노티피케이션 트레이로 도착을 하게 됩니다. 활성화 되어있는 iOS 앱의 경우 노티피케이션은 didReceiveRemoteNotification: 으로 전달되게 됩니다. 활성화 되어있는 Android 앱의 경우 노티피케이션은 notification 키 아래의 페이로드가 onMessageReceived() 에 전달되게 됩니다.

데이터 메시지

당신의 클라이언트 앱에 전송하고자 하는 개발자 임의의 커스텀 키/값 쌍으로 구성된 data 를 설정합니다. 데이터 메시지는 최대 4KB의 크기를 가질 수 있습니다. 다음은 위와 같이 인스턴트 메세징 앱을 예로 들어본 예시입니다. 하지만 위의 컨텐츠를 data 에 좀 더 캡슐화 하여 정의하였습니다.

{
  "to": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
  "data": {
    "Nick": "Mario",
    "body": "great match!",
    "Room": "PortugalVSDenmark"
  }
}

Android에서 클라이언트 앱은 onMessageReceived() 에서 데이터 메시지를 수신받고 키/값 쌍을 처리할 수 있습니다. iOS에서는 GCM이 메시지를 보관하고 앱이 포그라운드로 복귀하고 GCM 커넥션을 맺을 때 메시지를 전송하게 됩니다.

이러한 플랫폼 별 세부사항을 정리해 보면 다음과 같습니다.

  • Android에서는 Intent를 이용해서 당신의 엑티비티를 실행하여 data 페이로드를 수신할 수 있습니다.
  • iOS에서는 didReceiveRemoteNotification: 안에서만 data 페이로드를 받을 수 있습니다.

만약 당신이 커스텀 키/값들로만 이루어진 메시지를 백그라운드 상태인 iOS 앱에 전송하고 싶다면 data 키에 원하는 커스텀 키/값을 설정하고 content_available 를 true로 설정하시기 바랍니다.

노티피케이션과 데이터 페이로드를 모두 포함한 하이브리드 메시지에 대해

노티피케이션과 데이터 페이로드를 모두 가지고 있는 메시지를 앱이 수신했을 때 어떻게 동작하는지는 앱이 백그라운드에 있는지 포그라운드에 있는지에 따라 다릅니다. 정리하자면 수신 시점에 앱이 활성화 되어있는지 아닌지에 따라 결정됩니다.

  • 백그라운드의 경우, 앱은 노티피케이션 트레이의 노티피케이션 페이로드를 수신받습니다. 그리고 유저가 이 노티피케이션을 탭하였을 경우에만 데이터 페이로드를 조작할 수 있습니다.
  • 포그라운드의 경우, 당신의 앱은 이 두가지 페이로드를 모두 수신받을 수 있습니다.

다음은 notification과 data 페이로드 둘 모두를 가진 JSON 포맷의 예제입니다.

{
  "to": "APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx...",
  "notification": {
    "body": "great match!",
    "title": "Portugal vs. Denmark",
    "icon": "myicon"
  },
  "data": {
    "Nick": "Mario",
    "Room": "PortugalVSDenmark"
  }
}

일반적으로 사용되는 메시지 옵션

이번에는 일반적으로 사용되는 단순한 형태의 다운스트림 메시지를 발송할 때 자주 사용되는 옵션에 대해 자세히 알아보겠습니다. 이러한 옵션에는 메시지 교체(Collapsible Messaging), 다중 발송자(Multiple Senders), 메시지 우선순위(Message Priority), 메시지 수명(Lifespan for Messages) 등이 있습니다.

교체 가능한/불가능한 메시지

정확한 영문 문서의 표기는 Collapsible / Non Collapsible Messages 입니다만. 이걸 한글로 옮겨적기에 적절한 단어가 아닌것 같아 “교체 가능/불가능” 정도로 번역해 보았습니다. 참고 부탁드립니다.

교체 불가능한(Non-collapsible) 메시지

교체 불가능한 메시지는 각각의 개별 메시지가 모두 디바이스에 도착하게 된다는 의미를 가지고 있습니다. 교체 불가능한 메시지는 어떤 유용한 컨텐츠등을 모바일 앱이 서버에 접속하여 데이터를 가져오게 한다던지 하는데에 사용될 수 있습니다. 항상 교체되는 노티피케이션을 제외하고 모든 메시지는 기본적으로 교체 불가능한 메시지입니다. GCM은 발송 순서 대기 도착하는 순서를 보장하지 않습니다.

몇몇 적절한 교체 불가능한 메시지의 사용예로는 채팅 메시지나 크리티컬 메시지가 있습니다. 예로 인스턴트 메세징 앱에서 당신은 모든 메시지를 GCM을 통해 전달하고 싶을 수 있습니다. 왜냐하면 각각의 메시지는 다른 컨텐츠이기 때문입니다.

참고 : 메시지는 기본적으로 최대 100개까지 교체되지 않고 보관될 수 있습니다. 이 제한에 도달하게 될 경우 모든 저장된 메시지는 버려집니다. 디바이스가 온라인으로 돌아오면 이 제한에 도달했음을 알리는 특별한 메시지가 수신됩니다. 이경우 앱은 서버와 전체 동기화 작업을 하는등의 적절한 대응을 해야 합니다.

교체 가능한(Collapsible) 메시지

교체 가능한 메시지는 같은 Collapse 키를 가진 새로운 메시지가 도착하면 기존의 메시지가 교체되는 메시지를 의미합니다.

이러한 교체 가능한 메시지의 일반적인 두가지 사용 예로는 동기 발송 메시지와 노티피케이션이 있습니다. 동기 발송 메시지는 모바일 앱이 서버와 데이터 동기화를 하도록 “Ping”을 보내는 역할을 하는 메시지 입니다. 예로 경기의 가장 최신 점수를 유저에게 보여주는 스포츠앱이 있다고 할 때, 가장 마지막에 발송한 메시지만이 유효한 정보일것입니다.

GCM은 앱서버가 디바이스에 언제든지 발송할 수 있는 Collapse 키를 최대 4개까지 지원합니다. 다른 말로 GCM 커넥션 서버는 각 디바이스에 동기 발송을 할 수 있수 교체 가능한 메시지를 각기 다른 Collapse 키를 사용하여 동시에 최대 4개까지 저장할 수 있음을 말합니다. 만약에 GCM이 정의한 이 4개의 제한을 초과 할 경우 어떤 Collapse 키가 유지될지 보장할 수 없습니다.

iOS 환경에서 앱 서버가 동기 메시지 발송을 할 필요가 있을 때 content_available 를 설정하면 비활성화 되어있는 클라이언트 앱이 백그라운드에서 당신이 정의한 로직을 수행하게 되며 앱이 포그라운드에 돌아올 때 didReceiveRemoteNotification: 에 메시지를 전달하게 됩니다.

무엇을 할 수 있나?

당신의 앱이 교체불가능한 메시지를 제공해야 할 필요가 없다면 퍼포먼스 관점에서 교체가능한 메시지를 사용하는것이 더 나은 선택입니다. 하지만 교체가능한 메시지를 사용할 경우 GCM은 Registration Token별로 GCM 커넥션 서버에 의해 최대 4개의 Collapse 키만이 허용된다는 점을 기억하시기 바랍니다. 절대로 이 제한을 초과해서는 안되며 이 경우 예측 불가능한 상황이 발생할 수 있습니다.

  • 교체 불가능한 메시지 : 클라이언트 앱 입장에서 모든 메시지가 중요할 경우에 사용하십시오. 노티피케이션 메시지를 제외하고 모든 메시지는 기본적으로 교체 불가능한 메시지입니다.
  • 교체 가능한 메시지 : 클라이언트 앱 입장에서 오래된 필요없어진 메시지가 더 새로운 메시지에 의해 교체될 필요가 있을때 사용합니다. GCM은 기존의 메시지를 교체합니다. 예로 동기 메시지 발송 목적이나 기한이 지난 메시지를 교체할 목적으로 사용합니다. collapse_key 파라미터를 설정하여 적용합니다.

메시지의 우선순위 설정하기

당신은 다운스트림 메시지에 대해 “일반”과 “높은” 우선순위 2가지의 메시지 전달 우선순위 옵션을 선택할 수 있습니다. 이 두가지 옵션은 다음과 같이 동작합니다.

  • 높은 우선순위 : GCM은 높은 우선순위를 가진 메시지를 전달하는 작업을 호출 즉시 시도합니다. GCM 서비스는 잠들어있는 디바이스를 가능할 때 깨우고 네트워크 연결을 복구하여 당신의 앱서버와 통신할 수 있도록 해줍니다. 예를 들어 인스턴트 메세징 앱과 같이 당신의 앱서버와 통신이 필요한 채팅, 보이스 콜 알림이 있을 경우 GCM은 디바이스에 딜레이 없이 바로 도착하도록 해 줄것입니다. 높은 우선순위 설정은 꼭 메시지가 도착 시간이 중요하고 유저가 바로 확인해야 하는 경우에만 사용하여야 하며, 이 높은 우선 순위를 가진 메시지를 일반 우선순위의 메시지에 비해 배터리 소모에 더 많은 기여를 하게 되므로 주의하여 사용할것을 권장합니다.
  • 보통 우선순위 : 이것은 메시지 전달의 기본이 되는 우선순위입니다. 보통 우선 순위 메시지는 잠들어 있는 디바이스에 대해 네트워크 커넥션을 맺지 않고 배터리 보호를 위해 메시지 전달이 늦어질 수 있습니다. 새로운 이메일의 도착이나 다른 필요한 데이터 싱크 작업과 같은 시간이 덜 중요한 메시지의 경우 보통 우선순위 메시지를 사용하시기 바랍니다.

이 파라미터의 올바른 값은 high와 normal입니다. 더 자세한 정보를 확인하려면 HTTP, XMPP 서버 레퍼런스를 확인하시기 바랍니다.

기존의 iOS앱의 경우 메시지 우선 순위를 명시적으로 설정하지 않았었습니다. GCM의 우선순위 정의가 2015년 8월 13일부로 변경되었으므로 당신의 앱의 동작에 변화가 생겼을 수 있습니다. 이런 경우 메시지 발송시에 우선순위를 “높은”으로 변경해주어야 딜레이 없이 바로 전달 할 수 있습니다.

iOS 클라이언트 앱의 경우 “일반”과 “높은” 우선순위의 경우 APNS의 우선순위 레벨 5와 10과 유사하게 정의됩니다. 자세한 내용은 iOS 의 [APNS 문서]를 참고하시기 바랍니다. Android의 경우에는 [도즈 및 스탠바이 최적화하기]를 참고하시기 바랍니다.

다음은 잡지 구독자에게 새로운 컨텐츠를 다운받을 수 있게 되었음을 알리는 “일반” 우선순위를 가진 알림 메시지를 발송하는 예시입니다.

{
  "to": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
  "priority": "normal",
  "notification": {
    "body": "This week’s edition is now available.",
    "title": "NewsMagazine.com",
    "icon": "new"
  },
  "data": {
    "volume": "3.21.15",
    "contents": "http://www.news-magazine.com/world-week/21659772"
  }
}

메시지 수명 설정하기

GCM은 보통 메시지를 발송하는 순간 즉시 전달을 합니다. 하지만 이것이 항상 가능한것은 아닙니다. 예를 들어 플랫폼이 Android라면 디바이스가 꺼져있거나 네트워크에 연결되어있지 않을때 혹은 다른 상황등에서 즉시 전달이 어려울 수 있습니다. 혹은 발송자가 delay_while_idle 플래그를 사용하여 디바이스가 활성화 될때까지 메시지를 전달하지 않도록 요청할 수 있습니다. 결과적으로 GCM은 앱이 과도한 자원낭비를 하게 되거나 배터리에 부정적인 영향을 끼칠 수 있는것을 막도록 메시지를 의도적으로 지연 발송할 수 있습니다.

이러한 일이 발생할 경우 GCM은 메시지를 보관했다가 가능할 때 메시지 전달을 재개합니다. 이러한 기능은 대부분의 경우에는 문제가 없지만 몇몇 앱에서는 이런 경우에 아예 전달되지 않는것이 좋은 경우도 있을 것입니다. 예를 들어 메시지가 전화나 영상통화가 오고 있음을 알리는 노티피케이션이라면 이것은 매우 짧은 순간동안만 유의미할것이며 전화를 받지 않아 결국 끊어진 이후에는 의미가 없을 것입니다. 또는 어떤 이벤트의 초대장을 포함한 메시지라면 이 이벤트가 끝난 뒤에는 의미가 없을것입니다.

이럴 경우 HTTP와 XMPP 요청 모두에서 time_to_live 파라미터를 사용하여 메시지의 최대 수명을 정의할 수 있습니다. 이 파라미터는 0에서 2,419,200초의 기간으로 설정되어야 하며 GCM이 메시지를 보관하고 전달하는데까지 걸리는 시간을 의미합니다. 요청이 이 필드를 설정하지 않을 경우 기본값이 4주로 설정됩니다.

다음과 같은 특별한 기능에 대해 사용할 수 있습니다. (예시)

  • 화상 통화 요청 콜
  • 이벤트 초대 만료
  • 달력 이벤트

메시지의 수명을 설정하는것은 또다른 장점으로는 time_to_live 를 0으로 설정함으로써 GCM이 발송 여부를 관리하지 않게 하는 방법이 있습니다. 다른말로 GCM은 “지금 아니면 필요없다”는 마음으로(?) 최선의 노력을 보장할 수 있습니다. 즉 time_to_live가 0으로 설정된다는 것은 지금 즉시 메시지를 전달할 수 없다면 이 메시지는 버려짐을 의미합니다. 이 메시지는 보관되지 않을것이기 때문에 노티피케이션을 발송하는데에 최고의 응답 속도를 제공할 수 있습니다.

다음은 TTL 설정이 포함되어있는 JSON 형태의 예제입니다.

 {
   "collapse_key" : "demo",
   "delay_while_idle" : true,
   "to" : "xyz",
   "data" : {
     "key1" : "value1",
     "key2" : "value2"
   },
   "time_to_live" : 3
 }

현재, time_to_live 설정은 iOS 환경의 노티피케이션 메시지를 지원하지 않습니다.

다중 발송자로부터 메시지 수신하기

GCM은 다수의 그룹에서 하나의 클라이언트 앱에 메시지를 발송하는것을 허용합니다. 예를 들어 다양한 컨텐츠 컨텐츠들의 글을 하나의 앱이 수집할 때 각각의 생산자들은 자신들의 새로운 글이 생성되었을 때 이 하나의 앱에 메시지를 발송하는 것이 가능합니다. 이 메시지에는 글의 다운로드 URL을 가질 수 있습니다. 모든 컨텐츠를 하나의 장소에 모을 필요없이 GCM은 각각의 생산자들이 자기 자신들의 메시지를 발송할 수 있게 해줍니다.

이것을 가능하게 하기 위해서는 각각의 발송자들은 자신들의 발송자 아이디(Sender ID)를 발급받아야 합니다. 클라이언트 문서를 확인하시고 자신에게 적절한 GCM 발송자 아이디를 발급받으시기 바랍니다. 등록 요청이 되면 클라이언트 앱은 각기 다른 발송자 아이디별로 여러번 토큰을 발급받게 됩니다.

결과적으로 각 앱서버에 대응되는 Registration 토큰을 공유하여 클라이언트 앱과 그들이 가지고 있는 인증 키를 이용하여 메시지 발송이 가능하게 됩니다. 이때에 최대 100개의 다중 발송자 제한이 있습니다.

메시지의 일생 (Lifetime)

앱 서버가 GCM 에 메시지를 전송하고 메시지 ID를 응답받았을 때 이것이 메시지가 이미 디바이스에 도착했다는것을 의미하는것은 아닙니다. 이것은 메시지 전달 요청을 받았음을 의미하며 이 메시지가 정상적으로 전달될 것인지는 다양한 경우의 수에 달려있게 됩니다.

최고의 시나리오는 디바이스가 GCM에 연결되어있고 스크린이 켜져있고 어떤 쓰로틀 제한도 없다면 이 메시지는 지체없이 즉시 전달될 것입니다.

만약 디바이스가 연결은 되어있지만 유휴상태라면 메시지는 delay_while_idle 플래그가 참으로 설정되어있지 않다는 조건하에 즉시 전달됩니다. 그렇지 않다면 이 메시지는 디바이스가 깨어날 때까지 GCM 커넥션 서버에 저장될 것입니다. 그리고 여기서 collapse_key 플래그에 맞추어 다른 동작을 하게 됩니다. 이미 디바이스에 같은 collapse_key를 가진 메시지가 존재할 경우 메시지의 수신을 기다린 후 기존의 메시지는 새로운 메시지에게 자리를 넘겨주고 폐기됩니다. 하지만 collapse_key가 설정되어있지 않다면 새로운 메시지는 기존의 메시지와 함께 보관되게 됩니다.

만약 디바이스가 GCM에 연결되어 있지 않다면 이 메시지는 다시 커넥션이 연결될 때까지 보관됩니다. (Collapse 키의 역할도 여전히 유지됩니다.) 커넥션이 다시 연결되면 GCM은 delay_while_idle 플래그 설정값과 상관없이 모든 지연된 메시지를 디바이스에 전송합니다. 만약 디바이스는 공장초기화와 같은 이유로 다시 연결이 되지 않는다면 이 메시지는 점진적으로 GCM 저장소에서 삭제되어나갈것입니다. 기본적인 유지 기간은 4주이며 time_to_live 플래그를 사용하여 임의의 원하는 시간을 설정할 수 있습니다.

마지막으로 GCM이 디바이스에 메시지 전송을 시도할 때 해당 앱이 삭제된 상태라면 GCM은 그 메시지를 즉시 삭제하고 Registration 토큰을 폐기합니다. 이후에 이 디바이스에 또 메시지 발송을 요청하면 NotRegistered 에러를 받게 됩니다.

참고 : https://developers.google.com/cloud-messaging/concept-options