0x00 FCM简介

消息类型
使用 FCM,您可以向客户端发送两种类型的消息:

  • 通知消息,有时被称为“显示消息”。此类消息由 FCM SDK 自动处理。
  • 数据消息,由客户端应用处理。

通知消息包含一组预定义的用户可见的键。 与其相对,数据消息只包含用户定义的自定义键值对。通知消息可以包含可选的数据载荷。两种消息类型的载荷上限均为 4KB,但从 Firebase 控制台发送消息时会强制执行 1024 个字符的限制。
使用情景 如何发送
通知消息 FCM 代表客户端应用自动向最终用户设备显示消息。通知消息包含一组预定义的用户可见键以及自定义键值对的可选数据载荷。 在可信环境(例如 Cloud Functions 或应用服务器中),使用 Admin SDK 或者 FCM 服务器协议:设置 notification 键。可能包含可选的数据载荷。 一律可折叠。请参阅一些显示通知示例并发送请求载荷。使用 Notifications Composer:输入消息文本、标题等,然后发送。 通过提供自定义数据添加可选的数据载荷。
数据消息 客户端应用负责处理数据消息。数据消息仅包含自定义键值对,没有保留键名(请参阅下文)。 在可信环境(例如 Cloud Functions 或应用服务器中),使用 Admin SDK 或者 FCM 服务器协议:仅设置 data 键。

0x01 FCM中几个重要凭据说明:

根据要实现的 FCM 功能的不同,您可能需要下列来自 Firebase 项目的凭据:
项目 ID 您的 Firebase 项目的唯一标识符,用于向 FCM v1 HTTP 端点发出请求。您可以在 Firebase 控制台设置窗格中找到该值。
注册令牌 用于标识每个客户端应用实例的唯一令牌字符串。 单一设备消息传递和设备组消息传递需要注册令牌。请注意,注册令牌必须保密。
发送者 ID 您在创建 Firebase 项目时系统创建的唯一数字值,可在 Firebase 控制台设置窗格的 Cloud Messaging 标签页找到。发送者 ID 用于标识可以向客户端应用发送消息的每个发送者。

访问令牌 一个只在短时间内有效的 OAuth 2.0 令牌,用于对发送到 HTTP v1 API 的请求进行授权。此令牌与属于您的 Firebase 项目的服务帐号相关联。如需创建和轮替访问令牌,请按照向发送请求提供授权中所述的步骤操作。
服务器密钥(用于旧版协议) 用于授权您的应用服务器访问 Google 服务(包括通过 Firebase Cloud Messaging 传递旧版协议发送消息)的服务器密钥。您在创建 Firebase 项目时获取服务器密钥。您可以在 Firebase 控制台设置窗格的 Cloud Messaging 标签页查看此密钥。重要提示:切勿在客户端代码中的任何位置包含服务器密钥。另外,请确保在为您的应用服务器授权时仅使用服务器密钥。Android 密钥、iOS 密钥和浏览器密钥会被 FCM 拒绝。

0x02 Android接入

前提是已经在项目中集成了Firebase,在build.gradle中添加FCM依赖

//兼容firebase版本
implementation platform('com.google.firebase:firebase-bom:26.8.0')

implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-analytics'

FCM 客户端需要在 Android 4.1 或更高版本且安装了 Google Play 商店应用的设备上运行,或者在 Android 4.1 版本且支持 Google API 的模拟器中运行。请注意,除了使用 Google Play 商店,您还可以通过其他方式部署您的 Android 应用。
注意:为了获得最佳 FCM 体验,我们强烈建议您在项目中启用 Google Analytics(分析)。FCM 的消息传送报告功能需要使用 Google Analytics(分析)。

在AndroidManifest.xml中添加
一项继承 FirebaseMessagingService 的服务。在后台应用中,如果除了接收通知外,您还希望进行任何消息处理,则必须添加此服务。如需在前台应用中接收通知、接收数据载荷以及发送上行消息等,您必须继承此服务。

<service
    android:name=".java.MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

(可选)应用组件中用于设置默认通知图标和颜色的元数据元素。如果传入的消息未明确设置图标和颜色,Android 就会使用这些值。

<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
     See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_ic_notification" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
     notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

(可选)从 Android 8.0(API 级别 26)和更高版本开始,我们支持并推荐使用通知渠道。FCM 提供具有基本设置的默认通知渠道。如果您希望创建和使用自己的默认渠道,请将 default_notification_channel_id 设置为您的通知渠道对象的 ID(如下所示);只要传入的消息未明确设置通知渠道,FCM 就会使用此值。如需了解详情,请参阅管理通知渠道。

<meta-data
    android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="@string/default_notification_channel_id" />

0x03 获取设备注册令牌(token)

初次启动您的应用时,FCM SDK 会为客户端应用实例生成一个注册令牌。如果您希望指定单一目标设备或者创建设备组,则需要通过扩展 FirebaseMessagingService 并重写 onNewToken 来获取此令牌。
本部分介绍如何检索令牌以及如何监控令牌的变更。因为令牌会在初始启动后轮替,所以我们强烈建议您检索最近更新的注册令牌。
注册令牌可能会在发生下列情况时更改:
应用在新设备上恢复
用户卸载/重新安装应用
用户清除应用数据。

获取当前令牌

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();

          // Log and toast
          String msg = getString(R.string.msg_token_fmt, token);
          Log.d(TAG, msg);
          Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
        }
    });

新建一个服务,在服务中继承FirebaseMessagingService 监控新令牌生成

/**
 * There are two scenarios when onNewToken is called:
 * 1) When a new token is generated on initial app startup
 * 2) Whenever an existing token is changed
 * Under #2, there are three scenarios when the existing token is changed:
 * A) App is restored to a new device
 * B) User uninstalls/reinstalls the app
 * C) User clears app data
 */
@Override
public void onNewToken(String token) {
    Log.d(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);
}

获取到令牌或者有新的令牌时,将该令牌发送到业务服务器,客户端自己也需要存储一份。
用户同样可以设置不自动生成令牌,在AndroidManifest.xml中通过配置这两项,禁用自动生成令牌。

<meta-data
    android:name="firebase_messaging_auto_init_enabled"
    android:value="false" />
<meta-data
    android:name="firebase_analytics_collection_enabled"
    android:value="false" />

启用自动初始化时候,执行下面方法即可
要重新启用 FCM 自动初始化功能,请执行运行时调用:

0x04 检查Google Play服务

依靠 Play 服务 SDK 运行的应用在访问 Google Play 服务功能之前,应始终检查设备是否拥有兼容的 Google Play 服务 APK。我们建议您在以下两个位置进行检查:主 Activity 的 onCreate() 方法和 onResume() 方法。onCreate() 中的检查可确保该应用在检查成功之前无法使用。onResume() 中的检查可确保当用户通过一些其他方式返回正在运行的应用(比如通过返回按钮)时,检查仍将继续进行。
如果设备没有兼容的 Google Play 服务版本,您的应用可以调用 GoogleApiAvailability.makeGooglePlayServicesAvailable(),以便用户从 Play 商店下载 Google Play 服务。

0x05 控制台发送消息并处理

下面是完整的Android核心代码
MainActivity.java

package com.evenvi.international;

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

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.messaging.FirebaseMessaging;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "evenvi";
    private Boolean supportGooglePlay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //启动FCM消息处理服务


        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                supportGooglePlay = checkGooglePlay();
            }
        });

        Button btnFcmToken = findViewById(R.id.btn_fcm_token);
        btnFcmToken.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getFCMToken();
            }
        });

        Button btnFcmService = findViewById(R.id.btn_fcm_service);
        btnFcmService.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                startFCMService();
            }
        });
    }

    private void startFCMService(){
        startService(new Intent(getBaseContext(), MyFirebaseMessagingService .class));
    }

    /**
     * 是否支持google play
     * @return
     */
    private boolean checkGooglePlay(){
        GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
        int gpRet = googleApiAvailability.isGooglePlayServicesAvailable(this);

        if (gpRet == ConnectionResult.SUCCESS){
            Log.d(TAG, "support google play");
            Toast.makeText(MainActivity.this, "support", Toast.LENGTH_SHORT).show();
            return true;
        }else{
            Log.e(TAG, "not support google play");
            Toast.makeText(MainActivity.this, "not support", Toast.LENGTH_SHORT).show();
            return false;
        }
    }

    private void getFCMToken(){
        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());
                            Toast.makeText(MainActivity.this, "get fcm token failed", Toast.LENGTH_SHORT).show();
                            return;
                        }

                        String token = task.getResult();

                        String msg = getString(R.string.msg_token_fmt, token);
                        Log.d(TAG, msg);
                        Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
                    }
                });
    }
}

MyFirebaseMessagingService.java

package com.evenvi.international;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.NonNull;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    private static final String TAG = "evenvi-FMS";

    public MyFirebaseMessagingService() {
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "Start  FCM Success");
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
//        super.onMessageReceived(remoteMessage);
        Log.d(TAG, "Receive Message From: " + remoteMessage.getFrom());
        Log.d(TAG, "Message Content: " + remoteMessage.getData());

    }

    @Override
    public void onNewToken(@NonNull String token) {
//        super.onNewToken(s);
        Log.d(TAG, "Refresh token:" + token);

    }

    private void sendRegistrationToServer(String token) {
        // TODO: Implement this method to send token to your app server.
        Log.d(TAG, "Sending token:" + token + " to server...");
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.evenvi.international">


    <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.FirebaseDemo">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="48dp"
        android:text="检测Google Play"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_fcm_token"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="28dp"
        android:text="获取FCM Token"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <Button
        android:id="@+id/btn_fcm_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="72dp"
        android:text="开启消息监听服务"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_fcm_token" />

</androidx.constraintlayout.widget.ConstraintLayout>

打开控制台,拿到手动创建发送消息并在客户端查看是否发送成功。

Tags: android, firebase

Related Posts:

Leave a Comment