Firebase云消息传递-处理注销


73

当用户注销我的应用程序并且我不再希望他接收到设备的通知时,我该如何处理情况。

我试过了

FirebaseInstanceId.getInstance().deleteToken(FirebaseInstanceId.getInstance().getId(), FirebaseMessaging.INSTANCE_ID_SCOPE)

但是我仍然收到设备的通知registration_id

我还确保这是我应该删除的令牌:

FirebaseInstanceId.getInstance().getToken(FirebaseInstanceId.getInstance().getId(), FirebaseMessaging.INSTANCE_ID_SCOPE)

或简单地FirebaseInstanceId.getInstance().getToken())。

我也尝试过FirebaseInstanceId.getInstance().deleteInstanceId(),但是下次我打电话给FirebaseInstanceId.getInstance.getToken我时会收到null(它在第二次尝试中有效)。

我想,在deleteInstanceId我可以立即getToken()再次致电之后,但这看起来像是骇客。还有一个答案,指出不应该这样做,但它建议删除显然无效的令牌。

那么正确的方法是什么呢?


1
在深入研究实现这些解决方案之一之前,请务必先查看Dan Alboteanu的答案。TL; DR大部分应在服务器端而不是客户端处理。
tir38 '19

Answers:


67

好的。因此,我设法进行了一些测试,并得出以下结论:

  1. deleteToken()是的对应物getToken(String, String),但不是getToken()

仅当您传递的发件人ID是其他发件人ID(与您在google-services.json中可以看到的ID不相同)不同时,它才有效。例如,您要允许其他服务器发送到您的应用程序,您打电话getToken("THEIR_SENDER_ID", "FCM")给他们授权以发送到您的应用程序。这将返回仅与该特定发件人相对应的不同注册令牌。

将来,如果您选择删除其授权以发送到您的应用,则必须使用deleteToken("THEIR_SENDER_ID", "FCM")。这将使相应的令牌无效,并且当发件人尝试发送消息时,作为预期的行为,他们将收到NotRegistered错误。

  1. 为了删除您自己的发件人的令牌,请使用正确的处理方法deleteInstanceId()

特别提及@Prince给出的答案,特别是帮助我解决此问题的代码示例。

正如@MichałK在其帖子中已经做过的那样,在调用之后deleteInstanceId()getToken()应调用,以便发送新令牌的请求。但是,您不必第二次调用它。只要实施,它应该会自动触发为您提供新令牌。onTokenRefresh() onNewToken()

对于短,deleteInstanceId()> getToken()>检查。onTokenRefresh() onNewToken()

注意:调用deleteInstanceId()不仅会删除您自己的应用程序的令牌。它将删除所有主题订阅以及与该应用程序实例关联的所有其他令牌。


您确定自己打的deleteToken()正确吗?受众群体的值(也可以从您链接的我的回答中看到)“设置为应用服务器的发送者ID”。您传递的getId()值与发件人ID不同(它包含应用程序实例ID值)。另外,您如何发送消息(App Server或Notifications Console)?

getToken()getToken(String, String)返回不同的令牌。在这里查看我的答案。

我也尝试过FirebaseInstanceId.getInstance().deleteInstanceId(),但是下次我打电话给FirebaseInstanceId.getInstance.getToken我时会收到null(它在第二次尝试中有效)。

可能是因为您第一次调用时getToken(),它仍在生成。这只是预期的行为。

我想,在deleteInstanceId我可以立即getToken()再次致电之后,但这看起来像是骇客。

并不是的。这是获取新生成的(如果已经生成的)令牌的方式。所以我认为很好。


这是我调用getToken或deleteToken时唯一没有引发错误的“发件人ID”。当我从Firebase控制台使用文本项目ID时,两种方法都抛出了。然后,我使用了在googleservices.json中找到的数字ID,它看起来可以正常工作。然后传递了getId(),它也没有抛出。所以我想就是这样。
米哈尔ķ

至于hack,我必须在deleteInstanceId之后立即调用它,以便它第一次返回null,然后在登录期间对其进行调用以使其起作用。这就是为什么我认为这是黑客。
米哈尔ķ

我将尝试看看以后是否可以做一些测试并复制行为。如果有时间,我会回到这里。干杯!
AL。

2
感谢您检查!老实说,让我感到惊讶的是该API的记录如此糟糕和糟糕。
米哈尔ķ

2
有没有办法停止离线时监听FCM,因为在这种情况下deleteInstanceId将返回SERVICE_NOT_AVAILABLE_ERROR,并且您仍将注册
desgraci

30

我进行了简短的研究,以了解哪种最优雅的解决方案可以像以前一样恢复完全控制权(订阅和取消订阅FCM)。用户登录或注销后启用和禁用FCM。

步骤1.-防止自动初始化

Firebase现在处理InstanceID以及需要生成注册令牌的其他所有内容。首先,您需要防止自动初始化。根据官方设置文档,您需要将以下元数据值添加到您的AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<application>

  <!-- FCM: Disable auto-init -->
  <meta-data android:name="firebase_messaging_auto_init_enabled"
             android:value="false" />
  <meta-data android:name="firebase_analytics_collection_enabled"
             android:value="false" />

  <!-- FCM: Receive token and messages -->
  <service android:name=".FCMService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
  </service>

</application>

现在,您禁用了自动令牌请求过程。同时,您可以选择在运行时通过代码再次启用它。

步骤2-实现enableFCM()disableFCM()功能

如果再次启用自动初始化,则会立即收到一个新令牌,因此这是实现该enableFCM()方法的理想方法。所有订阅信息都分配给InstanceID,因此,在删除它后,便开始取消订阅所有主题。这样,您就可以实现disableFCM()方法,只需在删除它之前关闭自动初始化即可。

public class FCMHandler {

    public void enableFCM(){
        // Enable FCM via enable Auto-init service which generate new token and receive in FCMService
        FirebaseMessaging.getInstance().setAutoInitEnabled(true);
    }

    public void disableFCM(){
        // Disable auto init
        FirebaseMessaging.getInstance().setAutoInitEnabled(false);
        new Thread(() -> {
            try {
                // Remove InstanceID initiate to unsubscribe all topic
                // TODO: May be a better way to use FirebaseMessaging.getInstance().unsubscribeFromTopic()
                FirebaseInstanceId.getInstance().deleteInstanceId();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

}

步骤3.-FCMService实现-令牌和消息接收

在最后一步中,您需要接收新令牌并直接发送到您的服务器。另一方面,您会收到数据消息,然后随心所欲。

public class FCMService extends FirebaseMessagingService {

    @Override
    public void onNewToken(String token) {
        super.onNewToken(token);
        // TODO: send your new token to the server
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        String from = remoteMessage.getFrom();
        Map data = remoteMessage.getData();
        if (data != null) {
            // TODO: handle your message and data
            sendMessageNotification(message, messageId);
        }
    }

    private void sendMessageNotification(String msg, long messageId) {
        // TODO: show notification using NotificationCompat
    }
}

我认为这种解决方案是清晰,简单和透明的。我在生产环境中进行了测试,并且可以正常工作。希望对您有所帮助。


您好Janos,如果我没有通过调用“ FirebaseMessaging.getInstance()。setAutoInitEnabled(true);”来启用自动初始化,有什么影响?应用程序是否会收到“ onNewToken”回调?
嘿,您好'18

哦..对不起我迟来的答复。是的,再次启用它后,您可以通过调用onNewToken()在FCMService中获得一个新令牌。
亚诺什Sicz-Mesziár

1
我认为这是一个很好的实现。还应考虑清理不再有效的令牌(在服务器端)-当它收到时-error.code ==='messaging / invalid-registration-token'|| error.code ==='消息/注册令牌未注册'。检查本程式码实验室例如github.com/firebase/friendlychat-web/blob/master/...
丹Alboteanu

因此,请观察auth状态和if(user == null){disableFCM()...
Dan Alboteanu

19

logout()从应用程序完成操作后,我正在处理同一个问题。但是问题是,注销后,我仍然从Firebase收到推送通知。我试图删除Firebase令牌。但是在我的logout()方法中删除标记后,就是null在我的login()方法中查询它的时候。经过2天的工作,我终于找到了解决方案。

  1. 在您的logout()方法中,在后台删除Firebase令牌,因为您无法从主线程中删除Firebase令牌

    new AsyncTask<Void,Void,Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            try
            {
                FirebaseInstanceId.getInstance().deleteInstanceId();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        protected void onPostExecute(Void result) {
            // Call your Activity where you want to land after log out
        }
    }.execute();
    
  2. 在您的login()方法中,再次生成Firebase令牌。

    new AsyncTask<Void,Void,Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            String token = FirebaseInstanceId.getInstance().getToken();
            // Used to get firebase token until its null so it will save you from null pointer exeption
            while(token == null) {
                token = FirebaseInstanceId.getInstance().getToken();
            }
            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
        }
    }.execute();
    

1
while(token == null){忙循环}确实对您的电池不利...最好是注册一个回调。另一个(不好,但更好)是在等待令牌时进入Thread.sleep。
阿米尔·乌瓦尔

@AmirUval,谢谢您的建议,我一定会尝试一下,并让您知道
Sunil

9

由于以下原因,开发人员永远不应注销客户端应用程序作为注销或在用户之间切换的机制:

  • 注册令牌与特定的登录用户无关。如果客户端应用取消注册然后重新注册,则该应用可以接收相同的注册令牌或不同的注册令牌。
  • 注销和重新注册可能最多需要五分钟才能传播。在此期间,由于未注册状态,消息可能会被拒绝,并且消息可能发送给错误的用户。为确保将消息发送给目标用户,请执行以下操作:

  • 应用服务器可以维护当前用户和注册令牌之间的映射。

  • 然后,客户端应用程序可以检查以确保收到的消息与登录用户匹配。

此报价来自已弃用的Google文档

但是有理由相信这仍然是正确的-即使上述文档已弃用。

您可以在此处进行观察-在此代码实验室中查看他们的操作方式https://github.com/firebase/functions-samples/blob/master/fcm-notifications/functions/index.js

和这里 https://github.com/firebase/friendlychat-web/blob/master/cloud-functions/public/scripts/main.js


1
您已链接并引用了已弃用的GCM文档。我找不到FCM的类似信息。
米哈尔ķ

1
我有理由相信FCM也可以使用此功能。在此代码实验室codelabs.developers.google.com/codelabs/…中了解他们的操作方式。该代码实验室已更新和维护。在那里他们从不deleteInstanceId()。相反,他们使用令牌-authStateObserver(user)github.com/firebase/friendlychat-web/blob/master/中的userID(uid)对更新服务器(Firestore)– 还清理了在服务器上不再有效的令牌
Dan Alboteanu

@DanAlboteanu,但您链接的代码示例未显示用户注销时发生的情况。从我所看到的信息来看,服务器没有更新...
enyo

现在链接已死
Vadim Kotov

8

由于getToken()弃用,请改用getInstanceId()重新生成新令牌。具有相同的效果。

public static void resetInstanceId() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                FirebaseInstanceId.getInstance().deleteInstanceId();
                FirebaseInstanceId.getInstance().getInstanceId();   
                Helper.log(TAG, "InstanceId removed and regenerated.");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

2

我知道我参加晚会很晚。deleteInstanceId()应该从后台线程调用,因为它是阻塞调用。只是检查方法deleteInstanceId()FirebaseInstanceId()类。

@WorkerThread
public void deleteInstanceId() throws IOException {
    if (Looper.getMainLooper() == Looper.myLooper()) {
        throw new IOException("MAIN_THREAD");
    } else {
        String var1 = zzh();
        this.zza(this.zzal.deleteInstanceId(var1));
        this.zzl();
    }
}  

您可以启动IntentService来删除实例ID和与其关联的数据。


这是真实的,但这里无关紧要(还有其他答案包裹在其中的AsyncTask基本相同)
米哈尔ķ


0

要将其全部包装起来,请使用后台线程删除instanceID,下次登录时,请密切注意Firestore / Realtime数据库(如果将令牌保存在此处),它们将刷新

public void Logout() {

        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    FirebaseInstanceId.getInstance().deleteInstanceId();
                    FirebaseInstanceId.getInstance().getInstanceId();
                } catch (final IOException e) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(Flags.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }.start();
        FirebaseMessaging.getInstance().setAutoInitEnabled(false);
        FirebaseAuth.getInstance().signOut();
        SharedPreferences sharedPreferences = getDefaultSharedPreferences(Flags.this);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.clear();
        editor.apply();
        startActivity(new Intent(Flags.this, MainActivity.class));
        Flags.this.finish();
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.