Firebase FCM 强制调用 onTokenRefresh()

Firebase FCM force onTokenRefresh() to be called

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

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

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

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

获取注册令牌:

您可以通过扩展 FirebaseInstanceIdService. Make sure you have added the service to your 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

It will Called when the system determines that the tokens need to be refreshed. The application should call getToken() and send the tokens to all application servers.

这不会被频繁调用,密钥轮换和处理实例 ID 更改需要它,原因是:

  • 应用程序删除实例 ID
  • 应用已在新设备用户上恢复
  • uninstalls/reinstall 应用程序
  • 用户清除应用数据

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

试试下面的方法:

you'd call FirebaseInstanceID.getToken() anywhere off your main thread (whether it is a service, AsyncTask, etc), store the returned token locally and send it to your server. Then whenever onTokenRefresh() is called, you'd call FirebaseInstanceID.getToken() again, get a new token, and send that up to the server (probably including the old token as well so your server can remove it, replacing it with the new one).

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

根据 FirebaseCloudMessaging 指南:

You can target notifications to a single, specific device. On initial startup of your app, the FCM SDK generates a registration token for the client app instance.

Source Link: 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

This class is deprecated. In favour of overriding onNewToken in FirebaseMessagingService. Once that has been implemented, this service can be safely removed.

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

此答案不会破坏实例 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(您想要 set/get 首选项的任何 class)

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 中,它被声明为最终的并且不能被覆盖。 但是,当我们使用 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 方法来进一步增强这一点。

我在 shared pref 中维护一个标志,它指示 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 中 class

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

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

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

这是在 RxJava2 中,当一个用户从您的应用程序注销而其他用户登录(同一应用程序)重新生成并调用登录(如果用户的设备在 [=16= 之前没有互联网连接) ] 开始,我们需要在登录时发送令牌 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

This class is deprecated. In favour of overriding onNewToken in FirebaseMessagingService. Once that has been implemented, this service can be safely removed.

执行此操作的新方法是覆盖 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>

我如何更新我的 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

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

对于那些正在寻找一种方法来强制刷新您的令牌并以这种方式获得 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() 的原因。

作为解决这些问题的一般方法: 我无法通过所有 Whosebug 文章解决我的这个问题。不过,对我有帮助的是使用 Android Studio-Tools-Firebase 中的助手。在我的例子中,构建摇篮文件中缺少库。

一个简短的回答是,如果您想在 onTokenRefresh() 中执行的操作需要用户登录,则将其全部包装在一个:

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

这样,在用户登录之前什么都不会发生,此时该方法将再次触发并发生一些事情。 :)