Android에서 FCM 푸시 사용하기

주의

이 문건은 과거 Hexo 블로그 (2017-12-25) 에서 이동된 문서입니다.

시간이 지남에 따라 최신 기술과 다를 수 있으니 주의 바랍니다.


Android에서 푸시 알림을 구현하려면?

전에 하던 프로젝트에서 안드로이드와 서버간의 푸시 알림을 구현하였습니다.
이걸 한번 정리해서 문서화 해야 했지만 시간이 없어서 정리를 못하다가 이번 기회를 통해 포스팅을 하여 정리하려 합니다.


img 01

위 이미지는 FCM 사이트에서 가져온 것인데, 이미지에서 보는 것처럼 푸시 서비스를 구현하려면 모바일측 구현과
푸시 메세지를 관리하는 서버 측 두 가지를 구현해야 합니다. (GUI는 제외하도록 하겠습니다.)

이번 포스팅에서는 FCM을 안드로이드에 적용하는 방법에 대해서 알아보도록 하겠습니다.


안드로이드에서 FCM 설정하기

설정하는 것은 공식문서를 참고하면 되지만, 이 포스팅에서는 핵심만 집어서 정리하도록 하겠습니다.


1. Firebase 프로젝트 만들기

FCM을 적용할 프로젝트에 Firebase 프로젝트를 추가해야 하는데, 콘솔사이트에서 프로젝트를 추가해야 합니다.
프로젝트가 미 추가 상태라면 위 콘솔 사이트에서 생성합니다.

img 02

생성이 완료되면 위 사진처럼 설정 아이콘 클릭 후 프로젝트 설정을 클릭하면 아래 사진처럼 설정 화면으로 이동하게 됩니다.

img 03


이곳에서 Android 앱에 Firebase 추가를 클릭하면 앱 등록 화면이 뜹니다.

앱 등록 구성 파일 다운로드
img 04 img 05

앱 등록화면에서는 안드로이드 패키지 이름(build.gradle의 android{ applicationId }에 선언된 것)을 등록 후,
다음 화면인 구성 파일 다운로드에서 설명에 나온 대로 json 파일을 프로젝트에 추가합니다.

이후는 아래의 사진처럼 작업을 진행합니다.

img 06


2. Dependencies 설정하기

아래의 의존성을 추가해줍니다.

dependencies {
	...
	ompile 'com.google.firebase:firebase-messaging:11.0.4'
}

4. FCM 처리를 위한 서비스 클래스 작성

Android Studio에서 서비스를 구현할 클래스를 만들어줍니다.

img 07

위 사진은 그냥 참고용이며, 저 같은 경우 패키지를 하나 만들고 그곳에 생성하였습니다.
2개의 클래스를 생성해야 하는데, FirebaseMessagingService를 상속받은 클래스와 FirebaseInstanceIdService를 상속받은 클래스를 만들어줍니다.

아래는 예시로 만든 클래스들입니다. 먼저 FirebaseMessagingService을 상속받은 클래스를 하나 만들어줍니다.

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import java.util.Date;
import java.util.Map;

public class CustomFirebaseMessagingService extends FirebaseMessagingService {

    /**
     * Called when message is received.
     *
     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
     */
    // [START receive_message]
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Map<String, String> pushDataMap = remoteMessage.getData();
        sendNotification(pushDataMap);
    }

    private void sendNotification(Map<String, String> dataMap) {
        Intent intent = new Intent(this, MachineListActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);

        NotificationCompat.Builder nBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(dataMap.get("title"))
                .setContentText(dataMap.get("msg"))
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setVibrate(new long[]{1000, 1000})
                .setLights(Color.WHITE,1500,1500)
                .setContentIntent(contentIntent);

        NotificationManager nManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nManager.notify(0 /* ID of notification */, nBuilder.build());
    }

이 서비스 클래스는 푸시 메세지를 받아서 처리하는 것을 구현합니다.
onMessageReceived 메서드에서는 푸시 메세지를 수신했을 때 호출이 되며, pushDataMap에는 푸시 메세지 내용이 담기게 됩니다.
sendNotification 메서드는 푸시 메세지를 알림으로 표현하는 처리를 담당하게 됩니다.
해당 메서드는 자신의 로직에 맞게끔 구현하면 되며, 위는 예로 구현을 한 것입니다.

다음은 FirebaseInstanceIdService를 상속받은 클래스 예시입니다.

import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;

public class CustomFirebaseInstanceIdService extends FirebaseInstanceIdService {

    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the InstanceID token
     * is initially generated so this is where you would retrieve the token.
     */
    // [START refresh_token]
    @Override
    public void onTokenRefresh() {
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        sendRegistrationToServer(refreshedToken);
    }
    // [END refresh_token]


    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM InstanceID token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private void sendRegistrationToServer(String token) {
		//FCM 토큰 갱신
    }
}

이 클래스는 어플리케이션이 처음 설치되거나 가끔 토큰이 갱신될 때 onTokenRefresh 메서드를 통해 FCM Token을 하나 생성이 되어집니다.
그리고 이 토큰이 생성되거나 갱신된 경우 서버에 보내주는 메서드인 sendRegistrationToServer를 호출하게 됩니다.

이 FCM 토큰을 통해 해당 기기로 서버에서 푸시 메세지를 보내게 해주는 고유 값입니다.
이 부분은 향후에 서버 측 구현 시 FCM 토큰이 갱신되는 부분을 개발해야 합니다.

사실 다른 블로그들에서는 여기까지만 대부분 설명을 하게 됩니다.
그런데 사실 이런 정보를 찾으시는 분들은 요구사항에 맞는 내용을 구현하기 위해 내용을 찾게 됩니다.
전부 다 커버할 수는 없지만 두 가지 예시 사항을 보고 어떤식으로 로직을 풀어나가야 할지를 알아보겠습니다.

알림 수신 여부

예를 들어 알림 수신 시간을 제한하는 기능을 구현해야 한다고 해봅시다.
그럼 아래와 같은 로직을 생각해 볼 수 있겠습니다.

  1. 사용자로부터 알림 수신을 제외할 시간을 입력받습니다.
  2. 1번의 데이터를 Sharedpreference 등에 저장을 해둡니다.
  3. FirebaseMessagingService를 구현한 클래스의 onMessageReceived메서드에서 푸시 알림 수신을 하였을 때 2번의 저장된 데이터를 읽어와서 해당 시간이 푸시 알림이 가능할 경우에만 sendNotification() 메서드를 호출.

저 같은 경우 아래와 같은 메서드를 하나 만들고 로직을 구현하였습니다.

private boolean isCanReceivePush(String pushMsg) {
	//시간 체크 로직
}

그래서 onMessageReceived 메서드에서 저 메서드를 호출하여 true 값을 받을 경우에만 메세지를 수신하는 처리를 하였습니다.


토큰 생성 및 갱신 부분

위에서 설명드린 것처럼 어플리케이션을 처음 설치할 때나 FCM 토큰을 갱신할 경우엔 Sharedpreference에 토큰 값을 저장해두는 게 좋습니다.
그래서 onTokenRefresh() 메서드에서 호출 시 아래와 같이 응용할 수 있습니다.

@Override
    public void onTokenRefresh() {
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();

        if (isValidString(refreshedToken)) { //토큰이 널이거나 빈 문자열이 아닌 경우
            if (!isValidString(getSharedPreferencesStringData(getApplicationContext(), AD_FCM_TOKEN))) { //토큰에 데이터가 없는 경우에만 저장
                setSharedPreferencesStringData(getApplicationContext(), AD_FCM_TOKEN, refreshedToken);
            }

            if (isValidString(getSharedPreferencesStringData(getApplicationContext(), AD_LOGIN_ID))) { //로그인 상태일 경우에는 서버로 보낸다.
                if (!refreshedToken.equals(getSharedPreferencesStringData(getApplicationContext(), AD_FCM_TOKEN))) { //기존에 저장된 토큰과 비교하여 다를 경우에만 서버 업데이트
                    setSharedPreferencesStringData(getApplicationContext(), AD_FCM_TOKEN, refreshedToken);
                    sendRegistrationToServer(refreshedToken);
                }
            }
        }
    }

제가 개발했던 구형 로직을 그대로 가져와서 약간 지저분해 보일 수 있습니다만 일단 참고 및 설명용임을 감안해주시기 바랍니다.
일단 저 같은 경우 아래의 로직으로 구현하였습니다.

  1. 갱신된 토큰이 정상인지 체크
  2. Sharedpreference에 저장된 FCM 토큰 값이 없는 경우에만 저장.
  3. 만약 로그인 상태일 경우 Sharedpreference에 갱신된 토큰을 저장하고 서버로 갱신된 토큰을 전송하는 메서드를 호출.

간단하게 두 가지의 예시만 설명을 드렸습니다.
다른 요구사항이 있다면 위에서 안내드린 것처럼 로직을 풀어보고 그에 맞춰서 구현을 하면 푸시는 어렵지 않게 구현을 하실 수 있습니다.


5. Manifest 파일 수정

위에서 생성한 서비스 클래스를 매니페스트에 등록합니다.

		<!-- Firebase Service -->
		<service
			android:name=".firebase.CustomFirebaseMessagingService"
			android:enabled="true"
			android:exported="true">
				<intent-filter>
					<action android:name="com.google.firebase.MESSAGING_EVENT"/>
				</intent-filter>
		</service>

		<service
           android:name=".firebase.CustomFirebaseInstanceIdService"
           android:enabled="true"
           android:exported="true">
           	<intent-filter>
					<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
             </intent-filter>
       </service>

위 작업을 다 마칠 경우 푸시를 수신할 수 있게 됩니다.


정리

사실 이 포스트 하나만으로는 푸시 서비스를 구현했다 할 수 없습니다.
개인적으로 푸시 서비스를 구현하려면 서버 측의 구현이 제일 중요한 것 같습니다.
이 포스트와 짝을 이룰 서버 측 구현 포스팅은 곧 작성하여 업데이트 하도록 하겠습니다.
또한 1:1 푸시 말고 구독 값을 이용하여 푸시를 받는 내용도 포스팅할 예정입니다.

업데이트를 기다려주세요.


Written by@MHLab
로또는 흑우집합소 🎲
와인관리, 시음노트, 셀러관리는 마와셀 🥂

🫥 My Service|  📜 Contact|  💻 GitHub