Android

[Firebase 클라우드 메시징] FirebaseMessagingService 사용시 앱에서 Firebase 알림을 비활성화(미노출)하는 방법

Firebase 클라우드 메시징 서비스 사용

서버를 운영하지 않는 개인 개발자라면 FireBase 메시징 서비스 만큼 좋은 것 서비스는 없다. FireBase 메시징 서비스를 사용하여 최신버전의 앱을 출시하였을 때 사용자에게 푸시알림을 발송하고 있다. FirebaseMessagingService 클래스를 사용하지 않아도 Firebase콘솔에 로그인하여, 알림을 발송할 수 있다. 이 방법은 무조건적인 단방향 소통이 된다. 사용자들은 이런 푸시알림을 원하지 않을 수 있다. 그래서 Firebase메시징 서비스는 주제 구독이라는 기능를 제공한다. 그럼에도 불구하고 문제점이 있다.

FirebaseMessagingService를 사용할 경우, 앱이 실행중이라면 주제 구독 기능이 정확하게 동작한다. 하지만 앱이 실행중인경우가 아닐때, 즉 백그라운드 서비스에서 돌아가는 중 일 때, 구독 관련 푸시알림을 보낼 경우 앱의 백그라운드 서비스는 제대로 일을 하지 않는다. 다시 말하면 FirebaseMessagingService를 확장하여 만든 커스터마이징 클래스 안에 걸어둔 조건들을 타지 않는다. 주제 구독을 하고 있는 사용자의 경우 앱이 켜져있든 꺼져있든 푸시알림은 사용자에게 전달 되니까 문제 되지않는다. 하지만 조금 옆으로 비켜나서 사용자가 구독을 하고 있고, 나는 코드단에서 예외처리를 하고 싶을 때가 있다.

가령, 앱 업데이트 알림을 보냈고, 일부 사용자들은 앱을 업데이트 할 것이며, 그렇지 않은 경우도 있다. 앱 업데이트를 강요하기 위해 또 다시 푸시알림을 보내야하는데, 앱을 이미 업데이트 한 사용자에게는 발송하면 안되는 조건이 생겼고 조건을 생성하였다. 우선 코드를 살펴보자.

■AndroidManifest.xml

         <service android:name=".common.FcmService"
             android:exported="false">
             <intent-filter>
                 <action android:name="com.google.firebase.MESSAGING_EVENT" />
             </intent-filter>
         </service>
         
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@drawable/push_icon" />
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_color"
            android:resource="@color/colorAccent" />

■FcmService.class

public class FcmService extends FirebaseMessagingService {
    public FcmService() {
    }
 

    @Override
    public void onNewToken(@NonNull String token) {
        super.onNewToken(token);
        //새 토큰이 생성될 때마다 onNewToken 콜백이 호출됩니다.
        Log.d(DefaultSettings.TAG, "Refreshed token: " + token);
    }
	
	// 앱이 켜져있을경우에만 이 메소드를 호출한다.
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
 
        //앱이 백그라운드 상태일 때는 알림 메시지가 작업 표시줄에 표시되며 onMessageReceived는 호출되지 않습니다.
        //알림 메시지에 데이터 페이로드가 있는 경우 알림 메시지는 작업 표시줄에 표시되며 알림 메시지에 포함된 데이터는 사용자가 알림을 탭할 때 실행되는 인텐트에서 검색할 수 있습니다.
        if(remoteMessage != null) {
            Log.d(DefaultSettings.TAG, "From: " + remoteMessage.getFrom());

            // Check if message contains a data payload.
            Log.d(DefaultSettings.TAG, "remoteMessage.getData().size() :" + remoteMessage.getData().size());
            if (remoteMessage.getData().size() > 0) {
                Log.d(DefaultSettings.TAG, "Message data payload: " + remoteMessage.getData());
                sendNotification(remoteMessage);
            }
        }
    }

    private int getNotificationIcon() {
        boolean useWhiteIcon = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP);
        return useWhiteIcon ? R.drawable.push_icon : R.mipmap.ic_launcher;
    }

    private void buildNotification(Intent intent, String title, String message){
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        String channelId = "APP_UPDATE";
        NotificationCompat.Builder notificationBuilder =
        new NotificationCompat.Builder(this, channelId)
                .setSmallIcon(getNotificationIcon())
                .setContentTitle(title)
                .setContentText(message)
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                //.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                ///.setLights(000000255,500,2000)
                .setContentIntent(pendingIntent);
 

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "업데이트 알림",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        } 

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
   

    private void sendNotification(RemoteMessage remoteMessage) {
        try {

            Log.d(DefaultSettings.TAG, "messageBody :" + remoteMessage.getData().get("message"));
            String title = remoteMessage.getNotification().getTitle() == null ? getResources().getString(R.string.app_name) : remoteMessage.getNotification().getTitle();
            String message = remoteMessage.getNotification().getBody();

            Log.d(DefaultSettings.TAG, "title : " + title);
            Log.d(DefaultSettings.TAG, "message : " + message); 

            Intent intent;
            String action = remoteMessage.getData().get("action");
            Log.d(DefaultSettings.TAG, "onMessageReceived Method :: action : " + action);
            if (action != null && action.equals(FCM_UPDATE)) {  // 마켓으로 이동 (업데이트)
                intent = StoreUtil.getIntentOnGPDetail(getPackageName()); 
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                this.buildNotification(intent, title, message);
            } else if (action != null && action.equals(FCM_NOT_UPDATE)) { 
                //미업데이트 유저체크 ::앱이 켜져있지않을경우에는 비교할 수 없어서 무조건 알림이 온다 ㅡ.,ㅡ;;이런;;댄장;
                try {
                    String inputTxt = remoteMessage.getData().get("inputTxt");
                    PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
                    Log.d(DefaultSettings.TAG, "onMessageReceived Method :: inputTxt : " + inputTxt);
                    Log.d(DefaultSettings.TAG, "onMessageReceived Method :: packageInfo.versionName : " + packageInfo.versionName);
                    if (inputTxt != null && !inputTxt.equals(packageInfo.versionName)) {  //입력한 버전과 버전이 다르면....
                        intent = StoreUtil.getIntentOnGPDetail(getPackageName());
                        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        this.buildNotification(intent, title, message);
                    }
                } catch (PackageManager.NameNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                intent = new Intent(this, MainActivity.class);
                this.buildNotification(intent, title, message);
            } 
        }catch (NullPointerException e){
       	    e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        } 
    }
}

FirebaseMessagingService 클래스를 확장하여 FcmService 클래스를 생성하였다. onMessageReceived()메소드는 앱이 실행중인 경우(포그라운드에 있는 경우에 해당) 에만 호출된다. 이 경우에는 앱을 업데이트하지 않은 사용자에게만 푸시알림이 전달되고, 앱을 업데이트한 사용자에게는 노출 되지않는다. 문제는 백그라운드에 있는 경우가 가장 큰 문제점이다. 즉, 앱이 꺼져있는 경우에는 onMessageReceived()메소드가 전혀 호출 되지않는다. 그렇기 때문에 앱을 업데이트하였는지 안하였는지 체크할 방법이 없다. 이러한 경우 앱을 사용하지 않을 때는 푸시알림을 전달해서는 안된다.

그래서 방법을 찾았지만 이 방법을 공유한 개발자도 이건 정말 멍청하고 어리섞은 짓이라고 문구를 달았다. 정말 그렇긴하다. 앱을 사용중인 사용자에게만 전달해야하는 푸시알림이 있다는 것 자체가 말이다. 사용하지 않는 경우가 더 많기 때문이며, 무엇보다도 이렇게하면 반쪽짜리 알림이 된다. 그래도 필요한 경우가 생길 수 있으니 기록해둔다.

방법은 FcmService클래스에서 onCreate()메소드를 오버라이드 하여, 푸시알림을 삭제처리하는 것이다. 코드는 다음과 같다.

public class FcmService extends FirebaseMessagingService {
    public FcmService() {
    }
 
 	..................생략
    
	@Override
    public void onCreate() {
        super.onCreate();
        removePush();
    }
    
    
    
    private void removePush(){
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                if(manager == null ) return;

                if (Build.VERSION.SDK_INT >= 23) {
                    StatusBarNotification[] notifications = manager.getActiveNotifications();
                    Log.d(DefaultSettings.TAG, " notifications.length : " + notifications.length);  //화면 잠금이거나 앱을 실행하지 않고 있을경우 값이 넘어온다.
                    //n.getTag : campaign_collapse_key_8282514140827925926
                    for (StatusBarNotification n : notifications) {
                        Log.d(DefaultSettings.TAG, "  : n.getTag : " + n.getTag());
                        if (n.getTag().contains("campaign_collapse_key_")) {
                            Log.d(DefaultSettings.TAG, "  : Found firebase push. remove it. : " + n.getTag());
                            manager.cancel(n.getTag(), n.getId());
                        }
                    }
                }
                else{
                    manager.cancelAll();
                }
            }
        },500);

    }
    
    
    ..................생략
}

앱이 실행중이지않을 경우 푸시알림을 보내면, onCreate()메소드를 호출하게되며, 앱이 실행중인 경우에는 생명주기에 따라 이미 지나갔음으로 호출되지않는 것을 이용한것이다. onCreate()메소드가 호출 되었을 때 getTag()메소드를 호출하여 로그를 찍어보면 “campaign_collapse_key_”로 시작하여 뒤에 숫자가 붙은 값이 넘어온다. 이 녀석을 캐취하여 푸시알림을 사용자가 볼 수 없게 삭제해주는 방법이다.

[참고 자료 및 관련 자료]
Android에서 Firebase 클라우드 메시징 클라이언트 앱 설정

백그라운드 앱에 테스트 메시지 보내기

Android 앱에서 메시지 수신

How can I disable firebase notification on android client app

Leave a Reply

error: Content is protected !!