December 25, 2017
전에 하던 프로젝트에서 안드로이드와 서버간의 푸시 알림을 구현하였습니다.
이걸 한번 정리해서 문서화 해야 했지만 시간이 없어서 정리를 못하다가 이번 기회를 통해 포스팅을 하여 정리하려 합니다.
위 이미지는 FCM 사이트에서 가져온 것인데, 이미지에서 보는 것처럼 푸시 서비스를 구현하려면 모바일측 구현과
푸시 메세지를 관리하는 서버 측 두 가지를 구현해야 합니다. (GUI는 제외하도록 하겠습니다.)
이번 포스팅에서는 FCM을 안드로이드에 적용하는 방법에 대해서 알아보도록 하겠습니다.
설정하는 것은 공식문서를 참고하면 되지만, 이 포스팅에서는 핵심만 집어서 정리하도록 하겠습니다.
FCM을 적용할 프로젝트에 Firebase 프로젝트를 추가해야 하는데, 콘솔사이트에서 프로젝트를 추가해야 합니다.
프로젝트가 미 추가 상태라면 위 콘솔 사이트에서 생성합니다.
생성이 완료되면 위 사진처럼 설정 아이콘 클릭 후 프로젝트 설정을 클릭하면 아래 사진처럼 설정 화면으로 이동하게 됩니다.
이곳에서 Android 앱에 Firebase 추가를 클릭하면 앱 등록 화면이 뜹니다.
앱 등록 | 구성 파일 다운로드 |
---|---|
앱 등록화면에서는 안드로이드 패키지 이름(build.gradle의 android{ applicationId }에 선언된 것)을 등록 후,
다음 화면인 구성 파일 다운로드에서 설명에 나온 대로 json 파일을 프로젝트에 추가합니다.
이후는 아래의 사진처럼 작업을 진행합니다.
아래의 의존성을 추가해줍니다.
dependencies {
...
ompile 'com.google.firebase:firebase-messaging:11.0.4'
}
Android Studio에서 서비스를 구현할 클래스를 만들어줍니다.
위 사진은 그냥 참고용이며, 저 같은 경우 패키지를 하나 만들고 그곳에 생성하였습니다.
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 토큰이 갱신되는 부분을 개발해야 합니다.
사실 다른 블로그들에서는 여기까지만 대부분 설명을 하게 됩니다.
그런데 사실 이런 정보를 찾으시는 분들은 요구사항에 맞는 내용을 구현하기 위해 내용을 찾게 됩니다.
전부 다 커버할 수는 없지만 두 가지 예시 사항을 보고 어떤식으로 로직을 풀어나가야 할지를 알아보겠습니다.
예를 들어 알림 수신 시간을 제한하는 기능을 구현해야 한다고 해봅시다.
그럼 아래와 같은 로직을 생각해 볼 수 있겠습니다.
저 같은 경우 아래와 같은 메서드를 하나 만들고 로직을 구현하였습니다.
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);
}
}
}
}
제가 개발했던 구형 로직을 그대로 가져와서 약간 지저분해 보일 수 있습니다만 일단 참고 및 설명용임을 감안해주시기 바랍니다.
일단 저 같은 경우 아래의 로직으로 구현하였습니다.
간단하게 두 가지의 예시만 설명을 드렸습니다.
다른 요구사항이 있다면 위에서 안내드린 것처럼 로직을 풀어보고 그에 맞춰서 구현을 하면 푸시는 어렵지 않게 구현을 하실 수 있습니다.
위에서 생성한 서비스 클래스를 매니페스트에 등록합니다.
<!-- 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 푸시 말고 구독 값을 이용하여 푸시를 받는 내용도 포스팅할 예정입니다.
업데이트를 기다려주세요.