FCM 푸시 메시지를 구현하며 발생한 문제들을 모두 해결해보았다

 

GCM부터 FCM까지 구현하면 구현할수록 말도 많고 탈도 많았던 개발이었던 것 같다

필자는 웹뷰하나 달랑 있는 프로젝트에 필요한 기능을 기본적인 방법으로 직접 구현하는 형태로 작업을 주로 하고 있어서

항상 서버 쪽 FCM 발송 클래스 1개, 안드로이드 쪽 클래스 4개를 통으로 가지고 다닌다

 

별거아닐 수 있지만 여태 경험한 이야기를 해볼까 한다

소스의 일부는 이해를 돕기위해서나 한 클래스에서 보여질 수 있기위해 임의로 변경하여 작성하였기 때문에 복사하여 사용은 가능하지만 각자의 스타일대로 수정이 필요하다

 

1. 안드로이드 앱을 설치할 때 마다 토큰이 변경된다

//MyFirebaseInstanceIDService.java
public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {

    private static final String TAG = "MyFirebaseIIDService";
    
    @Override
    public void onTokenRefresh() {
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        
        setPreferences(getApplicationContext(),"tokenId",refreshedToken);
    }
    
    public static void setPreferences(Context context, String key, String value) {

        SharedPreferences p = (context.getApplicationContext()).getSharedPreferences("niphyang-fcm", context.MODE_PRIVATE);
        SharedPreferences.Editor editor = p.edit();
        editor.remove(key);
        editor.putString(key, value);
        editor.commit();

    }
}

 1) 위 소스는 FirebaseInstanceIdService를 상속받는 클래스의 일부이다

 2) onTokenRefresh 메소드를 오버라이드하여 토큰이 갱신될 때 마다 SharedPreferences 클래스이용하여 토큰을 저장한다

<!-- AndroidManifest.xml -->
<service android:name=".MyFirebaseMessagingService">
	<intent-filter>
    		<action android:name="com.google.firebase.MESSAGING_EVENT" />
	</intent-filter>
</service>

 3) FCM의 문서를 모두 보았겠지만 당연히 위 서비스를 AndroidManifest.xml에 작성해주어야 한다

 

2. Foreground에서는 정상적으로 수신되고 Background에서는 수신되지 않는다

//MyFirebaseMessagingService.java
public class MyFirebaseMessagingService extends FirebaseMessagingService {


    private static final String TAG = "MyFirebaseMsgService";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        if (remoteMessage.getData().size() > 0) {

            scheduleJob();
            sendNotification(remoteMessage.getData().get("content"),remoteMessage.getData().get("title"));

        }
    }
    private void scheduleJob() {
        FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this));
        Job myJob = dispatcher.newJobBuilder()
                .setService(MyJobService.class)
                .setTag("my-job-tag")
                .build();
        dispatcher.schedule(myJob);
    }
    private void sendNotification(String messageBody,String title) {
        NotificationUtil.show(this, title, messageBody, "새 메시지가 도착했습니다");
    }

}

1) Background에서 수신이 되지 않던 문제에 대하여 onMessageReceived메소드에서 remoteMessage.getNotification() 안에 있는 body와 title을 가져와 사용하던 반면 위 소스에서는 remoteMessage.getData() 안에 있는 컨텐츠와 타이틀을 사용하고 있다

 2) schedulJob도 새롭게 추가 하였다

<!-- AndroidManifest.xml -->
<service android:name=".MyFirebaseInstanceIDService">
	<intent-filter>
		<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
	</intent-filter>
</service>
<service android:name=".MyFirebaseMessagingService">
	<intent-filter>
		<action android:name="com.google.firebase.MESSAGING_EVENT" />
	</intent-filter>
</service>
<service android:name=".MyJobService" android:exported="false">
	<intent-filter>
		<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
	</intent-filter>
</service>
//MyJobService.java
public class MyJobService extends JobService {

    private static final String TAG = "MyJobService";

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.d(TAG, "Performing long running task in scheduled job");
        
        PowerManager powerManager;
        PowerManager.WakeLock wakeLock;
        powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "WAKELOCK");
        wakeLock.acquire(); // WakeLock 깨우기

        return false;
    }
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        return false;
    }

}

3) AndroidManifest.xml을 수정하고 MyJobService.java를 추가하였다.  추가된 클래스에는 수신시 화면을 켜주는 기능이 추가되었다

  <uses-permission android:name="android.permission.WAKE_LOCK" /> 을 등록해주어야 사용 가능하다

//푸시 발송 서버 소스 중 일부 
public static boolean sendFcm(String token, Map<String, String> extra, String message, String title) {
		String authKey = AUTH_KEY_FCM; // You FCM AUTH key
		String FMCurl = "https://fcm.googleapis.com/fcm/send"; // default

		try {
			URL url = new URL(FMCurl);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();

			conn.setUseCaches(false);
			conn.setDoInput(true);
			conn.setDoOutput(true);

			conn.setRequestMethod("POST");
			conn.setRequestProperty("Authorization", "key=" + authKey);
			conn.setRequestProperty("Content-Type", "application/json");

			JSONObject json = new JSONObject();
			
			JSONObject info = new JSONObject();
			info.put("title", title); // Notification title
			info.put("content", message); // Notification body
			// 수신자 토큰
			json.put("to", token.trim());
            
            		//중요한 부분
			json.put("data", info);

			OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
			wr.write(json.toString());
			wr.flush();
			conn.getInputStream();

			BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String inputLine;
			StringBuffer response = new StringBuffer();

			while ((inputLine = in.readLine()) != null) {
				response.append(inputLine);
			}
			in.close();

			return true;

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (ProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return false;
	}

 4) 위 소스는 자바로 구현한 푸시 발송 서버의 일부이다 "중요한 부분"이라고 표시된 부분을 보면 기존 info data를 notification이라는 키 값에 넣었으나 현재는 data에 넣고 있다 (발송시 data와 notification의 차이점은 꼭 찾아서 살펴보기를 권한다; background에서는 onMessageReceived를 타지 않는 점을 살펴보면 되겠다)

 

 

3. Notification 이미지가 깨지는 경우

<!-- AndroidManifest.xml -->
<meta-data android:name="com.google.firebase.messaging.default_notification_icon"
	android:resource="@mipmap/ic_launcher" />

 1) 첫째로 확인할 부분은 구글에서 권장하는 메트리얼 디자인을 사용했는가이다. 참고 :  https://material.io/ 

 2) 필자의 경우 메트리얼 디자인을 사용하지 않고 AndroidManifest.xml파일의 application태그 내 위 태그를 삽입하지 않으니 Foreground에서는 정상적으로 아이콘이 표시되지만 Background에서 수신시 아이콘이 깨져서 표시되었다

 

4.  FCM 메시지를 대량 발송하는 경우 (그룹전송을 지원한다)

 1) 개별로 발송하면서 특정 건수 구간마다 딜레이를 주는 방법 (정상적으로 발송되지만 시간이 오래 걸린다)

 2) 특정 건수의 쓰레드로 나눠서 발송하는 방법 (1000건씩 쓰레드를 생성하여 발송하였다 1만건 이하에서 정상적으로 발송되었다)

 2) https://firebase.google.com/docs/cloud-messaging/android/send-multiple?hl=ko

 

5. 기타

 1) 이 외에도 Notification 관련 오류가 많이 있었으나 이정도만 정리하고 추후에 추가하도록 하겠다