在前台收到 Firebase 通知后打开活动

13
当我的应用程序处于打开状态且我收到通知时,我希望能够立即打开关联的活动,而无需用户点击通知。这个问题与此问题非常相似,但它是在后台打开应用程序,而我需要在前台打开应用程序。
根据Firebase文档
通知会在您的应用程序处于后台运行时传递。在这种情况下,通知将传递到设备的系统托盘中。默认情况下,用户点击通知会打开应用程序启动器。同时带有通知和数据有效载荷的消息可在后台和前台传递。在这种情况下,通知将传递到设备的系统托盘中,并且数据有效载荷将在启动器活动的意图的附加项中传递。
这是我的onMessageReceived实现:
@Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

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

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

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     * @param remoteMessage FCM message message received.
     */
    private void sendNotification(RemoteMessage remoteMessage) {
        Intent intent = new Intent(this, MyActivity.class);

        Map<String, String> hmap ;
        hmap = remoteMessage.getData();
        hmap.get("data_info");
        intent.putExtra("data_info", hmap.get("data_info"));
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);


        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

    }

我能够正确地收到通知,但只有在我点击系统托盘上的通知后,活动才会启动。

在前台时是否有一种方法可以启动活动而无需点击通知?

当处于前台时,类MyFirebaseMessagingService中继承了FirebaseMessagingService并且包含方法onMessageReceived(),该方法被正确调用,但是活动未能启动。 我也尝试使用标记FLAG_ACTIVITY_NEW_TASK,但没有成功。提前致谢。

9个回答

4
你可以在前台活动中注册广播接收器,并从你的 onReceiveMessage() 方法发送广播来实现这一点。
前台活动。
mReceiver = new BroadcastReceiver() {
 @Override
 public void onReceive(Context context, Intent intent) {
     Intent myNewActivity = new Intent(this, MyActivity.class);
     startActivity(myNewActivity);
   }
 };

mIntentFilter=new IntentFilter("OPEN_NEW_ACTIVITY");

@Override
protected void onResume() {
     super.onResume();
     registerReceiver(mReceiver, mIntentFilter);
}



@Override
protected void onPause() {
     if(mReceiver != null) 
            unregisterReceiver(mReceiver);
            mReceiver = null;
     }
     super.onPause();
   }

FirebaseNotificationReceiver

@Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

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

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

        Intent broadcast = new Intent();
        broadcast.setAction("OPEN_NEW_ACTIVITY);
        sendBroadcast(broadcast);
    }     
}

您可以添加一项检查来确定应用程序是否在前台,以选择发送通知还是发送广播。 查看应用程序是否在前台

前台活动代码应该放在哪里?是放在Myactivity.class(应该打开的活动)中还是放在另一个活动中? - bankode
在另一个活动中,用户应该在通知到达时使用其中一个。例如:用户正在使用应用程序并检查某些内容,您可以在MainActivity类中声明观察者,但请注意,如果用户离开该活动,则不会捕获广播通知。 - cdiazmo
这是错误的,不可靠。运行时注册的接收器仅在应用程序运行时才有效。更不用说那个非静态内部类中的内存泄漏会阻止Activity被垃圾回收了。最后,无论在发布时它能否正常工作,在Android 10或更高版本上都无法正常工作 - Ryan M

1
你需要获取设备中当前前台应用程序的信息。基于此,您可以决定是否启动活动或发送通知。
为了做到这一点,我建议像这样做:
public void onMessageReceived(RemoteMessage remoteMessage) {
    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());
    }

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

private void handleNotification(RemoteMessage remoteMessage){
    Intent intent = new Intent(this, MyActivity.class);
    Map<String, String> hmap ;
    hmap = remoteMessage.getData();
    hmap.get("data_info");

    intent.putExtra("data_info", hmap.get("data_info"));
    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

    Context context = getApplicationContext();

    //CHECK IF THIS APP IS IN FOREGROUND
    ActivityManager am = (ActivityManager)
    AppService.this.getSystemService(ACTIVITY_SERVICE);

    // The first in the list of RunningTasks is always the foreground task.
    RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
    String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();

    if(foregroundTaskPackageName.equals(context.getPackageName()){
        //THIS STARTS MAINACTIVITY DIRECTLY IF THE FOREGROUND APP IS THIS APP
        startActivity(intent);
    }else{
        //IF THE FOREGROUND APP ISN'T THIS APP THEN SEND A PENDING INTENT TO OPEN MAIACTIVITY WHEN USER TAP ON NOTIFICATION
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
            PendingIntent.FLAG_ONE_SHOT);
        //CREATE A NOTIFICATION IN THE SYSTEM TRAY
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setContentTitle("TITLE")
                .setContentText("SUBMESSAGE")
                .setPriority(Notification.PRIORITY_MAX)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

        // notificationId is a unique int for each notification that you must define
        notificationManager.notify(notificationId, mBuilder.build());
    }
}

你需要设置通知的ID、标题和子消息,这些将会在通知中显示。
你还需要在应用清单文件中添加<uses-permission android:name="android.permission.GET_TASKS" />权限。

1
如果我在所有情况下都使用PendingIntent,它会打开一个活动。为什么要区分前台和后台呢? - CoolMind

1
我通过在pendingIntent上调用send()来实现这一点:
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);
try {
       pendingIntent.send();
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
    }

感谢PendingIntent.FLAG_ONE_SHOT!如果我们从最近使用的应用程序中打开一个应用程序,它将以空的“extras”打开,而使用FLAG_UPDATE_CURRENT每次都会使用我们在onMessageReceived中设置的相同“extras”打开。 - CoolMind

1

(kotlin) 如果您想检查应用程序是在前台还是后台,请在onMessageReceived中使用此代码

使用以下代码可以判断应用是否在前台或后台:

    var foreground = false

    try {
        foreground = ForegroundCheckTask().execute(this).get()
    } catch (e: InterruptedException) {
        e.printStackTrace()
    } catch (e: ExecutionException) {
        e.printStackTrace()
    }

最初的回答:然后使用“foreground”变量按您所需执行操作。
 if (foregroud) { //app in foreground
            intent = Intent(this, ChatAdminActivity::class.java)
            intent.putExtra("intent_backchat", 1)
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK)
            pendingIntent = PendingIntent.getActivity(this, Integer.valueOf(random) /* Request code */, intent, PendingIntent.FLAG_UPDATE_CURRENT)
            startActivity(intent)      // to directly open activity if app is foreground
        } else { //app in background
            intent = Intent(this, ChatAdminActivity::class.java)
            intent.putExtra("intent_backchat", 1)
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
            pendingIntent = PendingIntent.getActivity(this, Integer.valueOf(random) /* Request code */, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        }
.....

希望能帮到你..你可以查看我的完整FCMService代码。最初的回答请参考此处

如果我在所有情况下都使用PendingIntent,它会打开一个活动。为什么要区分前台和后台呢? - CoolMind
@fayerketto,既然应用程序已经在后台运行,为什么还需要PendingIntent呢? - Papps
@Papps,感谢您提出这个有趣的问题。我认为代码中存在错误。作者根据应用程序的状态在接收到推送通知时(在onMessageReceived中)创建pendingIntent,而不是当用户点击推送通知时。因此,当推送通知显示时,它并不重要,但是当用户点击时,它会根据之前的状态打开应用程序(例如,一小时前)。因此,我们不需要区分两种情况。 - CoolMind
1
@Papps,我认为在应用程序处于后台时需要使用PendingIntent来打开一个活动。 - CoolMind
ForegroundCheckTask 的代码应该在此回答中,而不是链接后面。另外,考虑到您正在调用它,然后立即在主线程上同步阻塞等待它完成,因此使用 AsyncTask 毫无意义。最后,您的“后台”情况没有任何作用。它实际上没有启动任何内容。它只是将一个变量设置为更新的 PendingIntent,然后返回。 - Ryan M

0
根据Google有关广播的页面

如果您不需要向应用程序外部的组件发送广播,则可以使用Support库中提供的LocalBroadcastManager发送和接收本地广播。 LocalBroadcastManager更加高效(无需进程间通信),并允许您避免考虑其他应用程序能够接收或发送您的广播所涉及的任何安全问题。 本地广播可用作应用程序中的通用发布/订阅事件总线,而无需考虑系统范围广播的任何开销。

因此,建议使用LocalBroadcastManager而不是BroadcastReceiver

在FirebaseMessagingService类中,您将从FCM(Firebase Cloud Messaging)接收通知并使用:

LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

向MainActivity发送消息:

public class AppFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        try {
            String messageFrom = remoteMessage.getFrom();
            String messageBody = (remoteMessage.getNotification() != null ? remoteMessage.getNotification().getBody() : null);
            Map<String, String> messageData = remoteMessage.getData();

            NotificationInfo notificationInfo = new NotificationInfo(messageData.get(message));
            notifyMainActivity(notificationInfo);
            showNotification(notificationInfo);
    }

    private void notifyMainActivity(NotificationInfo notificationInfo) {
        Intent intent = new Intent();
        intent.setAction(Constants.BROADCAST_MESSAGE_NOTIFICATION_RECEIVED);
        intent.putExtra(Constants.PARAM_NOTIFICATION_INFO, notificationInfo);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void showNotification(NotificationInfo notificationInfo) {

        Intent intentNotificationClicked = new Intent(this, MainActivity.class);
        intentNotificationClicked.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intentNotificationClicked.putExtra(Constants.PARAM_NOTIFICATION_INFO, notificationInfo);
        PendingIntent resultIntentNotificationClicked = PendingIntent.getActivity(this, 0, intentNotificationClicked, PendingIntent.FLAG_ONE_SHOT);

        String title = "MY APPLICATION";
        String message = notificationInfo.message;

        Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_notification_icon)
                .setContentTitle(title)
                .setContentText(message)
                .setAutoCancel(true)
                .setSound(notificationSoundURI)
                .setContentIntent(resultIntentNotificationClicked);

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

        int notificationID = Integer.parseInt(new SimpleDateFormat("HHmmssSSS").format(new Date()));
        notificationManager.notify(notificationID, mNotificationBuilder.build());
    }
}

在MainActivity中,您可以创建BroadcastReceiver来接收消息:
public class MainActivity extends AppCompatActivity {
    private NotificationBroadcastReceiver mNotificationBroadcastReceiver = null;
    private IntentFilter mIntentFilter = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        this.mNotificationBroadcastReceiver = new NotificationBroadcastReceiver(this);
        this.mIntentFilter = new IntentFilter(Constants.BROADCAST_MESSAGE_NOTIFICATION_RECEIVED);
    }

    @Override
    protected void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(this).registerReceiver(this.mNotificationBroadcastReceiver, this.mIntentFilter);
    }

    @Override
    protected void onPause() {
        if (this.mNotificationBroadcastReceiver != null) {
            unregisterReceiver(mNotificationBroadcastReceiver);
            this.mNotificationBroadcastReceiver = null;
        }

        super.onPause();
    }

    private class NotificationBroadcastReceiver extends BroadcastReceiver {

        WeakReference<MainActivity> mMainActivity;

        public NotificationBroadcastReceiver(MainActivity mainActivity) {
            this.mMainActivity = new WeakReference<>(mainActivity);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            MainActivity mainActivity = mMainActivity.get();
            if (mainActivity != null) {
                Bundle extras = intent.getExtras();
                if (extras != null && extras.containsKey(Constants.PARAM_NOTIFICATION_INFO)) {
                    NotificationInfo notificationInfo = (NotificationInfo) extras.getSerializable(Constants.PARAM_NOTIFICATION_INFO);
                    mainActivity.notificationReceived(notificationInfo);
                }
            }
        }
    }

    public void notificationReceived(@NonNull final NotificationInfo notificationInfo)
    {
        //handle the notification in MainActivity
    }
}

以下是代码中引用的类:

public class Constants {
    public static final String NOTIFICATION_BROADCAST_RECEIVER_MESSAGE_RECEIVED = "com.mycompany.myapp.NOTIFICATION_RECEIVED";
    public static final String PARAM_NOTIFICATION_INFO = "NotificationContent";
}

public class NotificationInfo implements Serializable {
    public String message;
    public NotificationInfo() { }
    public NotificationInfo(String message) { this.message = message; }
}

就是这样!


0
创建 BroadcastReceiver 是处理您的情况的最佳方法。但是你需要知道用户正在使用哪个活动。
在每个活动中创建 BroadcastReceiver 会显得很奇怪。因此创建一个扩展 Activity 的 BaseActivityBaseActivity 将具有 BroadcastReceiver 代码,并且所有其他活动都将扩展此 BaseActivity
open class BaseActivity : AppCompatActivity() {

private lateinit var broadcastReceiver: BroadcastReceiver

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(p0: Context?, p1: Intent?) {
            if (p1 != null) {
                handleBroadcastActions(p1)
            }
        }

    }
}

private fun handleBroadcastActions(intent: Intent) {
    when (intent.action) {
        Constants.ACTIVITY_STUFF -> {
            onHandleActivity()
        }
    }
}

protected open fun onHandleActivity() {
    startActivity(intentFor<YourActivity>())
}

override fun onResume() {
    super.onResume()
    registerReceiver(broadcastReceiver, IntentFilter(Constants.ACTIVITY_STUFF))

}

override fun onPause() {
    super.onPause()
    unregisterReceiver(broadcastReceiver)
}}

我已经添加了我的 Kotlin 代码。希望你能理解 :)
最后,你可以在 FirebaseMessagingService 的 onMessageReceived() 方法中调用这个 BroadcastReceiver。
override fun onMessageReceived(message: RemoteMessage) {
    sendBroadcast(Intent(Constants.ACTIVITY_STUFF))
}

这里的 Constants.ACTIVITY_STUFF 是什么? - Sid
@Sid 你可以使用任何常见的字符串。 例如: object Constants { const val ACTIVITY_STUFF = "处理推送通知" } - dpvmani
这是错误的,不可靠。运行时注册的接收器仅在应用程序运行时工作,并且更糟糕的是,当活动暂停时,您正在注销它,因此当应用程序在后台运行时,这绝对不会起作用。最后,即使修复了这个问题,从BroadcastReceiver启动Activity的方法也无法在Android 10或更高版本上工作。 - Ryan M

0
public class MyFirebaseMessagingService extends FirebaseMessagingService {
 private static final String TAG = "FCM Service";
 @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // TODO: Handle FCM messages here.
        // If the application is in the foreground handle both data and notification messages here.
        // Also if you intend on generating your own notifications as a result of a received FCM
        // message, here is where that should be initiated.
//     IntentFilter filter = new IntentFilter("OPEN_NEW_ACTIVITY");
//        registerReceiver(new BroadcastNotification(),filter);
//     showNotification(remoteMessage.getNotification().getBody());

        Log.d(TAG, "From: " + remoteMessage.getFrom());
        Log.d(TAG, "Notification Message Body: " + remoteMessage.getNotification().getBody());
     Log.e("Myname","shdjhfghgh");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent in= new Intent(this,MainActivity.class);
        in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(in);
    }
}

欢迎来到StackOverflow,请在您的答案中添加一些描述,并说明哪个部分解决了问题,以便其他人更容易理解。 - koceeng
这在Android 10或更高版本上无法工作。 - Ryan M

0
我通常做的是发送数据信息并从中创建意图。
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);
    Log.d(TAG, "onMessageReceived: called");

    Log.d(TAG, "onMessageReceived: Message received from: " + remoteMessage.getFrom());

    if (remoteMessage.getNotification() != null) {
        String title = remoteMessage.getNotification().getTitle();
        String body = remoteMessage.getNotification().getBody();
        Log.d(TAG, "onMessageReceived: "+title+" "+body);

        Notification notification = new NotificationCompat.Builder(this, FCM_CHANNEL_ID)
                .setSmallIcon(R.mipmap.ridersquare)
                        .setContentTitle(title)
                .setContentText(body)
                .setColor(Color.BLUE)
                .setSubText("KKKK")
                .build();

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(FCM_CHANNEL_ID, "Default channel", NotificationManager.IMPORTANCE_HIGH);
            manager.createNotificationChannel(channel);
        }
        manager.notify(1002, notification);
    }

    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "onMessageReceived: Data: " + remoteMessage.getData().toString());
        if (remoteMessage.getData().toString().length()>0){
            Notification notification = new NotificationCompat.Builder(this, FCM_CHANNEL_ID)
                    .setSmallIcon(R.mipmap.ridersquare)
                    .setContentTitle("New Order")
                    .setContentText("New order received from"+remoteMessage.getData().get("restaurant_name"))
                    .setSound(App.soundUri)
                    .setLights(Color.RED, 3000, 3000)
                    .setAutoCancel(true)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setVibrate(new long[]{0,1000, 500, 1000})
                    .build();

            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(FCM_CHANNEL_ID, "Default channel", NotificationManager.IMPORTANCE_HIGH);
                manager.createNotificationChannel(channel);
            }
            manager.notify(50, notification);
            Intent intent=new Intent(getApplicationContext(), NewOrderNoti.class);
            intent.putExtra("username",remoteMessage.getData().get("username"));
            startActivity(intent);

        }


    }

这基本上创建了一个通知并调用意图来打开新的活动


这在Android 10或更高版本上无法工作。 - Ryan M

-2
如果您只想设置您的通知,您可以更改此(客户端示例)。
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {

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

然后服务器发送类似这样的消息(示例),只有数据(不是通知)

var message = { //this may vary according to the message type (single recipient, multicast, topic, et cetera)
to: token,     
priority: 'high',
content_available: true,    
data: {  
    //you can send only notification or only data(or include both)
    message: 'news'
}};

参考文献)

FCM中有两种类型的消息:
- display-messages: 这些消息仅在您的应用程序位于前台时才触发onMessageReceived()回调
- data-messages: 即使您的应用程序在后台,这些消息也会触发onMessageReceived()回调

FCM中的两种消息类型


问题非常清楚,但你却表现得好像不理解!!!这是没有帮助的回答。 - ENSATE
这是与上述问题无关的答案。请确保了解问题之后再回答,我相信你会的。 - Andriya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接