我正在将我的应用从GCM迁移到FCM。
当新用户安装我的应用时,onTokenRefresh()
自动调用。问题在于用户尚未登录(没有用户ID)。
如何在用户登录后触发onTokenRefresh()
?
我正在将我的应用从GCM迁移到FCM。
当新用户安装我的应用时,onTokenRefresh()
自动调用。问题在于用户尚未登录(没有用户ID)。
如何在用户登录后触发onTokenRefresh()
?
onTokenRefresh()
方法将在生成新令牌时调用。应用程序安装后,它将立即生成(正如您已经发现的那样)。当令牌发生更改时,也会调用该方法。
根据 FirebaseCloudMessaging
指南:
您可以将通知定向到单个特定设备。在应用程序初始启动时,FCM SDK 为客户端应用程序实例生成注册令牌。
来源链接: https://firebase.google.com/docs/notifications/android/console-device#access_the_registration_token
这意味着令牌注册是针对应用程序的。您想在用户登录后利用令牌。我建议您在 onTokenRefresh()
方法中将令牌保存到内部存储或共享首选项中。然后,在用户登录后从存储中检索令牌,并根据需要向您的服务器注册令牌。
如果您想手动强制触发 onTokenRefresh()
,可以创建 IntentService 并删除令牌实例。然后,当您调用 getToken 时,onTokenRefresh()
方法将再次被调用。
示例代码:
public class DeleteTokenService extends IntentService
{
public static final String TAG = DeleteTokenService.class.getSimpleName();
public DeleteTokenService()
{
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent)
{
try
{
// Check for current token
String originalToken = getTokenFromPrefs();
Log.d(TAG, "Token before deletion: " + originalToken);
// Resets Instance ID and revokes all tokens.
FirebaseInstanceId.getInstance().deleteInstanceId();
// Clear current saved token
saveTokenToPrefs("");
// Check for success of empty token
String tokenCheck = getTokenFromPrefs();
Log.d(TAG, "Token deleted. Proof: " + tokenCheck);
// Now manually call onTokenRefresh()
Log.d(TAG, "Getting new token");
FirebaseInstanceId.getInstance().getToken();
}
catch (IOException e)
{
e.printStackTrace();
}
}
private void saveTokenToPrefs(String _token)
{
// Access Shared Preferences
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
// Save to SharedPreferences
editor.putString("registration_id", _token);
editor.apply();
}
private String getTokenFromPrefs()
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
return preferences.getString("registration_id", null);
}
}
编辑
FirebaseInstanceIdService
public class FirebaseInstanceIdService extends Service
此类已被弃用,建议重写FirebaseMessagingService的onNewToken方法。 实现后,可以安全地删除此服务。
onTokenRefresh() 已被弃用。请使用 MyFirebaseMessagingService
中的 onNewToken()
方法。
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(String s) {
super.onNewToken(s);
Log.e("NEW_TOKEN",s);
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
}
}
尝试实现FirebaseInstanceIdService
以获取刷新令牌。
访问注册令牌:
您可以通过扩展FirebaseInstanceIdService来访问令牌值。确保已将服务添加到您的清单文件中,然后在onTokenRefresh
上下文中调用getToken
并按如下所示记录该值:
@Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// TODO: Implement this method to send any registration to your app's servers.
sendRegistrationToServer(refreshedToken);
}
完整代码:
import android.util.Log;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
private static final String TAG = "MyFirebaseIIDService";
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
// [START refresh_token]
@Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// TODO: Implement this method to send any registration to your app's servers.
sendRegistrationToServer(refreshedToken);
}
// [END refresh_token]
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM InstanceID token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private void sendRegistrationToServer(String token) {
// Add custom implementation, as needed.
}
}
请看我在这里的回答。
编辑:
你不应该自己启动FirebaseInstanceIdService。
当系统确定需要刷新令牌时,它会调用此服务。应用程序应调用getToken()并将令牌发送到所有应用程序服务器。
这不会经常被调用,因为它需要进行密钥旋转,并处理由于以下原因导致的实例ID更改:
系统会对所有设备的刷新事件进行限制,以避免向应用程序服务器发送过多的令牌更新。
尝试以下方法:
您可以在主线程之外的任何位置调用FirebaseInstanceID.getToken()(无论是服务、AsyncTask等),将返回的令牌存储在本地并将其发送到您的服务器。然后,每当调用
onTokenRefresh()
时,您都将再次调用FirebaseInstanceID.getToken(),获取一个新令牌并将其发送到服务器(可能还包括旧令牌,以便您的服务器可以将其删除并用新令牌替换它)。
onTokenRefresh()
会被调用吗? - Piyush Kukadiya对于那些想要强制刷新令牌并通过这种方式调用 onNewToken 函数的人,只需在需要时调用以下内容:
FirebaseMessaging.getInstance().deleteToken().addOnSuccessListener {
FirebaseMessaging.getInstance().token
}
为了简单起见,我将其编写成了MyFirebaseMessagingService中的静态函数:
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
}
// do your stuff.
}
override fun onNewToken(token: String) {
Log.d(TAG, "FCM token changed: $token")
// send it to your backend.
}
companion object {
private const val TAG = "MyFirebaseMessagingService"
fun refreshFcmToken() {
FirebaseMessaging.getInstance().deleteToken().addOnSuccessListener {
FirebaseMessaging.getInstance().token
}
}
}
}
仅调用 deleteToken() 不够,因为新令牌只有在被请求时才会生成; 当然,每次打开应用程序时都会请求它,因此如果你只调用 deleteToken() ,那么下一次用户打开应用程序时就会生成新的令牌,但如果你需要在他第一次使用应用程序期间或之后发送通知,则可能会导致问题。
而在 deleteToken() 后立即调用 token() 会导致并发问题,因为它们都是异步操作,而 token() 总是会在 deleteToken() 之前结束执行(因为它看到令牌已经存在,因为它尚未被删除,而且不会尝试生成新令牌,因为存在这种情况,而 deleteToken() 正在请求 Firebase 服务器删除当前令牌)。
这就是为什么你需要在成功完成 deleteToken() 线程之后调用 token()。
各位,这有一个非常简单的解决方案。
https://developers.google.com/instance-id/guides/android-implementation#generate_a_token
注意:如果您的应用程序使用了被deleteInstanceID删除的令牌,则您的应用程序需要生成替代令牌。
不要删除实例ID,只需删除令牌:
String authorizedEntity = PROJECT_ID;
String scope = "GCM";
InstanceID.getInstance(context).deleteToken(authorizedEntity,scope);
FirebaseMessaging.getInstance().getToken().addOnSuccessListener(new
OnSuccessListener<String>() {
@Override
public void onSuccess(String newToken) {
....
}
});
我在共享偏好设置中维护一个标记,指示是否将GCM令牌发送到服务器。在启动画面中,每次我都会调用一个名为sendDevicetokenToServer的方法。该方法检查用户ID不为空且GCM发送状态,则将令牌发送到服务器。
public static void sendRegistrationToServer(final Context context) {
if(Common.getBooleanPerf(context,Constants.isTokenSentToServer,false) ||
Common.getStringPref(context,Constants.userId,"").isEmpty()){
return;
}
String token = FirebaseInstanceId.getInstance().getToken();
String userId = Common.getUserId(context);
if(!userId.isEmpty()) {
HashMap<String, Object> reqJson = new HashMap<>();
reqJson.put("deviceToken", token);
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<JsonElement> call = apiService.updateDeviceToken(reqJson,Common.getUserId(context),Common.getAccessToken(context));
call.enqueue(new Callback<JsonElement>() {
@Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> serverResponse) {
try {
JsonElement jsonElement = serverResponse.body();
JSONObject response = new JSONObject(jsonElement.toString());
if(context == null ){
return;
}
if(response.getString(Constants.statusCode).equalsIgnoreCase(Constants.responseStatusSuccess)) {
Common.saveBooleanPref(context,Constants.isTokenSentToServer,true);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onFailure(Call<JsonElement> call, Throwable throwable) {
Log.d("", "RetroFit2.0 :getAppVersion: " + "eroorrrrrrrrrrrr");
Log.e("eroooooooorr", throwable.toString());
}
});
}
在我的FirebaseInstanceIDService类中
@Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
Common.saveBooleanPref(this,Constants.isTokenSentToServer,false);
Common.sendRegistrationToServer(this);
FirebaseMessaging.getInstance().subscribeToTopic("bloodRequest");
}
FirebaseInstanceIdService
此类已被弃用。建议重写 FirebaseMessagingService 中的 onNewToken 方法。一旦实现了该方法,就可以安全地移除此服务。
新的做法是从 FirebaseMessagingService 中重写 onNewToken 方法。
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(String s) {
super.onNewToken(s);
Log.e("NEW_TOKEN",s);
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
}
}
<service
android:name=".MyFirebaseMessagingService"
android:stopWithTask="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
这是在RxJava2中的场景,当一个用户从你的应用程序注销,其他用户登录(同一应用程序)。为了重新生成和调用登录(如果用户的设备之前在活动开始时没有互联网连接,并且我们需要在登录API中发送令牌)。
Single.fromCallable(() -> FirebaseInstanceId.getInstance().getToken())
.flatMap( token -> Retrofit.login(userName,password,token))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(simple -> {
if(simple.isSuccess){
loginedSuccessfully();
}
}, throwable -> Utils.longToast(context, throwable.getLocalizedMessage()));
@FormUrlEncoded
@POST(Site.LOGIN)
Single<ResponseSimple> login(@Field("username") String username,
@Field("password") String pass,
@Field("token") String token
);
onTokenRefresh()
内想要执行的操作需要用户已登录,则将所有操作都包装在一个中:if (FirebaseAuth.instance.currentUser != null) {
// All code that requires a logged in user
}
这样只有在用户登录后才会发生任何事情,此时该方法将再次触发并执行相关操作。 :)