Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

안드로이드 앱 만들기 Firebase FCM 으로 메시지 전송하기

Billcorea 2021. 10. 22. 22:17
반응형

Fcm 으로 메시지를 수신하는 예제들은 많이 찾아 볼 수 있으나, 보내는 건 ? 그것도 안드로이드 앱으로 그런 예제는 없는 것 같아서 정리를 해 보겠다. 다만, 전체를 다 정리하는 것이 아니라 꼭 필요한 부분만...

 

  1. MainActivity 에 아래 함수를 넣고 앱이 실행 되는 동안에 처리를 하자.   

   - 목적은 allDevices 라는 것은 나중에 메시지 전송을 할 때 사용할 Topic 이다.  

     subscribeToTopoc 을 이용해서 내가 구동하는 메시지중에서 해당 Topic 으로 전송되는 것을 구독(?)할 수 있도록 등록을 해 두는 것이다. 

   - 두번째 목적은 getToken 함수를 이용해서 특정앱에게만 메시지를 보내고자 할 떄 token 값으로 구분 하여 메시지 수신자를 지정하기 위함이다.

    public void onRegistryToken() {

        FirebaseMessaging.getInstance().subscribeToTopic("allDevices")
                .addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        Log.e(TAG, "allDevices subscribed ...");
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Toast.makeText(getApplicationContext(), getString(R.string.nonSyncTopic), Toast.LENGTH_LONG).show();
                    }
                });

        FirebaseMessaging.getInstance().getToken()
                .addOnCompleteListener(new OnCompleteListener<String>() {
                    @Override
                    public void onComplete(@NonNull Task<String> task) {
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
                            return;
                        }
                        // Get new FCM registration token
                        String token = task.getResult();
                        getRegistryPhoneNumber(token);
                    }
                });
    }

 

2. 다음은 Message 전송을 위한 함수 코드을 만들어 두는 것이다. 

   - 아래는 전체 코드이고 package 이름만 숨김했다.

package com.bill..............tils;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.util.Log;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Scanner;

/**
 Firebase 클라우드 메시징(FCM)을 사용하여 iOS, Android 및 웹의 클라이언트에 메시지를 보낼 수 있습니다.

 이 샘플은 FCM을 사용하여 `news`를 구독하는 클라이언트에 두 가지 유형의 메시지를 보냅니다.
 주제. 메시지의 한 유형은 단순 알림 메시지(디스플레이 메시지)입니다. 다른 하나는
 플랫폼별 사용자 정의가 포함된 알림 메시지(알림 표시), 예를 들어,
 iOS 장치로 전송되는 메시지에 배지가 추가됩니다.
 */
public class Messaging {

    /**
     * project_id 는 firebase 에 등록한 나의 project ID
     */
    private static final String PROJECT_ID = "my-application-f80fb";
    private static final String BASE_URL = "https://fcm.googleapis.com";
    private static final String FCM_SEND_ENDPOINT = "/v1/projects/" + PROJECT_ID + "/messages:send";

    private static final String MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
    private static final String[] SCOPES = { MESSAGING_SCOPE };
    private static final String TAG = "FCM Messaging";

    public static String TITLE = "FCM Notification";
    public static String BODY = "Notification from FCM";
    public static String URL = "https://billcorea.tistory.com";
    public static String IMAGEURL = "";
    public static final String MESSAGE_KEY = "message";

    public static String tokenExam = "fIkVvZbATHu6Ilv1PNhp_8:APA91bG2q8MmfQvNqeP5afzKZRuLsTu1Mu6MVuYXuGlBhhgXZ3QGvP5EjoUBhVmmaBsT3CTKwKpUy5odRhzp5N46NB01txhxOFTUZnP8-LwnUovCx3hMGqNZtYW9jZ7FT-kCV37m7V2r";

    Context context ;
    SharedPreferences sp ;

    public Messaging(Context context) {
        sp = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE) ;
        this.context = context ;
    }

    /**
     FCM REST에 대한 요청을 승인하는 데 사용할 수 있는 유효한 액세스 토큰을 검색합니다.
     API.
     *
     *  https://console.firebase.google.com/u/1/project/fcm.......40/settings/serviceaccounts/adminsdk
     *  이 url 에서 비공개생성키 을 클릭 해서 생성된 파일은 assets 폴더에 넣는다.
     *     파일 이름은 소문자로만 적용한다.  (파일 이름이 너무 길면 줄이는 것이 좋다)
     *
     * @return Access token.
     * @throws IOException
     */
    // [START retrieve_access_token]
    public String getAccessToken(Context context) throws IOException {

        AssetManager assetManager = context.getAssets();
        AssetFileDescriptor fileDescriptor = assetManager.openFd("fcmdemo.json");
        FileInputStream fileInputStream = fileDescriptor.createInputStream();

        GoogleCredentials googleCredentials = GoogleCredentials
                .fromStream(fileInputStream)
                .createScoped(Arrays.asList(SCOPES));
        return googleCredentials.refreshAccessToken().getTokenValue();
    }
    // [END retrieve_access_token]

    /**
     * 검색 및 게시 모두에 사용할 수 있는 HttpURLConnection을 만듭니다.
     *
     * @return Base HttpURLConnection.
     * @throws IOException
     */
    public HttpURLConnection getConnection(Context context) throws IOException {
        // [START use_access_token]
        URL url = new URL(BASE_URL + FCM_SEND_ENDPOINT);
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
        httpURLConnection.setRequestProperty("Authorization", "Bearer " + getAccessToken(context));
        httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
        return httpURLConnection;
        // [END use_access_token]
    }

    /**
     HTTP를 사용하여 FCM 메시지에 요청을 보냅니다.
     UTF-8로 인코딩되고 특수 문자를 지원합니다.
     *
     * @param fcmMessage Body of the HTTP request.
     * @throws IOException
     */
    public void sendMessage(JsonObject fcmMessage, Context context) throws IOException {
        HttpURLConnection connection = getConnection(context);
        connection.setDoOutput(true);
        OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
        writer.write(fcmMessage.toString());
        writer.flush();
        writer.close();

        int responseCode = connection.getResponseCode();
        if (responseCode == 200) {
            String response = inputstreamToString(connection.getInputStream());
            Log.e(TAG, "Message sent to Firebase for delivery, response:");
            Log.e(TAG, response);
        } else {
            Log.e(TAG, "Unable to send message to Firebase:");
            String response = inputstreamToString(connection.getErrorStream());
            Log.e(TAG, response);
        }
    }

    /**
     공통 FCM 필드를 사용하여 모든 사용자에게 알림 메시지를 보내는 메시지를 보냅니다.
     플랫폼. 또한 플랫폼별 재정의는 메시지가 표시되는 방식을 사용자 지정하는 데 사용됩니다.
     안드로이드와 iOS에서 받았습니다.
     *
     * @throws IOException
     */
    public void sendOverrideMessage() throws IOException {
        JsonObject overrideMessage = buildOverrideMessage();
        Log.e(TAG, "FCM request body for override message:");
        prettyPrint(overrideMessage);
        sendMessage(overrideMessage, context);
    }

    /**
     FCM 요청의 본문을 작성합니다. 이 본문은 공통 알림 개체를 정의합니다.
     android 및 apns 개체를 사용하는 플랫폼별 사용자 정의도 가능합니다.
     *
     * @return JSON representation of the FCM request body.
     */
    public JsonObject buildOverrideMessage() {
        JsonObject jNotificationMessage = buildNotificationMessage();

        JsonObject messagePayload = jNotificationMessage.get(MESSAGE_KEY).getAsJsonObject();
        messagePayload.add("android", buildAndroidOverridePayload());

        jNotificationMessage.add(MESSAGE_KEY, messagePayload);

        return jNotificationMessage;
    }

    /**
     * Android에서 메시지가 수신되는 방식을 사용자 지정하는 Android 페이로드를 빌드합니다.
     *
     * @return android payload of an FCM request.
     */
    public JsonObject buildAndroidOverridePayload() {
        /**
         * 이부분은 notify 알림에 표시될 제목, 내용, 보여줄 이미지의 URL 경로
         */
        JsonObject androidNotification = new JsonObject();
        androidNotification.addProperty("body", BODY);
        androidNotification.addProperty("title", TITLE);
        androidNotification.addProperty("image", IMAGEURL);

        /**
         * 사용자 viewAcitivy 로 전달할 내용 과 URL
         *
         * FcmReceiveService 에서 URL, BODY 을 읽어서 ViewActivity 로 전달함.
         */
        JsonObject data = new JsonObject();
        data.addProperty("URL", URL);
        data.addProperty("BODY", BODY);

        JsonObject androidNotificationPayload = new JsonObject();
        androidNotificationPayload.add("notification", androidNotification);
        androidNotificationPayload.add("data", data);

        return androidNotificationPayload;
    }

    /**
     * iOS에서 메시지가 수신되는 방식을 사용자 지정하는 apns 페이로드를 빌드합니다.
     *
     * @return apns payload of an FCM request.
     */
    public JsonObject buildApnsHeadersOverridePayload() {
        JsonObject apnsHeaders = new JsonObject();
        apnsHeaders.addProperty("apns-priority", "10");

        return apnsHeaders;
    }

    /**
     전송되는 메시지에 배지 필드를 추가할 앱 페이로드를 빌드합니다.
     iOS 기기.
     *
     * @return JSON object with aps payload defined.
     */
    public JsonObject buildApsOverridePayload() {
        JsonObject badgePayload = new JsonObject();
        badgePayload.addProperty("badge", 1);

        JsonObject apsPayload = new JsonObject();
        apsPayload.add("aps", badgePayload);

        return apsPayload;
    }

    /**
     * 등록된 장치에 전달하기 위해 FCM에 알림 메시지를 보냅니다.
     *
     * @throws IOException
     */
    public void sendCommonMessage(Context context) throws IOException {
        JsonObject notificationMessage = buildNotificationMessage();
        Log.e(TAG, "FCM request body for message using common notification object:");
        prettyPrint(notificationMessage);
        sendMessage(notificationMessage, context);
    }

    /**
     * 알림 메시지 요청의 본문을 구성합니다.
     *
     * @return JSON of notification message.
     */
    public JsonObject buildNotificationMessage() {
        /**
         * notify 알림에 사용할 제목과 내용
         */
        JsonObject jNotification = new JsonObject();
        jNotification.addProperty("title", TITLE);
        jNotification.addProperty("body", BODY);

        JsonObject jMessage = new JsonObject();
        if (sp.getBoolean("pushOne", false)) {
            jMessage.addProperty("token", tokenExam);
        } else {
            jMessage.addProperty("topic", "allDevices");
        }
        jMessage.add("notification", jNotification);

        JsonObject jFcm = new JsonObject();
        jFcm.add(MESSAGE_KEY, jMessage);

        return jFcm;
    }

    /**
     * InputStream의 내용을 String으로 읽습니다.
     *
     * @param inputStream InputStream to read.
     * @return String containing contents of InputStream.
     * @throws IOException
     */
    public String inputstreamToString(InputStream inputStream) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        Scanner scanner = new Scanner(inputStream);
        while (scanner.hasNext()) {
            stringBuilder.append(scanner.nextLine());
        }
        return stringBuilder.toString();
    }

    /**
     * JsonObject를 예쁘게 인쇄하십시오.
     *
     * @param jsonObject JsonObject to pretty print.
     */
    public void prettyPrint(JsonObject jsonObject) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        Log.e(TAG, gson.toJson(jsonObject) + "\n");
    }

}

 

이 소스는 사실 구글링을 통해서 github 에서 퍼온 자료인데, 한국어 번역만 구글 번역을 통해서 해 보았다.

 

반응형

 

이 소스에서 봐야할 부분은 

 

- 서버인증을 위한 부분 인데... 여기서 중요한 것은 assetManager 의 자원을 가져오는 것이다.  fcmdemo.json 이라고 적었는데, 그것은 firebase 의 프로젝트 설정에서 얻어온다.   이것을 얻어와서 저장을 하는 것은 FCM 전송을 위한 서버 구현을 하기 위해서는 FCM 에 인증을 받아야 하는데,  개발가이드를 보면 서버에 보내고 받고 하는 과정이 있어야 한다고 되어 있기도 하지만, 아래 함수를 이용해서 token 을 받아오면 서버 인증절차가 한번에 해소가 되는 것을 확인하였다.

    public String getAccessToken(Context context) throws IOException {

        AssetManager assetManager = context.getAssets();
        AssetFileDescriptor fileDescriptor = assetManager.openFd("fcmdemo.json");
        FileInputStream fileInputStream = fileDescriptor.createInputStream();

        GoogleCredentials googleCredentials = GoogleCredentials
                .fromStream(fileInputStream)
                .createScoped(Arrays.asList(SCOPES));
        return googleCredentials.refreshAccessToken().getTokenValue();
    }

내 앱을 사용하기 위해서 구성한 firebase 의 프로젝트 설정에 보면 서비스 계정 이라고 있는데,  그곳에 보면 새 비공개키 생성이라고 버튼이 있다. 그 버튼을 클릭하면 기~인 이름의 json 파일 하나를 만들어 준다.   그럼 그것을 이름을 줄여서 저장하고. 

firebase 프로젝트 설정

 

내 앱 프로젝트에 assets 으로 넣어준다.   그런데, 여기서 하나 걸리는 부분이 그냥 넣어주고 빌드해 실행해 보면  오류가 발생한다.

 

 

 

 

 

 

 

실행시 오류

json 파일이 들어 있는데, 찾을 수 없다는 것이다. 왜 ?   빌드를 하면 압축(?)을 하나 보다. 그래서 json 파일을 제대로 읽어서 처리를 할 수 없는 것이다.    그래서 앱의 gradle 설정에 다음과 같이 추가 하였다.

gradle (module)

resources 는 noCompress 'json' 이라고... 압축을 하지 말라는 옵션이러니... 

 

- 그 다음은 상수로 선언된 project-id : 이것은 나의 프로젝트 일반에 있는 것을 가져다 적는다.

    /**
     * project_id 는 firebase 에 등록한 나의 project ID
     */
    private static final String PROJECT_ID = "my-application-f80fb";

 

프로젝트 일반

 

그리고 인증을 하기 위해서는 HttpURLConnection 을 이용하고 있기 때문이기도 하지만, manifest 에 user-permission 을 설정해 주는 것도 있지는 말아야겠다.

 

internet permission 설정

 

다음은 메세지를 보내는 함수 부분인데, 난 android 로만 메시지를 전송할 것이라서 buildAndroidOverridePayload함수만 사용한다.  그리고 그안에서 전달하고자 하는 파라미터등을 설정한다. body, title, image, URL, BODY 을 key로 설정해서 전달하고자 하는 값을 넣어 준다.  값을 global 변수를 이용해서 넣을 수 도 있고... 뭐 암튼...

    /**
     FCM 요청의 본문을 작성합니다. 이 본문은 공통 알림 개체를 정의합니다.
     android 및 apns 개체를 사용하는 플랫폼별 사용자 정의도 가능합니다.
     *
     * @return JSON representation of the FCM request body.
     */
    public JsonObject buildOverrideMessage() {
        JsonObject jNotificationMessage = buildNotificationMessage();

        JsonObject messagePayload = jNotificationMessage.get(MESSAGE_KEY).getAsJsonObject();
        messagePayload.add("android", buildAndroidOverridePayload());

        jNotificationMessage.add(MESSAGE_KEY, messagePayload);

        return jNotificationMessage;
    }

   /**
     * Android에서 메시지가 수신되는 방식을 사용자 지정하는 Android 페이로드를 빌드합니다.
     *
     * @return android payload of an FCM request.
     */
    public JsonObject buildAndroidOverridePayload() {
        /**
         * 이부분은 notify 알림에 표시될 제목, 내용, 보여줄 이미지의 URL 경로
         */
        JsonObject androidNotification = new JsonObject();
        androidNotification.addProperty("body", BODY);
        androidNotification.addProperty("title", TITLE);
        androidNotification.addProperty("image", IMAGEURL);

        /**
         * 사용자 viewAcitivy 로 전달할 내용 과 URL
         *
         * FcmReceiveService 에서 URL, BODY 을 읽어서 ViewActivity 로 전달함.
         */
        JsonObject data = new JsonObject();
        data.addProperty("URL", URL);
        data.addProperty("BODY", BODY);

        JsonObject androidNotificationPayload = new JsonObject();
        androidNotificationPayload.add("notification", androidNotification);
        androidNotificationPayload.add("data", data);

        return androidNotificationPayload;
    }

 

그럼 메시지 수신을 하는 FcmReceiveService 을 잠깐 살펴 보자.  onMessageReceived 부분을 보면 payload 에서 들어간 내용 URL, BODY 이런 것들이 키로 수신이 되는 것을 볼 수 있다.  그래서 그것을 저정하거나 해서 잘 활용하면 

수신된 메시지를 나의 앱으로 전달하는 방법으로 처리를 할 수 있을 것이다.

package com.bil.......................tils;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import com.billcoreatech.msgfcm1020.MainActivity;
import com.billcoreatech.msgfcm1020.R;
import com.billcoreatech.msgfcm1020.ViewActivity;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import java.util.Map;

public class FcmReceiveService extends FirebaseMessagingService {

    private static final String TAG = "FcmReceiveService";
    SharedPreferences sp ;
    SharedPreferences.Editor editor ;

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        // TODO(developer): Handle FCM messages here.
        // 여기에 메시지가 수신되지 않습니까? 이것이 가능한 이유를 참조하십시오 : https://goo.gl/39bRNJ
        Log.e(TAG, "From: " + remoteMessage.getFrom());

        // 메시지에 데이터 페이로드가 포함되어 있는지 확인하십시오.
        if (remoteMessage.getData().size() > 0) {
            Log.e(TAG, "Message data payload: " + remoteMessage.getData());

            sp = getSharedPreferences(getPackageName(), MODE_PRIVATE);
            editor = sp.edit() ;

            Map<String, String> strMap = remoteMessage.getData();
            Log.e(TAG, "URL=" + strMap.get("URL"));
            Log.e(TAG, "BODY=" + strMap.get("BODY"));

            editor.putString("URL", strMap.get("URL").contains("http") ? strMap.get("URL") : "https://" + strMap.get("URL"));
            editor.putString("BODY", strMap.get("BODY"));
            editor.putBoolean("FcmTy", true) ;
            editor.commit();

            handleNow();

        }

        // 메시지에 알림 페이로드가 포함되어 있는지 확인합니다.
        if (remoteMessage.getNotification() != null) {
            Log.e(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
            sendNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody()) ;
        }

        onDeletedMessages();
    }
    // [END receive_message]


    @Override
    public void onNewToken(String token) {
        Log.e(TAG, "Refreshed token: " + token);

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // FCM registration token to your app server.
        sendRegistrationToServer(token);
    }
    // [END on_new_token]

    /**
     * BroadcastReceivers에 할당된 시간을 처리합니다.
     */
    private void handleNow() {

        Log.d(TAG, "Short lived task is done.");
        Intent intent = new Intent(FcmReceiveService.this, ViewActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

    }

    /**
     타사 서버에 토큰을 유지합니다.

     이 방법을 수정하여 사용자의 FCM 등록 토큰을 임의의
     애플리케이션에서 유지 관리하는 서버 측 계정.
     *
     * @param token The new token.
     */
    private void sendRegistrationToServer(String token) {
        // TODO: Implement this method to send token to your app server.
    }

    /**
     * 수신된 FCM 메시지가 포함된 간단한 알림을 만들고 표시합니다.
     *
     * @param messageBody FCM message body received.
     */
    private void sendNotification(String msgTitle, String messageBody) {

        sp = getSharedPreferences(getPackageName(), MODE_PRIVATE);
        editor = sp.edit() ;
        editor.putString("BODY", messageBody);
        editor.putBoolean("FcmTy", true) ;
        editor.commit();

        Log.e(TAG, "---------------------------------------------------------------------");
        Bundle bundle = new Bundle();
        bundle.putString("TITLE", msgTitle);
        bundle.putString("BODY", messageBody);

        Intent notifyIntent = new Intent(this, MainActivity.class);
        notifyIntent.putExtras(bundle);
        // Set the Activity to start in a new, empty task
        notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent notifyPendingIntent = PendingIntent.getActivity(
                this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT
        );
        String channelId = getString(R.string.default_notification_channel_id);
        CharSequence channelName = getString(R.string.default_notification_channel_name);
        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId);
        builder.setContentIntent(notifyPendingIntent)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle(getString(R.string.fcm_message))
                .setContentText(messageBody)
                .setAutoCancel(true)
                .setSound(defaultSoundUri);

        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_HIGH);
            channel.enableLights(true);
            channel.enableVibration(true);
            channel.setLightColor(Color.BLUE);
            channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(Integer.parseInt(getString(R.string.default_notification_channel_id)), builder.build());

    }

}

 

수신된 알림이 notification 으로 저장이 되는 경우, 사용자는 알림창에 나와 있는 알림을 클릭했을 때 내 앱이 실행 되면서 그 내용을 확인할 수 있을 것이다.  알려야 하는 내용을 전달하는 방법은 메시지만 전달하거나, 미리 전달할 내용을 realtimedatabase 등에 저장해 두었다가, 확인해 볼 수 있도록 앱을 구성해 볼 수 있을 것 같다. 

 

알림 수신을 위한 FcmReceiveService는 manifest 에 service 로 등록해 주면 된다. 이런 부분들은 구글링을 해 보면 많이 나오고 해서 추가 설명은 생략해 보겠다.

        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@drawable/ic_launcher_foreground" />
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_color"
            android:resource="@color/purple_500" />
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/default_notification_channel_id" />

        <service
            android:name=".FcmUtils.FcmReceiveService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

 

3.  MainActivity 에서 메시지 전송을 구현해 보자...

    아래 소스 처럼 필요한 부분만 살펴 보도록 하겠다.

package co.......................020;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
........


import java.io.IOException;
import java.util.ArrayList;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    
    ......
    
    
    String strFCM = "" ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        # FcmreceiveService 에서 extra 로 전달하면 그 값을 확인하기 위해서 추가
        if (extras != null) {
            Log.e(TAG, "===" + extras.getString("BODY"));

            .....
        }

        # 안드로이드 버전이후 에서는 channel ID 을 설정해 주어야 한다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Create channel to show notifications.
            String channelId  = getString(R.string.default_notification_channel_id);
            String channelName = getString(R.string.default_notification_channel_name);
            NotificationManager notificationManager =
                    getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(new NotificationChannel(channelId,
                    channelName, NotificationManager.IMPORTANCE_LOW));
        }      
    }

    @Override
    protected void onStart() {
        super.onStart();

        ...

        binding.btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (sp.getBoolean("pushOne", false) && getChkCnt(userInfos) < 1) {
                    Toast.makeText(getApplicationContext(), getString(R.string.msgCheckOnlyOne), Toast.LENGTH_LONG).show();
                    return;
                }
                pushBinding = PushentryBinding.inflate(getLayoutInflater());
                pushBinding.edURL.setText("billcorea.tistory.com");
                pushBinding.edMesg.setText("test send messages ");
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.myDialog);
                builder.setTitle(getString(R.string.pushTitle))
                        .setMessage(getString(R.string.msgPushMessages))
                        .setView(pushBinding.getRoot())
                        .setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                            
                                # 메시지 전송을 위한 호출 
                                SendNotify(pushBinding.edMesg.getText().toString(),
                                        pushBinding.edURL.getText().toString()) ;
                            }
                        })
                        .setNegativeButton(getString(R.string.CANCEL), null);
                AlertDialog dialog = builder.create();
                dialog.show();
            }
        });
    }

    /**
     * 전송할 메시지와 url 을 입력 받아옴.  fragment 에서 개인/전체 발송 구분은 선택해서 sp 에 저장 되어 있다고 봄
     * @param msg
     * @param url
     */
    private void SendNotify(String msg, String url) {

        # 쓰레드을 사용하는 건 http 전송을 하기 때문에...
        new Thread(() -> {

            Messaging messaging = new Messaging(getApplicationContext());

            # 전송하고 싶은 값들을 여기서 선언해 준다. 
            # 추가가 필요하면 Message class 에 선언해 주면서 추가 하면됨.
            messaging.TITLE = getString(R.string.app_name) ;
            messaging.BODY = msg ;
            messaging.URL = url ;

            Log.e(TAG, "Send Messages " + msg);
            Log.e(TAG, "pushOne=" + sp.getBoolean("pushOne", false));
            Log.e(TAG, "pushAll=" + sp.getBoolean("pushAll", false));
            Log.e(TAG, "userInfo=" + userInfo.getUserToken());

            if (sp.getBoolean("pushOne", false)) {
                if (userInfo == null) {
                    Toast.makeText(getApplicationContext(), getString(R.string.nonSelectUser), Toast.LENGTH_LONG).show();
                    return ;
                }

                if (!userInfo.isUseTy()) {
                    Toast.makeText(getApplicationContext(), getString(R.string.nonSelectUser), Toast.LENGTH_LONG).show();
                    return ;
                }

                /**
                 * 개인에게 만 전송할 떄 : 미리 수집된 token 값으로 전달하고
                 */
                messaging.tokenExam = userInfo.getUserToken() ;
                try {
                    messaging.sendOverrideMessage();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {

                /**
                 * 전체에게 전송할 떄 : 앱 실행시 구독설정한 topic 으로 전송을 하게 됨
                 */
                try {
                    messaging.sendOverrideMessage();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }).start();

    }
 
    # 토근 얻어오기 및 구독 설정 하기 
    public void onRegistryToken() {

        FirebaseMessaging.getInstance().subscribeToTopic("allDevices")
                .addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        Log.e(TAG, "allDevices subscribed ...");
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Toast.makeText(getApplicationContext(), getString(R.string.nonSyncTopic), Toast.LENGTH_LONG).show();
                    }
                });

        FirebaseMessaging.getInstance().getToken()
                .addOnCompleteListener(new OnCompleteListener<String>() {
                    @Override
                    public void onComplete(@NonNull Task<String> task) {
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
                            return;
                        }
                        // Get new FCM registration token
                        String token = task.getResult();
                        getRegistryPhoneNumber(token);
                    }
                });
    }

}

처음에 보았던 client 토큰 값을 이용해서 개별에게 전달을 하거나,  구독 설정한 topic 을 통해서 전체 전달을 할 수 있다. 

 

여기서 중요한 것은 개별 전송을 위한 token 을 메시지를 보내는 쪽에서 알아야 한다는 것인데,  그걸 해소하기 위해서는 추천하는 방법으로는 realtime database 에 저장을 해서 공유하는 방법이 실시간으로 데이터를 공유할 수 있지 않을 까 생각이 든다. 관리자는 모든 사용자의 token 값을 알고 있어야 하고, 서로 모르는 상대방에게 메시지를 보낸다면 특정인의 token을 알 수 있는 방법이 있어야 하기 떄문이다 .

 

 

p.s : 2021.11.15 오늘 공개할 python 이야기도 잠시 참고해 보시길.

 

https://billcoreapython.tistory.com/29

 

반응형