Firebase FCM 强制 onTokenRefresh() 被调用
Posted
技术标签:
【中文标题】Firebase FCM 强制 onTokenRefresh() 被调用【英文标题】:Firebase FCM force onTokenRefresh() to be called 【发布时间】:2016-09-24 01:59:23 【问题描述】:我正在将我的应用从 GCM 迁移到 FCM。
当新用户安装我的应用程序时,onTokenRefresh()
会自动被调用。问题是用户还没有登录(没有用户ID)。
用户登录后如何触发onTokenRefresh()
?
【问题讨论】:
以下链接中已经提出了一个非常相似的问题。检查答案是否对您有用:***.com/questions/37517254/… 【参考方案1】:每当生成新令牌时,都会调用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
公共类 FirebaseInstanceIdService 扩展服务
此类已弃用。赞成压倒一切 FirebaseMessagingService 中的 onNewToken。一旦那已经 实施后,可以安全地删除此服务。
onTokenRefresh() 是deprecated。在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);
【讨论】:
即使在用户登录之前调用了 onTokenRefresh(),而不是做本地存储的工作,当用户登录时,您可以使用 FirebaseInstanceId.getInstance().getToken 检索令牌() 并将其发送到服务器进行注册。 (除非您想将其存储在本地以从服务器中删除旧令牌) FireBase 很聪明,它会调用 onTokenRefresh() 方法,只有当没有令牌(它被删除或第一次调用)或发生其他事情并且令牌已被更改时。如果有人想调用 onTokenRefresh 可以删除令牌,然后调用 FirebaseInstanceId.getInstance().getToken()。操作FirebaseInstanceId.getInstance().deleteInstanceId()需要在AsyncTask或者new Thread,不能在MainThread!!! 为什么不直接调用 FirebaseInstanceId.getToken? 好吧,在 IntentService 中调用时效果很好,而且我猜不需要在首选项中保存令牌。由于该值直到 FirebaseInstanceId.getInstance().deleteInstanceId(); 才改变;叫做。有点救了我的一天。 :) 为什么要将令牌保存到共享首选项 - 如果您可以随时致电 FirebaseInstanceId.getInstance().getToken() 获取其价值?【参考方案2】:尝试实现FirebaseInstanceIdService
获取刷新令牌。
获取注册令牌:
您可以通过扩展FirebaseInstanceIdService 来访问令牌的值。确保您已将服务添加到您的manifest,然后在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.
看我的回答here。
编辑:
您不应该自己启动 FirebaseInstanceIdService。
当系统确定需要令牌时调用 神清气爽。应用程序应调用 getToken() 并发送令牌 到所有应用服务器。
这不会被非常频繁地调用,它是密钥轮换和处理实例 ID 更改所必需的:
App 删除实例 ID 应用在新设备用户上恢复 卸载/重新安装应用程序 用户清除应用数据系统将限制所有设备的刷新事件,以避免令牌更新导致应用服务器过载。
试试下面的方法:
您可以在主服务器之外的任何地方调用 FirebaseInstanceID.getToken() 线程(无论是服务、AsyncTask等),存储返回的 本地令牌并将其发送到您的服务器。那么无论何时
onTokenRefresh()
被调用,你会调用 FirebaseInstanceID.getToken() 再次,获取一个新令牌,并将其发送到服务器(可能包括旧令牌) 您的服务器可以删除它,用新的替换它)。
【讨论】:
我确实实现了 FirebaseInstanceIdService,问题是 onTokenRefresh() 在用户安装应用程序后几乎立即被调用。我需要在执行登录/注册后调用它 所以删除 FirebaseInstanceId 会刷新令牌,谢谢! 在 GCM 到 FCM 之后,FirebaseInstanceId.getInstance().getToken();总是返回 null。有什么解决办法吗? @TareKhoury 您可以在需要获取令牌的地方调用此方法。 FirebaseInstanceId.getInstance().getToken(); @pRaNaY 如果客户端应用程序更新,onTokenRefresh()
会被调用?【参考方案3】:
伙计们,它有非常简单的解决方案
https://developers.google.com/instance-id/guides/android-implementation#generate_a_token
注意:如果您的应用使用了被 deleteInstanceID 删除的令牌,您的应用将需要生成替换令牌。
不删除instance Id,只删除token:
String authorizedEntity = PROJECT_ID;
String scope = "GCM";
InstanceID.getInstance(context).deleteToken(authorizedEntity,scope);
【讨论】:
对我不起作用。调用deleteToken()后,getToken()返回和之前一样的token,没有调用onTokenRefresh。【参考方案4】:对于那些正在寻找一种方法来强制刷新令牌并通过这种方式调用 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()。
【讨论】:
【参考方案5】:我在共享首选项中维护一个标志,指示是否将 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());
);
在 MyFirebaseInstanceIDService 类中
@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");
【讨论】:
【参考方案6】: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);
另外别忘了在 Manifest.xml 中添加服务
<service
android:name=".MyFirebaseMessagingService"
android:stopWithTask="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
【讨论】:
【参考方案7】:FirebaseMessaging.getInstance().getToken().addOnSuccessListener(new
OnSuccessListener<String>()
@Override
public void onSuccess(String newToken)
....
);
【讨论】:
疯了,这段代码解决了它,没有被弃用!请记得先初始化:FirebaseApp.initializeApp(context);【参考方案8】:这是在 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
);
【讨论】:
【参考方案9】:此答案不会破坏实例 ID,而是能够获取当前 ID。它还在共享首选项中存储刷新的一个。
字符串.xml
<string name="pref_firebase_instance_id_key">pref_firebase_instance_id</string>
<string name="pref_firebase_instance_id_default_key">default</string>
Utility.java(您想要设置/获取首选项的任何类)
public static void setFirebaseInstanceId(Context context, String InstanceId)
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor;
editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.pref_firebase_instance_id_key),InstanceId);
editor.apply();
public static String getFirebaseInstanceId(Context context)
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String key = context.getString(R.string.pref_firebase_instance_id_key);
String default_value = context.getString(R.string.pref_firebase_instance_id_default_key);
return sharedPreferences.getString(key, default_value);
MyFirebaseInstanceIdService.java(扩展 FirebaseInstanceIdService)
@Override
public void onCreate()
String CurrentToken = FirebaseInstanceId.getInstance().getToken();
//Log.d(this.getClass().getSimpleName(),"Inside Instance on onCreate");
String savedToken = Utility.getFirebaseInstanceId(getApplicationContext());
String defaultToken = getApplication().getString(R.string.pref_firebase_instance_id_default_key);
if(CurrentToken != null && !savedToken.equalsIgnoreCase(defaultToken))
//currentToken is null when app is first installed and token is not available
//also skip if token is already saved in preferences...
Utility.setFirebaseInstanceId(getApplicationContext(),CurrentToken);
super.onCreate();
@Override
public void onTokenRefresh()
.... prev code
Utility.setFirebaseInstanceId(getApplicationContext(),refreshedToken);
....
Android 2.0 及更高版本onCreate
的服务在自动启动时不会被调用 (source)。相反,onStartCommand
被覆盖和使用。但在实际的 FirebaseInstanceIdService 中,它被声明为 final 并且不能被覆盖。
但是,当我们使用 startService() 启动服务时,如果服务已经在运行,它的 original instance is used (这很好)。我们的 onCreate()(上面定义的)也被调用了!
在 MainActivity 开始时或在您认为需要实例 ID 的任何时候使用它。
MyFirebaseInstanceIdService myFirebaseInstanceIdService = new MyFirebaseInstanceIdService();
Intent intent= new Intent(getApplicationContext(),myFirebaseInstanceIdService.getClass());
//Log.d(this.getClass().getSimpleName(),"Starting MyFirebaseInstanceIdService");
startService(intent); //invoke onCreate
最后,
Utility.getFirebaseInstanceId(getApplicationContext())
注意,您可以通过尝试将 startservice() 代码移至 getFirebaseInstanceId 方法来进一步增强此功能。
【讨论】:
如果您第一次重置应用程序/运行,它需要一些时间来刷新令牌。所以你会在一两分钟内得到字符串“default”。【参考方案10】: [Service]
[IntentFilter(new[] "com.google.firebase.INSTANCE_ID_EVENT" )]
class MyFirebaseIIDService: FirebaseInstanceIdService
const string TAG = "MyFirebaseIIDService";
NotificationHub hub;
public override void OnTokenRefresh()
var refreshedToken = FirebaseInstanceId.Instance.Token;
Log.Debug(TAG, "FCM token: " + refreshedToken);
SendRegistrationToServer(refreshedToken);
void SendRegistrationToServer(string token)
// Register with Notification Hubs
hub = new NotificationHub(Constants.NotificationHubName,
Constants.ListenConnectionString, this);
Employee employee = JsonConvert.DeserializeObject<Employee>(Settings.CurrentUser);
//if user is not logged in
if (employee != null)
var tags = new List<string>() employee.Email;
var regID = hub.Register(token, tags.ToArray()).RegistrationId;
Log.Debug(TAG, $"Successful registration of ID regID");
else
FirebaseInstanceId.GetInstance(Firebase.FirebaseApp.Instance).DeleteInstanceId();
hub.Unregister();
【讨论】:
【参考方案11】:我如何更新我的 deviceToken
首先,当我登录时,我会在用户集合和当前登录的用户下发送第一个设备令牌。
之后,我只需在 FirebaseMessagingService()
中覆盖 onNewToken(token:String)
并在为该用户生成新令牌时更新该值
class MyFirebaseMessagingService: FirebaseMessagingService()
override fun onMessageReceived(p0: RemoteMessage)
super.onMessageReceived(p0)
override fun onNewToken(token: String)
super.onNewToken(token)
val currentUser= FirebaseAuth.getInstance().currentUser?.uid
if(currentUser != null)
FirebaseFirestore.getInstance().collection("user").document(currentUser).update("deviceToken",token)
您的应用每次打开时都会检查新令牌,如果用户尚未登录,则不会更新令牌,如果用户已经登录,您可以检查newToken
【讨论】:
【参考方案12】:作为解决这些问题的一般方法: 我无法通过所有 *** 文章解决我的这个问题版本。不过,帮助我的是使用 Android Studio-Tools-Firebase 中的助手。在我的情况下,构建底座文件中缺少库。
【讨论】:
以上是关于Firebase FCM 强制 onTokenRefresh() 被调用的主要内容,如果未能解决你的问题,请参考以下文章