티스토리 뷰
푸시 서비스는 메시지를 구글 클라우드 서ㅓ에서 구글 Play 스토어가 설치된 단말기로 보내주는 방식이다.
이 푸시 서비스를 단말에서 연결을 유지하고 있기 때문에 사용하는 각각은 앱의 구글 클라우드 서버에서 직접 연결하지 않는다.
만, 이 구글 서비스를 이용하지 않고 직접 구현하려면 단말에서 서버로 연결을 유지하며ㅓ 동시에 연결을 지속적으로 유지해야 한다. 이는 polling 매커니즘을 구현하여 일정 시간 간격으로 연결이 끊어졌는 지 확인해야 하는데, 이러한 매커니즘은 휴대폰의 리소스를 많이 잡아 먹는 문제가 있다.
다음은 안드로이드에서 제공하는 FCM 푸시 메시지 처리 과정을 순서대로 나타낸 것이다.
- 단말은 자신을 클라우드 서버에 등록하고 서버로부터 등록 ID 를 받는다.
- 등록 ID는 메시지 전송을 담당할 애플리케이션 서버로 보낸 후 메시지를 기다린다.
- 보내려는 메시지는 애플리케이션 서버에서 클라우드에 접속한 후 전송한다.
- 클라우드 서버로 전송된 메시지는 대상 단말에 보내진다.
예제를 한번 확인해보자.
우선, 새 프로젝트를 생성하고 해당 프로젝트에세 FCM Push 서비스를 사용하기 위해서는 여기에서 푸시 정보를 새로 등록해줘야 한다.
새 프로젝트 생성을 마치면, 해당 FCM 프로젝트에 사용할 Android 프로젝트를 등록해줘야 한다.
등록할 프로젝트의 패키지 이름을 작성한 뒤,
json 형식의 FCM Service 파일을 다운받아 위 디렉토리에 추가해주면 된다.
이제 FCM Push Service를 사용하기 위해 build.gradle을 아래와 같이 수정한다. google-services의 버전은 위 SDK 설정 페이지에 나와있다.
◎build.gradle
buildscript {
repositories {
// Check that you have the following line (if not, add it):
google() // Google's Maven repository
}
dependencies {
// Add this line
classpath 'com.google.gms:google-services:4.3.10'
}
}
plugins {
id 'com.android.application'
}
apply plugin: 'com.google.gms.google-services'
android {
compileSdk 32
defaultConfig {
applicationId "com.example.samplepush"
minSdk 16
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation platform('com.google.firebase:firebase-bom:29.3.1')
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-analytics'
}
이제 FCM Push를 사용하기 위한 기본 설정이 끝났다. FCM을 사용하려면 앱 프로젝트 안에 두 개의 서비스를 만들어야 한다.
New - Service - Service 를 클릭하여 MyFirebaseMessagingService 를 생성한다.
◎MyFirebaseMessagingService.java
package com.example.samplepush;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "FMS";
public MyFirebaseMessagingService() {
}
@Override
// 새로운 토큰을 확인했을 때 호출되는 메서드
public void onNewToken(String token) {
super.onNewToken(token);
Log.d(TAG, "onNewToken 호출됨 : " + token);
}
@Override
// 새로운 메시지를 받았을 때 호출되는 메서드
// 푸시 메시지를 받았을 때, 그 내용을 확인한 후 엑티비티 쪽으로 보내는 메서드 호출
public void onMessageReceived(@NonNull RemoteMessage message) {
super.onMessageReceived(message);
Log.d(TAG, "onMessageReceived 호출됨.");
// 어디에서 메시지를 전송한 것인지 발신자 코드를 확인할 수 있다.
String from = message.getFrom();
Map<String, String> data = message.getData();
String contents = data.get("contents");
Log.d(TAG, "from : " + from + ", contents : " + contents);
sendToActivity(getApplicationContext(), from, contents);
}
// 엑티비티 쪽으로 데이터를 보내기 위해 인텐트 객체를 만들고 startActivity() 메서드 호출
private void sendToActivity(Context context, String from, String contents) {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("from", from);
intent.putExtra("contents", contents);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
}
}
처음 서비스 생성 시 상속하고 있던 Service 객체를 FirebaseMessagingService로 변경하고, onBind() 메서드를 삭제했다.
FirebaseMessagingService 객체도 마찬가지로 서비스 클래스이며 푸시 메시지를 전달 받는 역할을 담당한다.
구글 클라우드 서버에서 보내오는 메시지는 이 클래스에서 받을 수 있으며, 메시지가 도착하면 onMessageReseived() 메서드가 호출된다.
onNewToken() 메서드는이 앱이 Firebase 서버에 등록되었을 때 호출된다. 파라미터로 전달받는 토큰의 정보는 이 앱의 등록 id를 의미하며, 해당 id는 이 단말로 메시지를 전달하고 싶은 쪽에서 이 등록 id를 사용할 수 있다.
이제 Manifest 파일에서 이 서비스가 인텐트 필터를 가지도록 수정해야 한다.
◎AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.samplepush">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SamplePush">
<service
android:name=".MyFirebaseMessagingService"
android:enabled="true"
android:exported="true"
android:stopWithTask="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
FirebaseMessagingService에서 참조할 regID와 푸시 메시지를 화면에 띄우기 위한 MainActivity를 구성한다.
◎MainActivity.java
package com.example.samplepush;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.installations.FirebaseInstallations;
import com.google.firebase.messaging.FirebaseMessaging;
public class MainActivity extends AppCompatActivity {
TextView textView;
TextView textView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
textView2 = findViewById(R.id.textView2);
// 등록 ID 확인을 위한 리스너
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
@Override
public void onComplete(@NonNull Task<String> task) {
if (!task.isSuccessful()) {
Log.w("Main", "토큰 가져오는 데 실패함", task.getException());
return;
}
String newToken = task.getResult();
println("등록 id : " + newToken);
}
});
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String instanceId = String.valueOf(FirebaseInstallations.getInstance().getId());
println("확인된 인스턴스 id : " + instanceId);
}
});
}
public void println(String data) {
textView2.append(data + "\n");
Log.d("FMS", data);
}
@Override
protected void onNewIntent(Intent intent) {
println("onNewIntent 호출됨");
if (intent != null) {
processIntent(intent);
}
super.onNewIntent(intent);
}
private void processIntent(Intent intent) {
String from = intent.getStringExtra("from");
if (from == null) {
println("from is null.");
return;
}
String contents = intent.getStringExtra("contents");
println("DATA : " + from + ", " + contents);
textView.setText("[" + from + "]로부터 수신한 데이터 : " + contents);
}
}
FirebaseMessaging.getInstance() 메서드를 호출하여 해당 인스턴스를 참조한 뒤, addOnSuccessListener() 메서드를 이용하여 리스너를 등록해야 등록 ID가 반환되었을 때 onComplete() 메서드가 호출되어 ID 값을 확인할 수 있다.
또한 MainActivity에서는 MyFirebaseMessagingService가 보낸 Intent객체를 받아서 해당 푸시 메시지를 화면에 띄워주도록 구성한다.
이제 푸시 메시지를 보내줄 새로운 앱을 하나 구성한다. 이는 웹 통신으로 reqeustData를
https://fcm.googleapis.com/fcm/send
에 보내주기만 하면 되는 거라 앱을 구성해도 되고, java나 다른 언어를 사용하여 구성하여도 무방하다. 보통은 서버단에서 해당 푸시를 보내줘야 하기 때문에 어플로 구성하지는 않는 거 같다.
대충 메시지를 입력하고 전송 버튼을 눌러 구글 클라우드 서버에 전송하는 앱을 구성을 하려고 한다.
http 통신을 위한 volley 라이브러리를 추가한다.
◎build.gradle
// https://mvnrepository.com/artifact/com.android.volley/volley
implementation group: 'com.android.volley', name: 'volley', version: '1.2.1'
MainActivity는 아래와 같이 구성한다.
◎MainActivity.java
package com.example.samplepushsend;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.android.volley.*;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView textView;
static RequestQueue requestQueue;
static String regId = "eU0FbkOWRCyz5MrdrJuWle:APA91bG9Be8epmtXTOJrEJtankeK5nbzOVaTUHVbt6UQjcmgmb4aeFGBk7Nkn47HI0TNR7zCp3rQzMKEhBO9Vy9Sj2I4j4CruJZ94yKAbcC2Nf0kn7VRcxxHKmrBPrj7wwj7xY-P_Yb8";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.editText);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String input = editText.getText().toString();
send(input);
}
});
if (requestQueue == null) {
requestQueue = Volley.newRequestQueue(getApplicationContext());
}
}
public void send(String input) {
JSONObject requestData = new JSONObject();
try {
requestData.put("priority", "high");
JSONObject dataObj = new JSONObject();
dataObj.put("contents", input);
requestData.put("data", dataObj);
JSONArray idArray = new JSONArray();
idArray.put(0, regId);
requestData.put("registration_ids", idArray);
} catch (Exception e) {
e.printStackTrace();
}
sendData(requestData, new SendResponseListener() {
@Override
public void onRequestCompleted() {
println("onRequestCompleted() 호출됨.");
}
@Override
public void onRequestStarted() {
println("onRequestStarted() 호출됨.");
}
@Override
public void onRequestWithError(VolleyError error) {
println("onRequestWithError() 호출됨.");
}
});
}
public interface SendResponseListener {
public void onRequestStarted();
public void onRequestCompleted();
public void onRequestWithError(VolleyError error);
}
public void sendData(JSONObject requestData, final SendResponseListener listener) {
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST, "https://fcm.googleapis.com/fcm/send", requestData, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
listener.onRequestCompleted();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
listener.onRequestWithError(error);
}
}) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
return params;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "key=AAAAcigofdU:APA91bHYKr-nb8x2T-JhnneiTfla_VFDz75HXCYa4JowTXhLlk748awqkt13ke1nN625QM2mVl3UdZXTpWmxhKWJUsP9YT78QgzBEyw25DEIZxUOeiWDhymguo7BQMzecN6E3-nGGmeB");
return headers;
}
@Override
public String getBodyContentType() {
return "application/json";
}
};
request.setShouldCache(false);
listener.onRequestStarted();
requestQueue.add(request);
}
public void println(String data) {
textView.append(data + "\n");
}
}
전송하기 버튼을 누르면 입력 상자에 담겨있는 데이터를 담아 send() 메서드를 호출한다.
send() 는 JsonObject를 하나 생성하여 푸시 메시지로 보낼 데이터들을 담으며, 이 때 푸시 메시지를 받을 앱의 RegID를 registration_ids key에 담아 보내는데, 보통의 경우는 푸시 메시지를 받을 어플에서 메시지를 보내는 어플에게 regID를 보내주지만 따로 해당 로직을 구현하지 않았기 때문에 정적으로 선언해주었다.
JsonObject를 생성한 뒤 sendData() 메서드를 호출하여 구글 FCM 서비스로 JsonObject를 전송하게 된다.
이 때 생성한 인터페이스인
SendResponseListener
를 재정의하여 인자로 넣어주는데, 해당 인터페이스의 콜백 메서드들은 http Response의 상태에 따라 호출된다.
다음으로 JsonObjectRequest객체의 header에 해당 FCM service의 Authorization Key를 넣어 요청을 보내주기만 하면 끝이 난다.
'Mobile > Android' 카테고리의 다른 글
[Android] 시스템 서비스 활용 (0) | 2022.04.30 |
---|---|
[Android] 센서 이해하기 (0) | 2022.04.30 |
[Android] 알림 2: 상단 알림 (0) | 2022.04.26 |
[Android] 알림 1: 소리와 진동 (0) | 2022.04.26 |
[Android] 위치 2: 현재 위치의 지도 표시하기 (0) | 2022.04.22 |
- 맛집
- redux-thunk
- Async
- 정보보안기사 #실기 #정리
- await
- react
- 인천 구월동 이탈리안 맛집
- javascript
- redux
- Promise
- AsyncStorage
- 인천 구월동 맛집
- react-native
- 이탈리안 레스토랑
- 파니노구스토
- Total
- Today
- Yesterday