即使应用程序进程已死,如何确保通知 Firebase 消息传递示例实现? [英] How to ensure that the Firebase Messaging sample implementation is notified even if app process is dead?

查看:12
本文介绍了即使应用程序进程已死,如何确保通知 Firebase 消息传递示例实现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我构建并运行 Firebase 消息传递示例应用程序时(在从我的 Firebase 控制台获取并安装自定义 google-services.json 文件后,如 Android Studio 中的向导所述),我可以发送一条简单的非通知消息用我的测试脚本来处理它,它可以工作.

...但仅当应用程序已在设备上启动时.

如果我未能先启动应用程序,或在启动后强制停止应用程序(从设置 | 应用程序),则消息似乎无法通过.(我这么说是因为我不再看到我的 onMessageReceived 方法的任何日志记录输出).

有些人报告说,当消息发出时,即使他们的应用程序没有首先启动,他们的应用程序也会唤醒——这太棒了!正是我想要的!

但我无法弄清楚他们正在做什么来实现这一目标.

我错过了什么?我应该如何更改此代码以确保即使在应用程序尚未启动或应用程序被强行停止后也能收到消息?

注意:我正在运行的代码(如下)非常基于 Firebase Quickstarts for Android 代码,该代码可以从 Android Studio 中下载和构建(文件 | 新建 | 导入示例......然后找到FirebaseAndroid 的快速入门"在它填充的列表中).(我相信 Google 在 GitHub 上托管了相同的代码:github.com/firebase/quickstart-android/tree/master/messaging‌ ).我稍微修改了日志输出和注释.

AndroidManifest.xml

MyFirebaseInstanceIDService.java

公共类 MyFirebaseInstanceIDService 扩展 FirebaseInstanceIdService {私有静态最终字符串 TAG = "MyFirebaseIIDService";/*** 如果 InstanceID 令牌已更新,则调用.如果安全性可能会发生这种情况* 之前的令牌已被泄露.请注意,这是在 InstanceID 令牌时调用的* 最初是生成的,因此您可以在此处检索令牌.*///[开始刷新令牌]@覆盖公共无效 onTokenRefresh() {//获取更新的 InstanceID 令牌.字符串 refreshedToken = FirebaseInstanceId.getInstance().getToken();Log.d(TAG, "刷新令牌:" + refreshedToken);//如果要向此应用程序实例发送消息或//在服务器端管理此应用订阅,发送//实例 ID 令牌到您的应用服务器.sendRegistrationToServer(refreshedToken);}//[结束刷新令牌]/*** 将令牌持久保存到第三方服务器.** 修改此方法以将用户的 FCM InstanceID 令牌与任何服务器端帐户关联* 由您的应用程序维护.** @param token 新的令牌.*/私人无效 sendRegistrationToServer(字符串令牌){//TODO: 实现此方法以将令牌发送到您的应用服务器.}}

MyFirebaseMessagingService.java

公共类 MyFirebaseMessagingService 扩展 FirebaseMessagingService {私有静态最终字符串 TAG = "MyFirebaseMsgService";/*** 收到消息时调用.** @param remoteMessage 表示从 Firebase 云消息传递接收到的消息的对象.*///[开始接收消息]@覆盖公共无效 onMessageReceived(RemoteMessage remoteMessage) {//[START_EXCLUDE]//消息有数据消息和通知消息两种.处理数据消息//这里在 onMessageReceived 中应用是在前台还是后台.数据消息是类型//传统上与 GCM 一起使用.通知消息仅在应用程序时在 onMessageReceived 中接收//在前台.当应用程序在后台时,会显示一个自动生成的通知.//当用户点击通知时,他们将返回到应用程序.包含两个通知的消息//并且数据有效负载被视为通知消息.Firebase 控制台始终发送通知//消息.有关更多信息,请参阅:https://firebase.google.com/docs/cloud-messaging/concept-options//[END_EXCLUDE]//TODO(developer):在此处处理 FCM 消息.Log.d(TAG, "onMessageReceived: From: " + remoteMessage.getFrom());//检查消息是否包含数据负载.if (remoteMessage.getData().size() > 0) {Log.d(TAG, "onMessageReceived: 消息数据负载:" + remoteMessage.getData());if (/* 检查数据是否需要被长时间运行的作业处理 */true) {//对于长时间运行的任务(10 秒或更长时间),请使用 Firebase Job Dispatcher.调度作业();} 别的 {//10秒内处理消息处理现在();}}//检查消息是否包含通知负载.if (remoteMessage.getNotification() != null) {Log.d(TAG, "onMessageReceived: 消息通知正文:" + remoteMessage.getNotification().getBody());}//此外,如果您打算根据收到的 FCM 生成自己的通知//消息,这里是应该启动的地方.请参阅下面的 sendNotification 方法.}//[结束接收消息]/*** 使用 FirebaseJobDispatcher 安排作业.*/私人无效 scheduleJob() {//[开始调度作业]FirebaseJobDispatcher 调度程序 = new FirebaseJobDispatcher(new GooglePlayDriver(this));工作 myJob = dispatcher.newJobBuilder().setService(MyJobService.class).setTag("我的工作标签").建造();dispatcher.schedule(myJob);//[结束 dispatch_job]}/*** 处理分配给广播接收器的时间.*/私人无效句柄(){Log.d(TAG, "短期任务已完成.");}/*** 创建并显示包含收到的 FCM 消息的简单通知.** @param messageBody 收到 FCM 消息正文.*/私人无效发送通知(字符串消息正文){Intent 意图 = new Intent(this, MainActivity.class);意图.addFlags(意图.FLAG_ACTIVITY_CLEAR_TOP);PendingIntent pendingIntent = PendingIntent.getActivity(this, 0/* 请求代码 */, intent,PendingIntent.FLAG_ONE_SHOT);Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_stat_ic_notification).setContentTitle("FCM 消息").setContentText(messageBody).setAutoCancel(真).setSound(defaultSoundUri).setContentIntent(pendingIntent);通知管理器通知管理器 =(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);notificationManager.notify(0/* 通知 ID */, notificationBuilder.build());}}

bash 测试脚本

curl -X POST --Header "Authorization: key=<来自 Firebase 控制台的服务器密钥>" --标题内容类型:应用程序/json"https://fcm.googleapis.com/fcm/send -d"{ \"to":"",\"优先级": "高" }"回声

解决方案

您观察到的行为是应用程序处于停止状态"的结果.此行为是在 Android 3.1 中引入的,在该部分中描述在已停止的应用程序上启动控件:

<块引用>

应用程序在首次安装时处于停止状态,但尚未启动,当它们被用户手动停止时(在管理应用程序中)

当应用处于停止状态时,系统不会向其传递广播意图,这意味着它不会收到 Firebase 消息.据我所知,你无法绕过这个;用户必须第一次启动应用程序.这告诉系统用户希望应用能够正常运行,并且可以安全地向其传递广播意图.

这里有一些与停止状态相关的SO问题/答案.p>

When I build and run the Firebase Messaging Sample app (after obtaining and installing a custom google-services.json file from my Firebase Console, as described by the wizard within Android Studio), I can send a simple non-notification message to it with my test script, and it works.

... but only if the app has been started on the device.

If I fail to first start the app, or force-stop the app (from Settings | Apps) after it's been started, the message doesn't quite seem to get through. (I say that because I no longer see any logging output from my onMessageReceived method).

Some have reported that when a message is sent out, their app wakes up even if their app has not first been started -- which is great! Exactly what I want!

But I haven't been able to figure out what they're doing to make that happen.

What am I missing? How should I change this code to make sure that it receives the message even when the app has not been started, or after the app has been forcibly stopped?

Note: the code I'm running (below) is based very closely upon the Firebase Quickstarts for Android code which can be downloaded and built from within Android Studio (File | New | Import Sample ... and then find "Firebase Quickstarts for Android" within the list it populates). (I believe that Google hosts that same code on GitHub here: github.com/firebase/quickstart-android/tree/master/messaging‌​). I modified the logging output and comments slightly.

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.firebase.quickstart.fcm">

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    <!-- [START fcm_default_icon] -->
    <!-- Set custom default icon. This is used when no icon is set for incoming notification messages. -->
    <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. -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_color"
        android:resource="@color/colorAccent" />
    <!-- [END fcm_default_icon] -->
    <activity
        android:name="com.google.firebase.quickstart.fcm.MainActivity"
        android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>

    <!-- [START firebase_service] -->
    <service
        android:name=".MyFirebaseMessagingService">
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT"/>
        </intent-filter>
    </service>
    <!-- [END firebase_service] -->
    <!-- [START firebase_iid_service] -->
    <service
        android:name=".MyFirebaseInstanceIDService">
        <intent-filter>
            <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
        </intent-filter>
    </service>
    <!-- [END firebase_iid_service] -->
    <service android:name=".MyJobService"
             android:exported="false">
        <intent-filter>
            <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
        </intent-filter>
    </service>
</application>

MyFirebaseInstanceIDService.java

public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {

    private static final String TAG = "MyFirebaseIIDService";

    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the InstanceID token
     * is initially generated so this is where you would retrieve the token.
     */
    // [START refresh_token]
    @Override
    public void onTokenRefresh() {
        // Get updated InstanceID token.
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        Log.d(TAG, "Refreshed token: " + refreshedToken);

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

    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM InstanceID token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private void sendRegistrationToServer(String token) {
        // TODO: Implement this method to send token to your app server.
    }
}

MyFirebaseMessagingService.java

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    private static final String TAG = "MyFirebaseMsgService";

    /**
     * Called when message is received.
     *
     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
     */
    // [START receive_message]
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // [START_EXCLUDE]
        // There are two types of messages data messages and notification messages. Data messages are handled
        // here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
        // traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
        // is in the foreground. When the app is in the background an automatically generated notification is displayed.
        // When the user taps on the notification they are returned to the app. Messages containing both notification
        // and data payloads are treated as notification messages. The Firebase console always sends notification
        // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
        // [END_EXCLUDE]

        // TODO(developer): Handle FCM messages here.
        Log.d(TAG, "onMessageReceived: From: " + remoteMessage.getFrom());

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

            if (/* Check if data needs to be processed by long running job */ true) {
                // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher.
                scheduleJob();
            } else {
                // Handle message within 10 seconds
                handleNow();
            }

        }

        // Check if message contains a notification payload.
        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, "onMessageReceived: Message Notification Body: " + remoteMessage.getNotification().getBody());
        }

        // Also if you intend on generating your own notifications as a result of a received FCM
        // message, here is where that should be initiated. See sendNotification method below.
    }
    // [END receive_message]

    /**
     * Schedule a job using FirebaseJobDispatcher.
     */
    private void scheduleJob() {
        // [START dispatch_job]
        FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this));
        Job myJob = dispatcher.newJobBuilder()
                .setService(MyJobService.class)
                .setTag("my-job-tag")
                .build();
        dispatcher.schedule(myJob);
        // [END dispatch_job]
    }

    /**
     * Handle time allotted to BroadcastReceivers.
     */
    private void handleNow() {
        Log.d(TAG, "Short lived task is done.");
    }

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     * @param messageBody FCM message body received.
     */
    private void sendNotification(String messageBody) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_stat_ic_notification)
                .setContentTitle("FCM Message")
                .setContentText(messageBody)
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

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

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

bash test script

curl -X POST 
--Header "Authorization: key=<server key from Firebase Console>" 
--Header "Content-Type: application/json" 
https://fcm.googleapis.com/fcm/send  
-d " 
    { 
        "to":"<token returned by FirebaseInstanceId.getInstance().getToken()>", 
        "priority": "high" 
    }"
echo 

解决方案

The behavior you are observing is the result of the app being in the "Stopped State". This behavior was introduced in Android 3.1 and is described here in the section Launch controls on stopped applications:

Applications are in a stopped state when they are first installed but are not yet launched and when they are manually stopped by the user (in Manage Applications)

When an app is in Stopped state, the system will not deliver Broadcast intents to it, which means it will not receive Firebase messages. As far as I know, you can't get around this; the user must start the app for the first time. This tells the system that the user wants the app to be operational and it is safe to deliver Broadcast intents to it.

Here are some SO questions/answers related to Stopped State.

这篇关于即使应用程序进程已死,如何确保通知 Firebase 消息传递示例实现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆