Android 的 Azure 通知中心:如何使用后台服务处理数据消息?

Azure Notification Hubs for Android: How do I handle Data-Messages with a Background-Service?

我正在尝试制作一个能够处理 Azure 通知中心发送的(数据)消息的应用程序。在当前状态下,它会在收到 Azure 的有效负载时发送通知。虽然该应用程序在前台 运行(或仍在快速面板中打开),但它完全没有问题,并且 onPushNotificationReceived() 可以很好地处理传入的消息,但是当从快速面板中删除该应用程序时,我尝试调用空对象引用时出错:


Logcat
2021-07-22 15:27:33.675 23017-23053/com.example.fcmtutorial1app E/AndroidRuntime: FATAL EXCEPTION: Firebase-Messaging-Intent-Handle
    Process: com.example.fcmtutorial1app, PID: 23017
    java.lang.NullPointerException: Attempt to invoke interface method 'void com.microsoft.windowsazure.messaging.notificationhubs.NotificationListener.onPushNotificationReceived(android.content.Context, com.google.firebase.messaging.RemoteMessage)' on a null object reference
        at com.microsoft.windowsazure.messaging.notificationhubs.FirebaseReceiver.onMessageReceived(FirebaseReceiver.java:52)
        at com.google.firebase.messaging.FirebaseMessagingService.dispatchMessage(com.google.firebase:firebase-messaging@@22.0.0:13)
        at com.google.firebase.messaging.FirebaseMessagingService.passMessageIntentToSdk(com.google.firebase:firebase-messaging@@22.0.0:8)
        at com.google.firebase.messaging.FirebaseMessagingService.handleMessageIntent(com.google.firebase:firebase-messaging@@22.0.0:3)
        at com.google.firebase.messaging.FirebaseMessagingService.handleIntent(com.google.firebase:firebase-messaging@@22.0.0:3)
        at com.google.firebase.messaging.EnhancedIntentService.lambda$processIntent[=12=]$EnhancedIntentService(com.google.firebase:firebase-messaging@@22.0.0:1)
        at com.google.firebase.messaging.EnhancedIntentService$$Lambda[=12=].run(Unknown Source:6)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at com.google.android.gms.common.util.concurrent.zza.run(Unknown Source:6)
        at java.lang.Thread.run(Thread.java:923)

这仅在发送数据消息时发生,因为 Firebase 服务会在不调用 onPushNotificationReceived().

的情况下处理带有通知负载的消息

我尝试了以下方法来解决这个问题:

第一个解决方案导致相同的错误,第二个解决方案根本没有任何消息。

如果有人有办法解决这个问题或知道可能是什么问题,如果您能写下答案,我将非常高兴:)

这是 类 的代码(android.app.Service 仍然包括在内,尽管它对我不起作用)。提前致谢!


MainActivity.class
package com.example.fcmtutorial1app;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.microsoft.windowsazure.messaging.notificationhubs.NotificationHub;

public class MainActivity extends AppCompatActivity
{
    public static final String CHANNEL_1_ID = "Channel1";
    public static final String CHANNEL_2_ID = "Channel2";

    public static String editTextTitle;
    public static String editTextMessage;

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

        createNotificationsChannels();

        NotificationHub.setListener(new CustomNotificationListener());
        NotificationHub.start(this.getApplication(), "spfcmtutorial1nhub", "Endpoint=sb://azurecloudmessaging.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=abc[...]xyz");
    }

    public static void sendCloudMessage(Context context)
    {
        editTextTitle = CustomNotificationListener.title;
        editTextMessage = CustomNotificationListener.body;

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_1_ID)
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentTitle(editTextTitle)
                .setContentText(editTextMessage)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setCategory(NotificationCompat.CATEGORY_MESSAGE);

        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(1, notificationBuilder.build());

        Log.v("MSG", "SENDCLOUDMESSAGE WAS ACTIVATED");
    }

    public void createNotificationsChannels() //Channel 2 is for tests only
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        {
            NotificationChannel channel1 = new NotificationChannel(
                    CHANNEL_1_ID,
                    "Channel 1",
                    NotificationManager.IMPORTANCE_HIGH
            );
            channel1.setDescription("This is Channel 1");

            NotificationChannel channel2 = new NotificationChannel(
                    CHANNEL_2_ID,
                    "Channel 2",
                    NotificationManager.IMPORTANCE_LOW
            );
            channel2.setDescription("This is Channel 2");

            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel1);
            manager.createNotificationChannel(channel2);
        }
    }
}

CustomNotificationListener.class
package com.example.fcmtutorial1app;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.firebase.messaging.RemoteMessage;
import com.microsoft.windowsazure.messaging.notificationhubs.NotificationListener;
import java.util.Map;

public class CustomNotificationListener extends Service implements NotificationListener
{
    private static final String TAG = "Message";

    public static String title;
    public static String body;
    public static String dataTitle;
    public static String dataBody;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        Log.d(TAG, "Service started");
        return Service.START_NOT_STICKY;
    }

    @Override
    public void onPushNotificationReceived(Context context, RemoteMessage message) //FATAL EXEPTION: Firebase-Messaging-Intent-Handle HERE
    {
        RemoteMessage.Notification notification = message.getNotification();

        try { title = notification.getTitle(); } catch(Exception e) {}
        try { body = notification.getBody(); } catch (Exception e) {}

        Map<String, String> data = message.getData();

        //region LOGGING
        if (message != null)
        {
            Log.d(TAG, "Message Notification Title: " + title);
            Log.d(TAG, "Message Notification Body: " + body);
        }
        else { Log.e(TAG, "ERROR, no message found"); }

        if (data != null)
        {
            for (Map.Entry<String, String> entry : data.entrySet())
            {
                Log.d(TAG, "key, " + entry.getKey() + "value " + entry.getValue());
            }
        }
        else { Log.e(TAG, "ERROR, no data found"); }
        //endregion

        Log.v("VERBOSE",  data.get("property1"));

        MainActivity.sendCloudMessage(context);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.FCMTutorial1App">
        <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=".CustomNotificationListener"></service>
    </application>

</manifest>

要回答这个问题,我们需要讨论一些 Android 生命周期概念,如 https://developer.android.com:

中所述

在您上面提供的代码中,您在应用程序的主入口点 MainActivity 中调用了 NotificationHub.setListener。如您所见,这在您的最终用户手动启动应用程序时有效,因为 MainActivity.onCreate 被调用。

但是,您的场景中还有第二个入口点:FirebaseMessagingService 在后台收到仅数据通知时启动应用程序。这是微妙且容易错过的 - 因为如果有效负载包含 notification 组件,当在系统托盘中单击通知时 Android 仍将路由到 MainActivity

在这种情况下 MainActivity 不涉及,因此 MainActivity.onCreateNotificationHub.setListener 永远不会在应用程序初始化时调用,并且 following line from the stack trace 遇到空引用:

mHub.getInstanceListener().onPushNotificationReceived(this.getApplicationContext(), remoteMessage);

要解决此问题,您需要在应用程序初始化时随时调用的某个地方调用 NotificationHub.setListener,无论入口点如何。

最自然的选择是通过扩展 android.app.Application 在应用程序级别设置 NotificationHub,覆盖 onCreate() method, and updating your manifest