当用户退出我的应用程序并且我不再希望他接收设备通知时,我该如何处理这种情况。
我试过
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
时我收到了空(它在第二次尝试时有效)。
我想,deleteInstanceId
之后我可以立即再次呼叫getToken()
,但它看起来像是黑客。还有this answer声明它不应该被完成,但它建议删除显然不起作用的令牌。
那么处理这个问题的正确方法是什么?
答案 0 :(得分:40)
好。所以我设法做了一些测试并得出以下结论:
deleteToken()
与getToken(String, String)
相对应,但不适用于getToken()
。仅当您传递的发件人ID是不同的发件人ID(与您在google-services.json中可以看到的ID不同)时,它才有效。例如,您希望允许其他服务器发送到您的应用,您可以致电getToken("THEIR_SENDER_ID", "FCM")
向他们发送授权以发送到您的应用。这将返回一个不同的注册令牌,该令牌仅对应于该特定发件人。
将来,如果您选择删除授权以发送到您的应用,则必须使用deleteToken("THEIR_SENDER_ID", "FCM")
。这将使相应的令牌无效,并且当发件人尝试发送消息时,作为预期的行为,他们将收到NotRegistered
错误。
deleteInstanceId()
。特别提到这个answer by @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)
会返回不同的令牌。请参阅我的回答here。
我也试过了
FirebaseInstanceId.getInstance().deleteInstanceId()
,但是下次我调用FirebaseInstanceId.getInstance.getToken
时我收到了空(它在第二次尝试时有效)。
这可能是因为你第一次打电话给getToken()
时,它仍在生成。这只是预期的行为。
我想,在
deleteInstanceId
之后,我可以立即再次致电getToken()
,但它看起来像是黑客。
不是真的。这是你如何获得新生成的(如果它已经生成)令牌。所以我认为没关系。
答案 1 :(得分:16)
当我从我的应用程序中完成logout()
时,我正在处理同样的问题。但问题是,在退出后,我仍然从Firebase获得推送通知。我尝试删除Firebase令牌。但是在我的logout()
方法中删除令牌后,当我在null
方法中查询令牌时,它是login()
。工作2天后,我终于找到了解决方案。
在您的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();
在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();
答案 2 :(得分:14)
我进行了简短的研究,以研究哪种最优雅的解决方案可以像以前一样恢复完全控制(订阅和取消订阅FCM)。用户登录或注销后启用和禁用FCM。
步骤1-防止自动初始化
Firebase现在可以处理InstanceID
以及需要生成注册令牌的所有其他内容。首先,您需要防止自动初始化。基于official set-up documentation,您需要将以下元数据值添加到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
}
}
我认为这种解决方案是清晰,简单和透明的。我在生产环境中进行了测试,并且可以正常工作。希望对您有所帮助。
答案 3 :(得分:3)
开发人员切勿取消将客户端应用注册为一种机制 注销或用于用户之间的切换,原因如下:
- 注册令牌与特定的登录用户无关。如果客户端应用取消注册然后重新注册,则该应用可以 收到相同的注册令牌或不同的注册令牌。
注销和重新注册可能最多需要五分钟才能传播。在此期间,由于 未注册状态,则消息可能会发送给错误的用户。为了 确保将消息发送给目标用户:
应用服务器可以维护当前用户和注册令牌之间的映射。
- 然后,客户端应用可以检查以确保其收到的消息与登录用户匹配。
答案 4 :(得分:3)
另一种使用 FirebaseMessaging.getInstance()
清除 firebase 令牌并重新生成新令牌的简便方法fun clearFirebaseToken() {
FirebaseMessaging.getInstance().apply {
deleteToken().addOnCompleteListener { it ->
Log.d("TAG++", "firebase token deleted ${it.result}")
token.addOnCompleteListener {
Log.d("TAG++", "firebase token generated ${it.result}")
if (it.result != null) saveTokenGenerated(it.result!!)
}
}
}
}
答案 5 :(得分:2)
我知道我参加晚会很晚。 deleteInstanceId()
应该从后台线程中调用,因为它是阻塞调用。只需检查FirebaseInstanceId()类中的方法deleteInstanceId()
。
@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和与其关联的数据。
答案 6 :(得分:2)
由于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();
}
答案 7 :(得分:1)
仅在注销后在后台线程上调用deleteToken方法:
addProductFromLocalStorage() {
if (this.productCarts === undefined || this.productCarts.length == 0) {
this.productCarts = this.fromLocalStorage;
}
}
第一个参数采用您在FireBaseConsole中定义的SenderID
更新需要几秒钟-之后,您将不再收到FCM通知。
答案 8 :(得分:1)
使用这种方法。 这是我的解决方案,我在 here 注册时,使用 initFirebaseMessage,。以及注销或删除时 使用 removeFirebaseMessage()。
private fun removeFirebaseMessage(){
CoroutineScope(Dispatchers.Default).launch {
FirebaseMessaging.getInstance().isAutoInitEnabled = false
FirebaseInstallations.getInstance().delete()
FirebaseMessaging.getInstance().deleteToken()
}
}
private fun initFirebaseMessage(){
val fcm = FirebaseMessaging.getInstance()
fcm.isAutoInitEnabled = true
fcm.subscribeToTopic("all")
fcm.subscribeToTopic("")
}
答案 9 :(得分:0)
要将其全部包装起来,请使用后台线程删除instanceID,下次登录时,请密切注意Firestore / Realtime DB(如果在其中保存令牌),它们将刷新
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();
}
答案 10 :(得分:0)
我使用下面的这段代码,它对我有帮助,我使用 Kotlin 协程而不是 Thread(Runnable{}).start() 因为它比创建新线程对象的成本更低
private fun logoutFromFCM() {
GlobalScope.launch(Dispatchers.IO) {
FirebaseInstallations.getInstance().delete()
FirebaseMessaging.getInstance().deleteToken()
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed", task.exception)
return@OnCompleteListener
}
// Get new FCM registration token
val token = task.result
saveFirebaseToken(token)
Log.w(TAG, "Token Updated - newToken> $token")
})
}
}
答案 11 :(得分:0)
包含 firebase.iid
的 FirebaseInstanceId
包是 now deprecated。自动初始化已从 Firebase 实例 ID 迁移到 Firebase 云消息传递。它的行为也略有改变。之前,如果启用了自动初始化,调用 deleteInstanceId()
会自动生成一个新令牌。现在,新令牌仅在下一次应用启动或显式调用 getToken()
时生成。
private suspend fun loginFCM() = withContext(Dispatchers.Default) {
val fcm = FirebaseMessaging.getInstance()
fcm.isAutoInitEnabled = true
fcm.token.await()
}
private suspend fun logoutFCM() = withContext(Dispatchers.Default) {
val fcm = FirebaseMessaging.getInstance()
fcm.isAutoInitEnabled = false // To prevent a new token to be generated automatically in the next app-start (remove if you don't care)
fcm.deleteToken().await()
}
如果您想从 Firebase 完全注销,您可以在之后删除整个安装:
private suspend fun logoutFirebase() = withContext(Dispatchers.Default) {
logoutFCM()
val firebase = FirebaseInstallations.getInstance()
firebase.delete().await()
}
答案 12 :(得分:0)
对于许多通知要求很简单的情况,处理注销的问题可以更容易地实现。例如,在我的例子中,每个用户只订阅了两个主题:
alerts
主题@
替换为 -
,因为主题字符串中不允许使用 @
)对于这种简单的场景,只需在注销时取消订阅不需要的主题:
Future<void> signOut() async {
messaging.unsubscribeFromTopic(emailToTopic(_firebaseAuth.currentUser.email));
await _firebaseAuth.signOut();
}
当然,只有在成功登录或注册后才能订阅主题:
Future<String> signIn({String email, String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
messaging.subscribeToTopic(emailToTopic(email));
return "Signed in";
} on FirebaseAuthException catch (e) {
return e.message;
}
}