2026/02/18

오늘의 이야기



보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.













오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기

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


 





오늘의 이야기

앱을 만들면서 사용자 인증을 하는 경우 여러 가지의 인증 기능을 이용할 수 있을 것 같다.  그러다가 오늘의 이야기를 하게 된 이유는 


 


https://stackoverflow.com/questions/47619229/google-sign-in-failed-com-google-android-gms-common-api-apiexception-10



 


Google sign in failed com.google.android.gms.common.api.ApiException: 10:


So I'm Stuck on this frustrating issue. I am quite new to Google Auth on Firebase but I done everything the firebase docs instructed in how to integrate the Google SignIn Auth, yet I'm still receiv...


stackoverflow.com




 


이런 것과 같이 APIException 10 에러를 만났다는 것 때문이다. 이런 경우에는 어떻게 할 것인가?


 


https://firebase.google.com/docs/auth/android/account-linking?hl=ko 



 


Android에서 계정에 여러 인증 제공업체 연결하기  |  Firebase


Google은 흑인 공동체를 위한 인종적 평등을 추구하기 위해 노력하고 있습니다. 자세히 알아보기 의견 보내기 Android에서 계정에 여러 인증 제공업체 연결하기 인증 제공업체의 사용자 인증 정보


firebase.google.com




 


가이드 북이 말해 주는 것은 여러 가지 인증을 할 수 있다고 되어 있지만, 실제 만들고 있는 앱에서 오류가 발생하고 있다면... 난 어떻게 해결을 해야 하는 가?


 


https://stackoverflow.com/questions/54557479/flutter-and-google-sign-in-plugin-platformexceptionsign-in-failed-com-google/58644909#58644909



 


Flutter and google_sign_in plugin: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null)


The dialog (Google form) for the credentials is opened successfully, but after I fill my credentials I'm getting this error. I followed the instructions from here. Created a Firebase project, enabl...


stackoverflow.com




 


이런저런 설정에 대한 이야기들이 나와 있다. 


 


google signin 오류



이렇게 API Exception 12500 이 나오는 경우가 있는데, 이런 경우는 firebase의 콘솔에서 확인을 해 볼 필요가 있다.


 


firebase authentication 설정



이렇게 gmail의 사용 설정이 되어 있지 않는 경우인 것으로 보인다.


그럼 새 제공업체 추가를 클릭해서 설정을 추가해 보자.


 


sns 로그인 설정



이렇게 여러 가지 sns 계정을 활용할 수 있으니 필요에 따라 설정을 해 볼 필요가 있겠다.


난 구글을 이용할 거니까 구글을 클릭한다.


 


구글 설정 화면



사용 설정을 클릭하면 다음과 같이 프로젝트 이름이 나오고 프로젝트 지원 이메일 설정을 하도록 나온다. 지원 이메일은 내가 사용하는 gmail 계정을 입력하면 된다.


 


sns 로그인 설정



저장을 클릭하고 나면 다음과 같이 google-services.json 을 내려받을 수 있도록 나온다. 새로 받고, 나의 project에 적용해서 rebuild을 해 보아야겠다.


 


google 로그인 로그



 


 


로그인된 앱 화면



 


 


오호라... 이제 로그인된다. ㅎㅎㅎ


 


firebase의 콘솔에서 나의 프로젝트를 들어가서 추가한 앱을 살펴보았다. 


 




 


여기도 잘 되어 있는 것 같다.   이것으로 끝인가?


 


글쎄... 아직은 release 하지 않아서 모르지만... 이런 경우 대부분 release을 하게 되면 또 같은 문제를 마주하게 될 것이다. 그때는 당황하지 말고 sha 인증서 지문을 찾아서 저 앱 설정 부분에 추가해 주도록 하자.


 


인증서 지문이 sha1, sha256으로 나누어지는 것도 있고, debug와 release로 구분되어 있다.  잊지 말자.


 





오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

bottomnavigationview 을 구현하다가 클릭을 했을 때 버튼이 적용 되지 않는 상황이 발생 했다. 왜 ? 무엇 떄문에 이런 상황이 벌어지는 것인가 ? 


 


bottomnavigationview



원래 메뉴를 클릭할 때 마다 선택한 메뉴가 옮겨져 가야 하는 것인데... 도대체 왜 ?


 


        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener()
{
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
Log.e(TAG, "onNavigationItemSelected=" + item.getItemId()) ;
switch (item.getItemId()) {
case R.id.menu_search:
Log.d(TAG, "onNavigationItemSelected") ;
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_layout, boardSelectFragment).commitNow() ;
return true ;
case R.id.menu_chatlist:
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_layout, chatListFragment).commitNow() ;
return true ;
case R.id.menu_mypage:
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_layout, accountFragment).commitNow() ;
return true ;
default:
return false ;
}
}
});

 


원인은 딱 하나 해당 메뉴를 클릭했을 떄... return true ; 끝나야 하는 것인데... return false 로 끝을 내었더니만...


메뉴를 클릭해도 선택이 되지 않았던 것이다.


 


다시는 이런 실수를 하지 않기를 바라며...





오늘의 이야기

What's New in Bumblebee ... 새로운 버전에는 무엇이 ?


 


Unified Gradle test runner 


 
Depending on whether you run your tests from Android Studio or from the command line using the Android Gradle plugin, such as on your continuous integration server, you might see different test results, such as tests passing using one runner and failing on another. That's because each implements a different version of the Android instrumented test runner.
 
To resolve this issue, Android Studio Bumblebee now also uses Gradle's Android instrumented test runner when running your tests. So, you should expect consistent when running tests locally. This is a similar change to Android Studio Arctic Fox, which introduced all unit tests to run through Gradle.


통합 Gradle 테스트 러너



통합 Gradle 테스트 러너


 


Android Studio에서 테스트를 실행하는지 또는 지속적 통합 서버와 같은 Android Gradle 플러그인을 사용하여 명령줄에서 테스트를 실행하는지에 따라 한 실행기를 사용하여 테스트를 통과하고 다른 실행기를 사용하여 테스트가 실패하는 것과 같은 다른 테스트 결과를 볼 수 있습니다. 각각 다른 버전의 Android 계측 테스트 실행기를 구현하기 때문입니다.
 
이 문제를 해결하기 위해 이제 Android Studio Bumblebee는 테스트를 실행할 때 Gradle의 Android 계측 테스트 러너도 사용합니다. 따라서 로컬에서 테스트를 실행할 때 일관성을 기대해야 합니다. 이것은 Gradle을 통해 실행되는 모든 단위 테스트를 도입한 Android Studio Arctic Fox와 유사한 변경 사항입니다.


 


New in Layout Inspector 


 


Capture layout hierarchy snapshots Layout Inspector now allows you to save snapshots of your running app's layout hierarchy, so that you can easily share them with others or refer to them later. Snapshots capture the data you would typically see when using the Layout Inspector, including a detailed 3D rendering of your layout, the component tree of your View, Compose, or hybrid layout, and detailed attributes for each component of your UI. When you want to capture a snapshot, click Export snapshot from the Layout Inspector toolbar. 
Capture layout hierarchy snapshots In Android Studio Bumblebee, you can now use the Layout Inspector to inspect semantic information in your Compose layouts. When selecting a Compose node, use the Attributes window to check whether it declares semantic information directly, merges semantics from its children, or both. To quickly identify which nodes include semantics, either declared or merged, use select the View options dropdown in the Component Tree window and select Highlight Semantics Layers.


 


레이아웃 검사기의 새로운 기능


 


레이아웃 계층 스냅샷 캡처 이제 Layout Inspector를 사용하여 실행 중인 앱의 레이아웃 계층 스냅샷을 저장할 수 있으므로 다른 사람과 쉽게 공유하거나 나중에 참조할 수 있습니다. 스냅샷은 레이아웃의 상세한 3D 렌더링, View, Compose 또는 하이브리드 레이아웃의 구성 요소 트리, UI의 각 구성 요소에 대한 자세한 속성을 포함하여 Layout Inspector를 사용할 때 일반적으로 표시되는 데이터를 캡처합니다. 스냅샷을 캡처하려면 Layout Inspector 도구 모음에서 스냅샷 내보내기를 클릭합니다.
레이아웃 계층 스냅샷 캡처 Android Studio Bumblebee에서 이제 레이아웃 검사기를 사용하여 Compose 레이아웃의 의미 체계 정보를 검사할 수 있습니다. Compose 노드를 선택할 때 속성 창을 사용하여 의미 체계 정보를 직접 선언하는지, 자식의 의미 체계를 병합하는지 또는 둘 다인지 확인합니다. 선언되거나 병합된 의미 체계를 포함하는 노드를 빠르게 식별하려면 구성 요소 트리 창에서 보기 옵션 드롭다운을 사용하고 의미 체계 계층 강조를 선택합니다.


 


New Device Manager 


 


The Device Manager is a stand-in replacement for the AVD Manager, both from Android Studio's Welcome Screen or after you open a project. The Device Manager introduces some new capabilities that make this feature more easy to create and manage all of your local test devices, such as a flexible tool window, separate tabs to manage your virtual and physical devices, and details of each connected device.
Open the Device Manager by selecting More Actions > Virtual Device Manager from the Welcome screen, select View > Tool Windows > Device Manager after opening a project.


 


새 장치 관리자



새 장치 관리자


 


Device Manager는 Android Studio의 시작 화면에서 또는 프로젝트를 연 후 AVD Manager를 대체합니다. 장치 관리자는 유연한 도구 창, 가상 및 물리적 장치를 관리하기 위한 별도의 탭, 연결된 각 장치의 세부 정보 등 모든 로컬 테스트 장치를 보다 쉽게 만들고 관리할 수 있도록 하는 몇 가지 새로운 기능을 도입했습니다.
시작 화면에서 추가 작업 > 가상 장치 관리자를 선택하여 장치 관리자를 열고 프로젝트를 연 후 보기 > 도구 창 > 장치 관리자를 선택합니다.


 


New App Inspection


 


Inspect Jobs, Alarms, and Wakelocks The Background Task Inspector now allows you to inspect your app's Jobs, Alarms, and Wakelocks, in addition to the existing support for inspecting Workers. Each type of asynchronous task now appears under the appropriate heading in the inspector tab, allowing you to easily monitor its status and progress. Similar to Workers, you can select a Job, Alarm, or Wakelock to inspect its detailed information in the Task Details panel.
Network Inspector The Network Profiler in the Profilers tool window has now moved to the App Inspection tool window. If you've previously used the Network Profiler, all the same features and rich network traffic data is still available. Simply deploy your app to a device running API level 26 and higher and open the App Inspector > Network Inspector tab.


 




새로운 앱 검사


 


작업, 알람 및 Wakelock 검사 백그라운드 작업 검사기를 사용하면 작업자 검사에 대한 기존 지원 외에도 앱의 작업, 알람 및 Wakelock을 검사할 수 있습니다. 이제 각 유형의 비동기 작업이 검사기 탭의 해당 제목 아래에 나타나서 상태와 진행 상황을 쉽게 모니터링할 수 있습니다. 작업자와 유사하게 작업, 알람 또는 Wakelock을 선택하여 작업 세부 정보 패널에서 세부 정보를 검사할 수 있습니다.
네트워크 검사기 프로파일러 도구 창의 네트워크 프로파일러가 이제 앱 검사 도구 창으로 이동되었습니다. 이전에 네트워크 프로파일러를 사용한 적이 있다면 동일한 기능과 풍부한 네트워크 트래픽 데이터를 계속 사용할 수 있습니다. API 레벨 26 이상을 실행하는 기기에 앱을 배포하고 앱 검사기 > 네트워크 검사기 탭을 열기만 하면 됩니다.


 


Emulator runs inside Studio by default


 


The Android Emulator now runs directly inside Android Studio by default. This helps conserve screen real estate, and gives you the ability to write and test your apps without leaving Android Studio.
 
When the emulator is running, you'll have access to common emulator actions like device rotation and extended control options like navigation playback.
 
To run the emulator in a separate window instead go to File > Settings > Tools > Emulator and deselect Launch in a tool window.


 


설정 화면 - 애뮬레이터 설정



 


에뮬레이터는 기본적으로 Studio 내에서 실행됩니다.


 


이제 Android Emulator는 기본적으로 Android Studio 내에서 직접 실행됩니다. 이렇게 하면 화면 공간을 절약하는 데 도움이 되며 Android Studio를 종료하지 않고도 앱을 작성하고 테스트할 수 있습니다.
 
에뮬레이터가 실행 중일 때 장치 회전 및 탐색 재생과 같은 확장된 제어 옵션과 같은 일반적인 에뮬레이터 작업에 액세스할 수 있습니다.
 
대신 별도의 창에서 에뮬레이터를 실행하려면 파일 > 설정 > 도구 > 에뮬레이터로 이동하고 도구 창에서 시작을 선택 취소합니다.


 


 


----------------


 


이상으로 새로운 버전에 대한 설명을 번역해 보았다.





오늘의 이야기


#스하리1000명프로젝트,
外国人労働者と話すのが難しいこともありますよね?
簡単に役立つアプリを作りました!あなたは自分の言語で書き、他の人は自分の言語でそれを見ます。
設定に基づいて自動翻訳します。
簡単なチャットに非常に便利です。機会があったら見てみてください!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

앱 개발중에는 간혹적으로 이미지 을 불러다가 사용해야 하는 경우들이 종종 발생한다.  SQLite 을 이용할 때는 이미지를 byte 형태로 변환한 다음 저장하는 방식으로 이미지를 저장하고 불러오는 기능을 구현 했다.


 


    public long insertDayinfo(String mDate, String msg, byte[] image) {
long _id = -1 ;
ContentValues values = new ContentValues() ;
values.put("mdate", mDate);
values.put("msg", msg) ;
values.put("image", image);
_id = db.insert(tableName, null, values) ;
Log.i(TAG, "insert " + _id + " " + image.toString()) ;
return _id ;
}

public byte[] getByteArrayFromBitmap(Bitmap d) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
d.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray() ;
}

public Bitmap getAppBitmap(byte[] b) {
return BitmapFactory.decodeByteArray(b, 0, b.length);
}

이미지를 불러와서 SQLite 에 insert 을 할 때 위의 함수를 호출하는 방식으로 insert 을 하는 것이였다.


Bundle bundle = data.getExtras();
assert bundle != null;
Bitmap test = bundle.getParcelable("data");
binding.imageView2.setImageBitmap(test);

dbHandler = DBHandler.open(CalendarView.this);
long lId = dbHandler.insertDayinfo(StringUtil.getDateString(pDate), "", dbHandler.getByteArrayFromBitmap(test));
if (lId > -1) {
Toast.makeText(getApplicationContext(), "저장이 되었습니다.", Toast.LENGTH_LONG).show();
}
dbHandler.close();

이런식의 호출이 되었지 않을까 싶다.


 


그럼 이번에는 firebase의 realtime database 을 사용하면서 경험한 부분에 대해서 이야기를 해 보자.  쉽게 생각해서 위의 예시와 같이 imageview 에 들어 있는 것을 byte[] 형식으로 변환한 다음 저장을 하면 되리라... 실제 코딩도 그렇게 해 보았더니...아니나 다를까 오류가 발생했다... 지금의 소스는 수정이 되어 버린 상황이라... 발생했던 오류 코드만 찾아 보겠다.


 


com.google.firebase.database.DatabaseException: Serializing Arrays is not supported, please use Lists instead ...


 


firebase 는 Serializing arrays 을 지원하지 않는단다... 그래서 Lists 로 구현을 하라는...  처음에는 이게 뭔 소리인가 하는 생각으로 구굴링을 해 보았는데, 결국 byte 뿐만 아니라 array 형태의 데이터 형식은 지원을 하지 않는다는 것을 알게 되었다... 


 


그래서 다음과 같은 data 구조체를 선언하였다.


import com.billcoreatech.dailylike1010.utils.StringUtil;
import java.util.ArrayList;
import java.util.Arrays;

public class MemberBean {
String memberId;
String name ;
...

String subscriptionDate ;
String withdrawalDate ;
String imgMyInfo ; // 사진 이미지

public void setImgMyInfo(String imgMyInfo) {
this.imgMyInfo = imgMyInfo;
}

public String getImgMyInfo() {
return this.imgMyInfo ;
}

....

public String getUserType() {
return userType;
}
}

뭐 그냥 쉬운 string 으로만 구현 했다...  그럼 이제 화면의 이미지는 어떻게 저장을 할 것인가 ?


 




앱에 imageview 을 넣고 그안에 그림이 들어가는 activity을 구현하고  폰에서 저장된 이미지를 가져오고


그것을 저장하기 위해서 


 


이제 어떻게 ?


 


 


 


 


 


 


/**
* byte[] 을 string 으로
* @param b
* @return
*/
public static String byteArrayToBinaryString(byte[] b) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.length; ++i) {
sb.append(byteToBinaryString(b[i]));
}
return sb.toString();
}

public static String byteToBinaryString(byte n) {
StringBuilder sb = new StringBuilder("00000000");
for (int bit = 0; bit < 8; bit++) {
if (((n >> bit) & 1) > 0) {
sb.setCharAt(7 - bit, '1');
}
}
return sb.toString();
}

/**
* string 을 byte[] 로
* @param s
* @return
*/
public static byte[] binaryStringToByteArray(String s) {
int count = s.length() / 8;
byte[] b = new byte[count];
for (int i = 1; i < count; ++i) {
String t = s.substring((i - 1) * 8, i * 8); b[i - 1] = binaryStringToByte(t);
} return b;
}

public static byte binaryStringToByte(String s) {
byte ret = 0, total = 0;
for (int i = 0; i < 8; ++i) {
ret = (s.charAt(7 - i) == '1') ? (byte) (1 << i) : 0;
total = (byte) (ret | total);
}
return total;
}

/**
* drawable to string
* @param image
* @return
*/
public static String drawableToString(Drawable image) {
Bitmap bitmap = ((BitmapDrawable) image).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] reviewImage = stream.toByteArray();
return byteArrayToBinaryString(reviewImage);
}

/**
* drawable to string
* @param imgMyInfo
* @return
*/
public static Drawable stringToDrawable(String imgMyInfo) {
byte[] bData = binaryStringToByteArray(imgMyInfo);
ByteArrayInputStream is = new ByteArrayInputStream(bData);
return Drawable.createFromStream(is, "reviewImage");
}

이런 몇가지 함수를 class 에 넣었다... 그것 중에서 활용하는 방식은 다음과 같다.


옆을 들어 위에 함수들이 들어있는 class 가 StringUtil.java 라고 한다면 ...


    memberDb.orderByChild("emailAddress").equalTo(mUser.getEmail()).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
for(DataSnapshot ds : snapshot.getChildren()) {
mMemberBean = ds.getValue(MemberBean.class);
if (mMemberBean != null) {
binding.txtEmail.setText(mMemberBean.getEmailAddress());

...

if (mMemberBean.getImgMyInfo() == null) {
binding.imgMyinfo.setImageResource(R.drawable.image_512);
} else {
// 불러온 이미지를 화면의 이미지뷰에 넣기...
binding.imgMyinfo.setImageDrawable(StringUtil.stringToDrawable(mMemberBean.getImgMyInfo()));
}
}
}
}

@Override
public void onCancelled(@NonNull DatabaseError error) {

}
});


...


Bundle bundle = data.getExtras();
assert bundle != null;
Bitmap test = bundle.getParcelable("data");
binding.imgMyinfo.setImageBitmap(test);
// 이미지뷰에서 이미지를 읽어와서 저장하기 위한 변수에 넣기
mMemberBean.setImgMyInfo(StringUtil.drawableToString(binding.imgMyinfo.getDrawable()));

작업한 소스의 일부만 켭쳐한 것이기는 하지만, 함수 하나로 쓩~ 이미지를 변수에 넣고 뺴고가 가능해 진다.


 


그럼 실제 화면에는 


 




 이렇게 보이게 될 것이고.


 


 


 


 


 


 


 


 


 


 


firebase 의 데이터에는 어떻게 ?


imgMyInfo 란 변수에는 1과 0으로 구성된 숫자만 가득.... 




 


 


 


 


이렇게 해서 firebase 의 realtime database 을 활용하는 작업을 할 때에도 이미지 처리는 무난(?) 하게 할 수 있게 되었다.


 





오늘의 이야기

보호되어 있는 글입니다. 내용을 보시려면 비밀번호를 입력하세요. 확인