Firebase云消息传递 - 处理注销

时间:2017-04-03 19:56:59

标签: android firebase firebase-cloud-messaging

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

我试过

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声明它不应该被完成,但它建议删除显然不起作用的令牌。

那么处理这个问题的正确方法是什么?

13 个答案:

答案 0 :(得分:40)

好。所以我设法做了一些测试并得出以下结论:

  1. deleteToken()getToken(String, String)相对应,但不适用于getToken()
  2. 仅当您传递的发件人ID是不同的发件人ID(与您在google-services.json中可以看到的ID不同)时,它才有效。例如,您希望允许其他服务器发送到您的应用,您可以致电getToken("THEIR_SENDER_ID", "FCM")向他们发送授权以发送到您的应用。这将返回一个不同的注册令牌,该令牌仅对应于该特定发件人。

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

    1. 要删除自己发件人的令牌,正确的处理方法是使用deleteInstanceId()
    2. 特别提到这个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天后,我终于找到了解决方案。

  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();
    

答案 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)

  

开发人员切勿取消将客户端应用注册为一种机制   注销或用于用户之间的切换,原因如下:

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

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

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

docs

答案 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方法:

https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceId.html#public-void-deletetoken-string-senderid,-string-scope

addProductFromLocalStorage() {

        if (this.productCarts === undefined || this.productCarts.length == 0) {
            this.productCarts  = this.fromLocalStorage;  
        } 
}

第一个参数采用您在FireBaseConsole中定义的SenderID

enter image description here

更新需要几秒钟-之后,您将不再收到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.iidFirebaseInstanceId 包是 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;
  }
}