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
扩展 CustomNotificationListener.class
- 将 onPushNotificationReceived() 替换为 Thunderbirds onMessageReceived()
第一个解决方案导致相同的错误,第二个解决方案根本没有任何消息。
如果有人有办法解决这个问题或知道可能是什么问题,如果您能写下答案,我将非常高兴:)
这是 类 的代码(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.onCreate
和 NotificationHub.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。
我正在尝试制作一个能够处理 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 扩展 CustomNotificationListener.class
- 将 onPushNotificationReceived() 替换为 Thunderbirds onMessageReceived()
第一个解决方案导致相同的错误,第二个解决方案根本没有任何消息。
如果有人有办法解决这个问题或知道可能是什么问题,如果您能写下答案,我将非常高兴:)
这是 类 的代码(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.onCreate
和 NotificationHub.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。