调用 TokenRefresh()上的 Firebase FCM 强制

我正在将我的应用程序从 GCM 迁移到 FCM。

当一个新用户安装我的应用程序时,onTokenRefresh()会被自动调用。问题是用户还没有登录(没有用户 ID)。

用户登录后如何触发 onTokenRefresh()

155237 次浏览

尝试实现 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);
}

Full Code:

   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 变化需要它,这是由于:

  • 应用程序删除实例 ID
  • 应用程序恢复到一个新的设备用户
  • uninstalls/reinstall the app
  • 用户清除应用程序数据

系统将控制所有设备的刷新事件,以避免令牌更新使应用程序服务器超载。

试试下面的方法 :

你可以把 GetToken ()调到任何离开主线的地方 thread (whether it is a service, AsyncTask, etc), store the returned 令牌,并将其发送到您的服务器 onTokenRefresh()叫,你会叫 再次使用 FirebaseInstanceID.getToken () ,获取一个新的令牌,并将其发送到服务器(可能也包括旧的令牌) 您的服务器可以删除它,用新的服务器替换它)。

每当生成一个新标记时,就会调用 onTokenRefresh()方法。在应用程序安装后,它将立即生成(正如您所发现的那样)。当令牌发生更改时,也将调用它。

根据 FirebaseCloudMessaging指南:

You can target notifications to a single, specific device. On initial 启动您的应用程序,FCM SDK 生成一个注册令牌 the client app instance.

Screenshot

资料来源: Https://firebase.google.com/docs/notifications/android/console-device#access_the_registration_token

这意味着令牌注册是针对每个应用程序的。似乎您希望在用户登录后使用令牌。我建议您将 onTokenRefresh()方法中的令牌保存到内部存储或共享首选项中。然后,在用户登录后从存储中检索令牌,并根据需要在服务器上注册令牌。

如果希望手动强制使用 onTokenRefresh(),可以创建一个 InentService 并删除令牌实例。然后,当您调用 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() is 不赞成. Use onNewToken() in MyFirebaseMessagingService

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

这个答案不会破坏实例 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>

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

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在自动启动时不会被调用(来源)。而是重写并使用 onStartCommand。但是在实际的 FirebaseInstanceIdService 中,它被声明为 final,不能被重写。 但是,当我们使用 startService ()启动服务时,如果服务已经在运行,那么它的 使用原始实例(这很好)。我们的 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 方法来进一步增强它。

我在共享 pref 中维护了一个标志,它指示是否将 gcm 令牌发送到服务器。在 Splash 屏幕中,每次我调用一个方法 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");
}

Guys it has very simple solution

Https://developers.google.com/instance-id/guides/android-implementation#generate_a_token

注意: 如果您的应用程序使用的令牌已被 delete teInstanceID 删除,您的应用程序将需要生成替换令牌。

不要删除实例 ID,只删除标记:

String authorizedEntity = PROJECT_ID;
String scope = "GCM";
InstanceID.getInstance(context).deleteToken(authorizedEntity,scope);

This is in RxJava2 in scenario when one user logout from your app and other users login (Same App) To regerate and call login (If user's device didn't have internet connection earlier at the time of activity start and we need to send token in login 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


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

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>

如何更新我的设备令牌

首先,当我登录时,我在用户集合和当前登录用户下发送第一个设备令牌。

之后,我只是在 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)
}
}
}

Each time your app opens it will check for a new token, if the user is not yet signed in it will not update the token, if the user is already logged in you can check for a newToken

FirebaseMessaging.getInstance().getToken().addOnSuccessListener(new
OnSuccessListener<String>() {
@Override
public void onSuccess(String newToken) {
....
}
});

For those who are looking for a way to force the refresh of your token and get this way onNewToken getting called because a new token was generated, you just need to call this whenever you need to do it:

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
}
}
}
}

仅仅调用 delete teToken ()是不够的,因为新令牌只有在被请求时才会生成; 当然,每次打开应用程序时都会被请求,所以如果你只是调用 delete teToken () ,下次用户打开应用程序时就会生成新令牌,但是如果你需要在用户首次使用应用程序时或之后立即发送通知,这就会导致问题。

在 delete teToken ()之后立即调用 Token ()会导致并发问题,因为它们都是异步操作,而且 Token ()总是在 delete teToken ()之前结束执行(因为它看到令牌已经存在,因为它还没有被删除,甚至没有尝试生成一个新的令牌,而 delete teToken ()正在请求 Firebase 服务器删除当前令牌)。

这就是为什么您需要在 deleToken ()线程成功完成之后调用 token ()的原因。

作为解决这些问题的一般方法: 我无法用堆栈溢出文章来解决这个问题。帮助我的是使用 Android Studio-Tools-Firebase 中的助手。在我的例子中,构建摇篮文件中缺少库。

一个简短的回答是,如果您想在 onTokenRefresh()中执行的操作需要用户登录,那么将所有这些内容包装在一个:

if (FirebaseAuth.instance.currentUser != null) {
// All code that requires a logged in user
}

这样的话,在用户登录之前什么都不会发生,这时方法将再次触发,然后事情就会发生。:)