Firebase FCM强制onTokenRefresh()


111

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

当新用户安装我的应用程序时,将onTokenRefresh()自动调用。问题是该用户尚未登录(没有用户ID)。

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


1
在下面的链接中已经提出了一个非常类似的问题。检查答案是对你有用:stackoverflow.com/questions/37517254/...
迭戈Giorgini

Answers:


172

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扩展Service

此类已弃用。支持在FirebaseMessagingService中重写onNewToken。一旦实施,就可以安全地删除该服务。

不推荐使用 onTokenRefresh()。用onNewToken()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);
    }
} 

27
即使在用户登录之前调用onTokenRefresh(),也无需进行将其存储在本地的工作,而是在用户登录时,您可以使用FirebaseInstanceId.getInstance()。getToken()检索令牌并将其发送到服务器进行注册。(除非您想将其存储在本地以便从服务器中删除旧令牌)
geekoraul

10
FireBase很聪明,只有在没有令牌(已删除令牌或第一次调用它)或发生其他情况且令牌已更改的情况下,它才会调用onTokenRefresh()方法。如果有人要调用onTokenRefresh,则可以删除令牌,然后调用FirebaseInstanceId.getInstance()。getToken()。操作FirebaseInstanceId.getInstance()。deleteInstanceId()必须位于AsyncTask或新线程中,而不能位于MainThread上!!!
Stoycho Andreev

3
为什么不只调用FirebaseInstanceId.getToken?
歌曲

1
好吧,在IntentService中调用时效果很好,而且我猜不需要将令牌保存在首选项中。由于该值直到FirebaseInstanceId.getInstance()。deleteInstanceId();才会更改。叫做。Kinda挽救了我的一天。:)
Detoxic-Soul

5
为什么要将令牌保存到共享首选项中-如果您可以随时调用FirebaseInstanceId.getInstance()。getToken()来获取其值?
亚历山大·法伯

18

尝试实现FirebaseInstanceIdService以获取刷新令牌。

访问注册令牌:

您可以通过扩展FirebaseInstanceIdService来访问令牌的值。确保已将服务添加到清单中,然后getToken在中调用onTokenRefresh,并记录值,如下所示:

     @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更改所必需的,原因是:

  • 应用删除实例ID
  • 应用已在新设备上还原用户
  • 卸载/重新安装应用程序
  • 用户清除应用数据

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

请尝试以下方式

您可以在主线程之外的任何地方调用FirebaseInstanceID.getToken()(无论它是服务,AsyncTask等),将返回的令牌存储在本地并将其发送到您的服务器。然后,无论何时 onTokenRefresh()调用,您都将再次调用 FirebaseInstanceID.getToken(),获取一个新令牌,并将其发送到服务器(可能还包括旧令牌,以便您的服务器可以删除它,将其替换为新令牌)。 。


2
我确实实现了FirebaseInstanceIdService,问题是在用户安装应用程序后几乎立即调用了onTokenRefresh()。在执行登录/注册后,我需要调用它
TareK Khoury

1
因此,删除FirebaseInstanceId将刷新令牌,谢谢!
路易CAD

从GCM转换为FCM之后,FirebaseInstanceId.getInstance()。getToken(); 始终返回null。有什么办法吗?
Govinda Paliwal

@TareKhoury您可以在需要获取令牌的任何地方调用此方法。FirebaseInstanceId.getInstance()。getToken();
sssvrock

@pRaNaY如果客户端应用程序更新将onTokenRefresh()被调用?
Piyush Kukadiya

2

我在共享首选项中维护一个标志,该标志指示是否将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");
}

2

伙计们,它有非常简单的解决方案

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

2
没为我工作。调用deleteToken()之后,getToken()返回与之前相同的令牌,并且未调用onTokenRefresh。
Lera

1

这是在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

);

0

此答案不会破坏实例ID,而是可以获取当前实例ID。它还在“共享”首选项中存储刷新的一个。

Strings.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);
     ....

}

onCreate自动启动时不会调用Android 2.0及更高版本的服务()。而是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方法来进一步增强此功能。


如果您是第一次重置应用程序/运行,则刷新令牌会花费一些时间。因此,您将在一两分钟内得到字符串“ default”。
Varun Garg,2016年

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

0

FirebaseInstanceIdService

此类已弃用。支持在FirebaseMessagingService中重写onNewToken。一旦实施,就可以安全地删除该服务。

做到这一点的新方法是onNewTokenFirebaseMessagingService

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>

0

我如何更新我的deviceToken

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

之后,我会覆盖onNewToken(token:String)我的FirebaseMessagingService()值,如果为该用户生成了新令牌,则只会更新该值

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.