본문 바로가기
Programming/Android

구글 GCM 구현하기

by 유주원 2016. 4. 26.

서버로부터 push 서비스를 받기 위해서는 구글에서 제공하는 GCM(Google cloud messaging) 서비스를 이용해야 한다. 


우선 구글 api 개발자 센터에 접속해서 GCM 서비스를 이용하기 위한 프로젝트를 만들자.




우측 상단에 API Project라는 메뉴를 클릭한 후 프로젝트 생성 메뉴를 클릭해서 프로젝트를 생성하자. 프로젝트를 생성하면 프로젝트 ID가 함께 생성되는데 이 ID 값을 google 측에 넘겨야 GCM ID를 받을 수가 있다. 


이제 프로젝트를 클릭해서 사용 하고자 하는 API를 선택해야 한다. Google Cloud Messaging을 클릭한 후 사용 설정을 활성화 해주자.



이제 서버에서 gcm 요청 curl을 날릴 때 인증을 위한 서버키를 만들어야 한다. 사용자 인증 정보에서 API 키를 클릭하자.






서버에서 사용할 것이기 때문에 서버 키를 클릭하고 간단한 label을 명기해주면 서버키가 생성된다. 생성된 서버키는 서버측에서 구글에 curl을 보낼 때 사용될 것이다. 




지금 막 멀 생성하고 만들고 하는데 머가 먼지 모르시는 분들을 위해 간단하게 architecture를 정리하자면 아래와 같다.



1. 구글 api 개발자 센터에서 생성한 projectID를 구글에 전송하고 gcmID를 받는다.

2. 수신한 gcmID를 서버에 전송해서 저장한다.

3. 서버에서는 gcmID와 구글 api 개발자 센터에서 생성한 서버키를 가지고 구글에 푸시 메시지를 요청한다.

4. 구글에서는 해당 gcmID를 가진 핸드폰에 푸시 메시지를 전달한다.


그럼 이번에는 클라이언트에서는 어떻게 구현해야 되는지를 살펴보자.


구글 gcm을 사용하기 위해서는 google play service 라이브러리를 필수적으로 사용해야 한다.

build.gradle 파일에 com.google.android.gms:play-services를 추가하고 gradle sync를 적용하자.


dependencies {

    .....

     compile 'com.google.android.gms:play-services:7.8.0'

    .....

}


play-service의 좀 더 최신 버전을 사용해도 무방하다. (나 같은 경우엔 예전 개발한 소스를 참조하느라..)


gradle 수정 후엔 manifest 파일에 권한을 추가해 주어야 한다. C2D_MESSAGE 권한 쪽에서는 패키지 명을 자신의 패키지 명을 입력해 주어야 한다.


<manifest>

     .....

    <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.indf.sadagu.permission.C2D_MESSAGE" android:protectionLevel="signature" />

    <uses-permission android:name="com.indf.sadagu.permission.C2D_MESSAGE" />

    .....

</manifest>




그리고 푸시를 받을 receiver와 service를 등록해준다.


<manifest>

    .....

    <application>

        ....

        <receiver android:name=".utils.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >

            <intent-filter>

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

                <category android:name="com.indf.sadagu" />

            </intent-filter>

        </receiver>

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

    </application>

</manifest>


gcm을 받을 준비 과정은 다 끝났으며, 이제는 gcm을 수신하기 위한 코드를 작성해 보자.


@Override

protected void onCreate(Bundle savedInstanceState){

    super.onCreate(savedInstanceState);

    

    ....

    if(checkPlayServices()){

        mGcm = GoogleCloudMessaging.getInstance(this);

        registerInBackground();

    }

}


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{

            finish();

        }

        return false;

    }

    return true;

}


private void registerInBackground(){

    new AsyncTask<Void, Void, String>(){

        @Override

        protected String doInBackground(Void... params){

            String msg;

            try{

                if(mGcm == null){

                    mGcm = GoogleCloudMessaging.getInstance(getApplicationContext());

                }

                msg = mGcm.register(SENDER_ID);

            }catch(IOException e){

                msg = "";

            }

            return msg;

        }


        @Override

        protected void onPostExecute(String msg){

            if(!msg.equals("")){

            }

        }

    }.execute(null, null, null);

}


우선 프로젝트ID를 구글에 보내고 gcmID를 얻는 방법 부터 살펴보자.


onCreate로 앱을 실행했을 시, checkPlayServices() 함수로 구글플레이서비스를 사용할 수 있는지 여부를 판단한다. 사용할 수 없다면 앱을 종료시킨다.

구글플레이 서비스를 사용할 수 있다면, gcm instance를 얻어오고, background로 구글에 프로젝트ID를 등록하고 gcmID를 얻어온다. 

이 과정은 mGcm.register(SENDER_ID) 이부분으로 요약해서 설명할 수 있는데, 개발자는 SENDER_ID 부분에 프로젝트 ID를 적으면 된다.


onPostExecute 부분에서 이제 gcmID를 받았는지를 판단한다. msg가 빈문자열이면 그냥 종료하고 msg가 gcmID 값을 가지고 있다면 서버에 해당 gcmID를 전송해서 저장하면 된다. 코드로 보면 if(!msg.equals("")) 이부분 안에 서버에 gcmID를 전송하는 부분을 기입하면 된다.


다른 예제 소스에서는 appversion이 키, gcmID가 값으로 preference에 저장한 후 appversion이 같을 경우 gcmID가 존재하면 구글에 gcmID를 요청하지 않는데, 서비스를 운영해 오면서 아래의 링크와 같은 문제점을 동일하게 겪은 바가 있다. 




문제 1

문제 2


gcmID가 바뀌게 된다면, 속수무책으로 푸시를 보낼수가 없기에 나같은 경우에는 앱을 켤때마다 구글에 gcmID를 요청하도록 로직을 구성하였다. (혹시 다른 방법으로 구현하신 분이 계시면 좀 알려주세요 ㅠ_ㅠ)


gcmID를 받아오는 부분의 구현은 완료가 되었고 이젠 푸시메시지를 받아서 뿌려주는 부분을 구현해 보자. 이를 위해 우리는 이전에 manifest 파일에서 receiver와 service를 등록해 둔 바가 있다.


우선 receiver 클래스를 생성한다.


public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {

    @Override

    public void onReceive(Context context, Intent intent) {

        ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());

        startWakefulService(context, (intent.setComponent(comp)));

        setResultCode(Activity.RESULT_OK);

    }

}


서버로부터 푸시 메시지가 전송되었을 때 처음 위의 receiver 클래스에서 받게 되며 receiver 클래스에서는 GcmIntentService라고 지정한 service를 실행시킨다. GcmIntentService에 대한 구현은 아래 부분에 있다.


public class GcmIntentService extends IntentService {

    private NotificationManager mNotificationManager;


    @Override

    protected void onHandleIntent(Intent intent) {

        Bundle extras = intent.getExtras();

        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

        String messageType = gcm.getMessageType(intent);


        if(!extras.isEmpty()){

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

                sendNotification(extras.getString("msg"));

            }

        }

    }


    private void sendNotification(String msg){

        mNotificationManager = (NotificationManager)this.getSystemService(Content.NOTIFICATION_SERVICE);

        String title = "";

        String body = "";

        String imageUrl = "";


        try{

            JSONObject jsonObject = new JSONObject(msg);

            title = jsonObject.getString("title");

            body = jsonObject.getString("body");

            imageUrl = jsonObject.getString("image");

        }catch(JSONException e){

            e.printStackTrace();

        }


        Intent intent = new Intent(this, MainActivity.class);

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        

        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);


        NotificationCompat.Builder notification = new NotificationCompat.Builder(this);

        try{

            URL url = new URL(imageUrl);

            Bitmap bigPicture = BitmapFactory.decodeStream(url.openConnection().getInputStream());

 

            NotificationCompat.BigPictureStyle notificationStyle = new NotificationCompat.BigPictureStyle();

            notificationStyle.setBigContentTitle(title);

            notificationStyle.bigPicture(bigPicture);


            notification.setSmallIcon(R.drawable.icon_push)

                              .setAutoCancel(true)

                              .setContentTitle(title)

                              .setContentText(body)

                              .setTicker(title)

                              .setStyle(notificationStyle)

                              .setContentIntent(contentIntent)

                              .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)

                              .build();

        }catch(MalformedURLException e){

            e.printStackTrace();

        }catch(IOException e){

            e.printStackTrace();

        }


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

        if(bigPicture != null && !bigPicture.isRecycled()){

            bigPicture.recycle();

        }

    }

}




GcmReceiver에서 호출한 Intent는 onHandleIntent에서 수신한다. intent 내에 담겨진 Bundle을 가져오고 bundle 내에 msg가 키로 되어 있는 데이터들을 가져와서 sendNotification에다 파라미터로 넘긴다.


sendNotification에서는 해당 message를 파싱을 해서 적절한 카테고리로 분류한다.(title, body, image 등등)


pendingIntent를 선언하는데, intent를 바로 띄우는게 아니라 사용자가 push를 클릭한 후에 intent 동작이 일어나야 하기 때문에 pendingIntent를 선언한다.


이후엔 푸시 메시지를 가공해서 사용자에게 보여주는 작업이다.


bigPicture라는 새로 나온 안드로이드 기능을 사용하기 위해 NotificationCompat을 사용하였다.

bigPicture가 머냐하면 푸시 메시지에 일정 크기의 이미지를 함께 넣어서 보낼 수 있는 기능을 말한다. bigPicture를 표현하기 위해서는 Bitmap을 써야 한다는 것에 주의하자. (imageLoader 안됨)


notification에 대한 title, body, sound, bibration, style(bigPicture) 등과 같은 설정을 모두 해준 후, 마지막에 mNotificationManager.notifiy로 호출하면 푸시 메시지가 나타나게 된다. NOTIFICATION_ID라는 것이 있는데 이 값이 같을 경우에는 계속 같은 메시지가 덮어 씌우는 것이고 이 값이 다르면 새로운 푸시 메시지로 띄우게 된다. 


마지막에 bitmap recycle 해주는 것을 잊지 말자.


이렇게 클라이언트 코드까지 작성 후 아래의 사이트를 통해 푸시 메시지를 테스트 해볼 수가 있다. 


푸시 메시지 테스트 사이트


api key는 구글 api 개발자 센터에서 발급 받은 서버 키를 입력하면 되고, regID는 앱에서 받은 gcmID 값을 입력하면 된다.