본문 바로가기

의역과 오역/Dev

GCM 클라이언트 구현하기 (Implementing GCM Client)

(원문 : http://developer.android.com/google/gcm/client.html )


 GCM 클라이언트는 안드로이드 디바이스에서 실행되는 GCM이 사용가능한 앱을 말합니다. 우리는 클라이언트 코드를 작성할 때, GoogleCloudMessaging API를 사용하는 것을 추천합니다. 이전 버전의 GCM에서 제공되었던 클라이언트 helper 라이브러리 역시 여전히 동작하지만, 이는 더 효율적인 GoogleClodMessaging APi로 대체되었습니다.


 GCM을 완전하게 구현하기 위해서는 클라이언트와 서버 구현이 모두 필요합니다. 서버 사이드에 관한 더많은 정보는 GCM 서버 구현하기를 참조하기 바랍니다. 


 아래의 각 색션은 GCM  클라이언트 사이드 애플리케이션 구현의 세계로 여러분을 인도할 것 입니다. 당신의 클라이언트 앱은 자유분방하게 짜여졌을 수도 있고, 복잡할 수도 있습니다. 그렇지만 GCM  클라이언트 앱은 최소한 등록(과 이를 통해 등록 ID를 얻는)과 GCM에서 보내진 메시지를 받을 수 있는 broadcast receiver 코드를 포함하고 있어야 합니다. 



Step 1: Google Play Services 설정 


 클라이언트 애플리케이션을 작성할 때 GoogleCloudMessaging API를 사용하게 됩니다. 이 API를 사용하기 위해서, 당신의 프로젝트를 Google Play services SDK를 사용하기 위해 설정을 해야합니다. 자세한 사항은 Setup Google Play Services SDK를 참조하기 바랍니다.


 주의 : Play Services 라이브러리를 프로젝트에 추가할 때 resources를 통해 이를 더한 것을 확인하길 바랍니다. 자세한 것은 Setup Google Play Services SDK에 설명되어 있습니다. 요점은 반드시 라이브러리를 참조해야한다는 것 입니다. 단순하게 .jar 파일을 당신의 이클립스 프로젝트에 추가하는 것만으로는 제대로 동작하지 않을 것 입니다. 라이브러리 참조를 위해서는 안내에 따라야합니다. 그렇지 않으면 라이브러리의 resource에 접근을 할 수 없을 것이며 제대로 실행되지 않을 것 입니다. 만약 Android Studio를 사용하고 있다면, 아래 텍스트를 애플리케이션의 bundle.gradle 파일의 dependency 섹션에 추가할 수 있습니다 :


dependencies {
  compile
"com.google.android.gms:play-services:3.1.+"
}



Step 2: 애플리케이션의 Manifest 수정하기


다음을 애플리케이션의 manifest에 추가해야 합니다. 

- com.google.android.c2dm.permission.RECEIVER 퍼미션은 안드로이드 애플리케이션을 등록하고 메시지를 받을 수 있도록 합니다

- android.permission.INTERNET 퍼미션은 안드로이드 애플리케이션이 서드 파티 서버에 registration ID를 보낼 수 있게 합니다.

- android.permission.GET_ACCOUNTS 퍼미션은 GCM이 Google 계정을 요구하도록 합니다. (디바이스가 안드로이드 4.0.4 버전보다 낮은 경우에 필요합니다.)

- android.permission.WAKE_LOCK 퍼미션은 애플리케이션이 메시지를 받게 될 때 슬리핑 상태가 되는 것을 막아줍니다. ㅡ 앱이 디바이스가 슬립 상태가 되지 않는 것을 필요로 할 때에만 사용해야합니다.

- applicationPackage + ".permission.C2D_MESSAGE" 퍼미션 다른 안드로이드 애플리케이션이 해당 안드로이드 애플리케이션의 메시지를 받는 것을 막아줍니다. 퍼미션명은 이 패턴과 정확히 일치해야합니다. ㅡ 그렇지 않으면 안드로이드 애플리케이션은 메시지를 받지 못할 것 입니다.

- com.google.android.c2dm.intent.RECEIVE 리시버는 applicationPackage의 카테고리 묶음입니다. 리시버는 com.google.android.c2dm.SEND 퍼미션을 요구해야 하고, 이를 통해 GCM 프레임워크는 이에 메시지를 보낼 수 있게 됩니다. 만약 앱이 IntentService를 사용하고 있다면(강제되는 것은 아니지만 일반적으로 사용되는), 이 리시버는 WakefulBraodcastReceiver의 인스턴스가 되어야합니다. WakefulBroadcastReceiver 앱의 부분적인 wake lock의 생성과 관리를 담당하게 됩니다.

- WakefulBroadcastReceiver의 Service(일반적으로 IntentService)는 디바이스를sleep 프로세스로 돌아가지 않도록 하는 동안, GCM 메시지를 다루는 일을 하게됩니다. IntentService의 사용은 부가적인 것입니다. (사용하지 않을 수도 있습니다) 일반적인 BroadcastReceiver가 메시지를 처리하도록 할 수도 있습니다. 그렇지만 실질적으로는 대부분의 앱은 IntentService를 사용할 것 입니다.

- GCM 기능이 안드로이드 애플리케이션의 핵심적인 부분이라면, android:minSdkVersion="8" 또는 그 이상을 manifest에서 설정하도록 해야합니다. 이를 통해 안드로이드 애플리케이션이 적절하게 실행될 수 없는 환경에서 설치되지 않도록 보장할 수 있습니다. 


다음은 GCM을 지원하는 manifest 예제입니다 :


<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=".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>

</manifest>


Step 3: 애플리케이션 작성하기 


 마지막으로 애플리케이션을 작성해봅시다. 이 섹션은 GoogleCloudMessaging API를 어떻게 사용할 지에 관한 클라이언트 애플리케이션 예제를 포함합니다. 예제는 mainActivity (DemoActivity), WakegulBroadcastReceiver (GcmBroadcastReceiver), IntentService (GcmIntentService)로 구성되어 있습니다. 오픈 소스 사이트에서 전체 샘플 코드를 확인할 수 있습니다.


 다음을 확인하세요 :


- 예제는 여러가지 부분들 중에 등록과 upstream messaging(device-to-cloud) 에 관해 다루고 있습니다. Upstream messaging은 CCS(XMPP) 서버에 대응하는 앱에만 적용할 수 있습니다; HTTP 기반의 서버는 upstream 메시징을 지원하지 않습니다. 

- GoogleCloudMessaging 등록 API는 기존의 등록 프로세스를 대체합니다. 기존의 프로세스는 현재는 구식의 클라이언트 helper 라이브러리를 기반으로 하고 있습니다. 이전의 등록 프로세는 여전히 동작하지만, 우리는 내부 서버에 상관없이 새로운 GoogleCloudMessaging 등록 API를 사용하는 것을 권장합니다.


Google Play Services APK 체크하기 


 Setup Google Play Services SDK 에서 설명한 것 처럼,  Play Serivices SDK에 의존성을 가지는 앱은 디바이스가 Google Play service 기능에 접근하기 전에 Google Play Service APK와 호환이 가능한지 체크를 해야만합니다. 예제 앱에서 이 체크는 두 곳에서 행해집니다. main activity의 onCreate() 메소드와 onResume() 입니다. onCreate()에서의 체크는 성공적인 확인없이는 앱이 실행되지 않도록 합니다. onResume()에서의 체크는 사용자가 어떤 다른 방법을 통해 (예를 들면 백 버튼 같은) 실행 중인 앱으로 돌아올 때 역시 체크가 실행되게 됩니다. 만약 디바이스가 Google Play services APK와 호환이 되지 않는다면, 앱은 GooglePlayServiceUtil.getErrorDialog()를 호출하여 사용자가 Google Play Store에서 APK를 다운받도록 하거나 기기의 시스템 설정에서 이를 사용가능하게 하도록 합니다. 다음은 예제입니다 :


private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
...
@Override
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);

    setContentView
(R.layout.main);
    mDisplay
= (TextView) findViewById(R.id.display);

    context
= getApplicationContext();

   
// Play Services APK에 대해 기기 체크를 합니다.
   
if (checkPlayServices()) {
       
// 체크가 성공하면 일반적인 프로세스를 수행합니다.
       
// 그렇지 않다면, 바로 사용자가 유효한 Play Services APK를

// 얻을 수 있도록 합니다.
       
...
   
}
}

// 이 곳에서도 Play Services APK를 체크해야 합니다.
@Override
protected void onResume() {
   
super.onResume();
    checkPlayServices
();
}

/**
 * 기기가 Google Play Services APK 가지고 있는지를 확인합니다.

* 만약 그렇지 않다면 다이얼로그를 띄워 사용자가 APK를 Google Play Store

* 에서 다운받도록 하거나 기기의 시스템 설정에서 이를 사용할 수 있도록

* 하게 합니다.

 */
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(TAG, "This device is not supported.");
            finish
();
       
}
       
return false;
   
}
   
return true;
}


GCM 등록


 안드로이드 애플리케이션은 메시지를 받기 전에 GCM 서버에 등록이 필요합니다. 앱을 등록할 때, 앱은 registration ID를 받습니다. 이 아이디는 미래의 용도(registration ID들은 보안을 유지해야 하는 점을 유념하세요.)를 위해 저장할 수 있습니다. 다음의 예제 앱의 main activity안의 onCreate() 메소드 스니펫은 앱과 관련해서 GCM과 서버과 등록되어있는지를 확인하고 있습니다.


/**
 * 데모 앱의 메인 UI 입니다.
 */

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 final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

   
/**
     * 당신의 sender ID로 이 부분을 변경하세요. Sender ID는 API Console
     * 에서 얻을 수 있습니다. 이는 "Getting Started."에 설명되어 있습니다
     */

   
String SENDER_ID = "Your-Sender-ID";

   
/**
     * 로그 메시지에 쓰이는 태그.
     */

   
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();

       
// Play Services APK에 대하여 기기를 체크합니다. 

// 체크가 성공하면 GCM 등록 프로세스를 실행합니다.
       
if (checkPlayServices()) {
            gcm
= GoogleCloudMessaging.getInstance(this);
            regid
= getRegistrationId(context);

           
if (regid.isEmpty()) {
                registerInBackground
();
           
}
       
} else {
           
Log.i(TAG, "No valid Google Play Services APK found.");
       
}
   
}
...
}


 앱은 shared preference안에 registration ID가 존재하는 지 여부를 확인하기 위해서 getRegistraionId() 를 호출합니다 : 



/**
 * GCM 서비스 상의 애플리케이션에 대한 현재 registration ID를 가져옵니다.
 * <p>
 * 결과값이 없다면 등록 절차를 거칠 필요는 없습니다.
 * @return registration ID, registration ID가 존재하지 않는다면 빈 문자열
 */

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 "";
   
}
   
// 앱의 업데이트 여부를 확인합니다; 만약 업데이트가 확인되면,

// registration ID를 제거해야 합니다. 그렇지 않으면 남아있는

    // regID를 사용 시 새로운 버전에서 정상적으로 동작할 지 보장할 수 없습니다.
   
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;
}
...
/**
 * @return 애플리케이션의 {@code SharedPreferences}.
 */

private SharedPreferences getGCMPreferences(Context context) {
   
// 이 샘플 앱은 registration ID를 shared preference 안에 보관하고 있지만,
   
// 어디에 regID를 저장할 지는 개발자가 정할 수 있습니다.
   
return getSharedPreferences(DemoActivity.class.getSimpleName(),
           
Context.MODE_PRIVATE);
}


 registrationID가 존재하지 않거나 앱이 업데이되었다면 getRegistrationID()는 빈 문자열을 반환해서, 앱이 새로운 regID를 가져와야 함을 알려줍니다. getRegistrationID()는 앱 버전을 체크하기 위해서 아래 메소드를 호출합니다 :


/**
 * @return {@code PackageManager}의 애플리케이션의 버전 코드.
 */

private static int getAppVersion(Context context) {
   
try {
       
PackageInfo packageInfo = context.getPackageManager()
               
.getPackageInfo(context.getPackageName(), 0);
       
return packageInfo.versionCode;
   
} catch (NameNotFoundException e) {
       
// 일어나지 않아야 합니다
       
throw new RuntimeException("Could not get package name: " + e);
   
}
} 


 존재하는 유효한 registration ID가 없다면, DemoActivity는 등록을 위해 아래의 registerInBackground() 메소드를 호출하게 됩니다. GCM 메소드인 register()와 unregister()는 blocking 이기 때문에, 이들은 백그라운드 스레드에 위치해야합니다. 예제는 이를 위해 AsyncTask를 사용하고 있습니다 :


/**
 * 애플리케이션을 GCM 서버에 비동기적으로 등록합니다.
 * <p>
 * 애플리케이션의 shared preference에 registration ID와 앱 versionCode를 저장합니다. 
 */

private void registerInBackground() {
   
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;

               
// registration ID를 HTTP를 통해 당신의 서버로 보내야합니다,
               
// 그 후에 GCM/HTTP 또는 CCS를 사용하여 당신의 앱에 메시지를 보낼 수 있게 됩니다.
               
// 당신의 서버로의 요청은 당신의 앱이 계정을 사용하는지 인증이 되도록 해야합니다.
                sendRegistrationIdToBackend
();

               
// 이 예제의 경우: 우리는 이것은 보낼 필요가 없습니다.
               
// 기기가 메시지 안의 'from' 주소를 사용하여 서버로 upstream 메시지들을 보낼 것이기 때문입니다.
               
// regID를 보관합니다 - 재등록할 필요가 없어집니다.
                storeRegistrationId
(context, regid);
           
} catch (IOException ex) {
                msg
= "Error :" + ex.getMessage();
               
// 에러가 있다면, 등록 진행을 중단하세요.
               
// 사용자가 클릭 버튼을 다시 누르도록 하거나
               
// back-off를 수행할 수 있도록 하세요.
           
}
           
return msg;
       
}

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

 registrationID를 받았다면 이를 서버로 보내줍니다 : 

/**
 * registration ID를 HTTP를 통해 당신의 서버로 보냅니다. 이렇게되면
 * GCM/HTTP 또는 CCS를 사용하여 앱으로 메시지를 보낼 수 있습니다.

* 이 데모에서는 기기가 메시지를 메시지 안의 'from' 주소를 사용하여 

* 서버로 upstream하기 때문에 이를 필요로 하지 않습니다.

 */
private void sendRegistrationIdToBackend() {
   
// 여기에 필요한 부분을 구현하세요.
}

 등록이 끝나면 앱은 storeRegistrationId()를 호출합니다. 이를 통해 나중에 사용할 수 있도록 shared preference에 registrationID를 저장하게됩니다. 이것은 regID를 보관하는 방법 중의 하나일 뿐입니다. 다른 접근 방법을 사용하는 것 역시 가능합니다.

/**
 * registration ID와 앱 버전 코드를 애플리케이션의
 * {@code SharedPreferences} 안에 저장합니다.
 *
 * @param context 애플리케이션의 context.
 * @param regId registration ID
 */

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();
}

메시지 보내기

 사용자가 앱의 send 버튼을 클릭할 때. 앱은 GoogleCloudMessaging API들을 이용하여 upstream message 보내게 됩니다.  upstream message를 받기 위해서, 서버는 CCS에 연결되어야 합니다. 예제를 실행하고 CCS에 연결하기 위해 Implementing an XMPP-based App Server의 데모 서버 중 하나를 사용할 수 있습니다. 


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("my_message", "Hello World");
                        data
.putString("my_action",
                               
"com.google.android.gcm.demo.app.ECHO_NOW");
                       
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 2에서 설명한 것과 같이, 앱은 com.google.android.c2dm.intent.RECEIVE 인텐트에 대한 WakefulBroadcastReceiver를 포함하고 있습니다. broadcast receiver는 메시지를 전달하기 위해 GCM이사용하는 메카니즘입니다. onClick()에서 gcm.send()를 호출할 때, 이것은 broadcast receiver의 onReceiver() 메소드를 동작시킵니다. onReceiver()는 GCM 메시지를 조작할 수 있게하는 역할을 하게 됩니다.


 WakefulBroadcastReceiver는 앱의 wake lock의 일부에 관한 생성과 관리를 맡게되는 broadcast receiver의 특별한 타입입니다. 이것은 Service(일반적으로 IntentService)로의 GCM 메시지 처리 과정을 진행하게 됩니다. 그 동안에 전송 중에 기기를 슬립 상태로 돌아가지 않게됩니다.  만약 작업에서 서비스로 전송 중에 wake lock을 걸지 않았다면, 실질적으로 작업이 끝나기 전에 슬립 상태로 돌아가는 것을 허용하는 것이 됩니다. 메시지를 보낸 이후에 특정 시점까지 앱이 GCM 메시지의 처리를 완료하지 못하면, 전송 결과는 의도하지 않은 것으로 나올 수도 있습니다. 


 Note : WakefulBraodcastReceiver 는 필수는 아닙니다. 만약 상대적으로 단순한 앱을 만들려고 한다면, 서비스는 필요하지 않을 것이며, GCM 메시지를 일반적인 BroadcastReceiver로 intercept하여 처리할 수 있을 것 입니다. 만약 GCM이 broadcast receiver의 onReceive() 메소드로 넘어갔다면, 무엇을 할지는 당신에게 달려있습니다. 


 스니펫은 GcmIntentService의 startWakefulService() 메소드로 시작됩니다. 이 메소드는 WakefulBraodcastReceiver는 이 서비스가 시작될 때 wake lock을 걸게되는 부분을 제외하면 startService() 메소드와 호환이 가능합니다. startWakefulService()로 넘어간 intent는 wake lock의 추가적인 확인을 하게됩니다. 


 아래의 intent service는 GCM 메시지를 실제로 다루는 것을 보여주고 있습니다. 서비스가 종료되면, GcmBroadcastReceiver.completeWakefulIntent()를 호출해서 wake lock을 해제합니다. completeWakefulIntent() 메소드는 파라미터로 WakefulBroadcastReceiver로 부터 넘어온 같은 intent를 받습니다. 


 아래 스니펫은 메시지 타입에 기반을 둔 GCM 메시지 처리와 notification에서 결과를 보여주는 일을 하고 있습니다. 그렇지만 앱에서 GCM 메시지로 무엇을 할지는 당신에게 달려있습니다. 예를 들어 메시지가 핑이라면, 앱에게 새로운 콘텐츠를 반환하도록 서버와 싱크를 맞추도록 하거나 UI 화면의 채팅 메시지로 만들 수 있습니다. 


 예제 실행하기 

 예제를 실행하려면 :

 1. Getting Started의 지시에 따라 senderID와 API key를 발급받는다.

 2. 이 문서에 설명된 것처럼 클라이언트 앱을 구현한다. open source site에서 클라이언트 앱의 전체 소스 코드를 찾을 수 있습니다. 

 3. Implementing an XMPP-based App Server에서 제공되는 데모 서버(Java 또는 Python)를 실행하세요. 어떤 데모 서버를 선택하던 간에 실행 전에 senderID와 API 키를 코드에 추가하는 것을 잊지 마세요.


통계 보기