Firebase FCM принудительно вызывает onTokenRefresh()

Я переношу свое приложение с GCM на FCM.

Когда новый пользователь устанавливает мое приложение, автоматически вызывается onTokenRefresh(). Проблема в том, что пользователь еще не вошел в систему (нет идентификатора пользователя).

Как я могу вызвать onTokenRefresh() после входа пользователя в систему?


person TareK Khoury    schedule 26.05.2016    source источник
comment
Очень похожий вопрос уже задавался по следующей ссылке. Проверьте, полезен ли вам ответ: triggered/" title="как запустить службу идентификации fcm только после запуска определенного действия"> stackoverflow.com/questions/37517254/   -  person Diego Giorgini    schedule 31.05.2016


Ответы (10)


Метод onTokenRefresh() будет вызываться всякий раз, когда генерируется новый токен. После установки приложения оно будет сгенерировано немедленно (как вы обнаружили). Он также будет вызываться при изменении токена.

Согласно руководству FirebaseCloudMessaging:

Вы можете настроить таргетинг уведомлений на одно конкретное устройство. При первоначальном запуске приложения пакет SDK FCM создает маркер регистрации для экземпляра клиентского приложения.

Снимок экрана

Ссылка на источник: 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 расширяет службу

Этот класс устарел. В пользу переопределения onNewToken в FirebaseMessagingService. Как только это будет реализовано, эту службу можно безопасно удалить.

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);
    }
} 
person Prince    schedule 01.06.2016
comment
Да, это то, что я сделал вчера. просто сохранил токен и отправил его с регистрацией. - person TareK Khoury; 02.06.2016
comment
Ответ принят. Хотя я не хотел никаких обходных путей, но я думаю, что Firebase не дает возможности поступить иначе. Спасибо! - person TareK Khoury; 03.06.2016
comment
Даже если onTokenRefresh() вызывается до того, как пользователь вошел в систему, вместо того, чтобы выполнять работу по его локальному хранению, когда пользователь входит в систему, вы можете получить токен с помощью FirebaseInstanceId.getInstance().getToken() и отправить его на сервер. для регистрации. (если вы не хотите хранить его локально для удаления старого токена с вашего сервера) - person geekoraul; 16.06.2016
comment
FireBase умен и будет вызывать метод onTokenRefresh(), только если токена нет (он удален или вызывается в первый раз) или что-то еще произошло и токен изменился. Если кто-то хочет вызвать onTokenRefresh, он может удалить токен, а затем вызвать FirebaseInstanceId.getInstance().getToken(). Операция FirebaseInstanceId.getInstance().deleteInstanceId() должна быть в AsyncTask или новом потоке, она не может быть в MainThread!!! - person Stoycho Andreev; 17.07.2016
comment
Почему бы просто не вызвать FirebaseInstanceId.getToken? - person esong; 11.10.2016
comment
ну, отлично работал при вызове IntentService, и, думаю, не нужно сохранять токен в префах. Поскольку значение не меняется до тех пор, пока FirebaseInstanceId.getInstance().deleteInstanceId(); называется. Типа спас мой день. :) - person Detoxic Spirit; 10.01.2017
comment
Зачем сохранять токен в общих настройках, если можно вызвать FirebaseInstanceId.getInstance().getToken() в любое время, чтобы получить его значение? - person Alexander Farber; 29.05.2017
comment
вызов FirebaseInstanceId.getInstance().deleteInstanceId(); будет сбрасывать идентификатор экземпляра и отзывать все токены. Означает ли это, что предыдущие токены становятся недействительными и больше не могут получать уведомления? - person Rahul Matte; 03.10.2017
comment
@geekoraul (спасибо), также не нужно хранить локально для удаления с сервера. непосредственно перед вызовом метода DeleteToken удалите токен с вашего собственного сервера. - person Ali_dev; 13.09.2018
comment
@AlexanderFarber getToken () теперь устарела. Любая другая альтернатива для этого сейчас? Он запрашивает два аргумента String для его передачи. Вы можете помочь мне с этим? - person GiridharaSPK; 18.09.2019
comment
этот ответ отлично сработал с небольшой настройкой для моих нужд. Возможность использовать adb shell am startservice для вызова службы удаления для запуска обновления токена. - person Ray Hunter; 02.02.2021

Попробуйте реализовать 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() и отправлять токены всем серверам приложений.

Это не будет вызываться очень часто, оно необходимо для ротации ключей и для обработки изменений идентификатора экземпляра из-за:

  • Приложение удаляет идентификатор экземпляра
  • Приложение восстановлено на новом устройстве Пользователь
  • удаляет/переустанавливает приложение
  • Пользователь очищает данные приложения

Система будет ограничивать событие обновления на всех устройствах, чтобы избежать перегрузки серверов приложений обновлениями токенов.

Попробуйте следующий способ:

вы должны вызвать FirebaseInstanceID.getToken() в любом месте вне вашего основного потока (будь то служба, AsyncTask и т. д.), локально сохранить возвращенный токен и отправить его на ваш сервер. Затем всякий раз, когда вызывается onTokenRefresh(), вы снова вызываете FirebaseInstanceID.getToken(), получаете новый токен и отправляете его на сервер (возможно, включая старый токен, чтобы ваш сервер мог его удалить). , заменив его новым).

person pRaNaY    schedule 29.05.2016
comment
Я реализовал FirebaseInstanceIdService, проблема в том, что onTokenRefresh() вызывается почти сразу после того, как пользователь устанавливает приложение. мне нужно, чтобы он вызывался после входа/регистрации - person TareK Khoury; 29.05.2016
comment
Так что удаление FirebaseInstanceId обновит токен, спасибо! - person Louis CAD; 22.11.2016
comment
после GCM в FCM, FirebaseInstanceId.getInstance().getToken(); всегда возвращайте ноль. Любое решение? - person Govinda Paliwal; 21.01.2017
comment
@TareKhoury Вы можете вызывать этот метод везде, где это необходимо для получения токена. FirebaseInstanceId.getInstance().getToken(); - person sssvrock; 17.08.2017
comment
@pRaNaY в случае обновления клиентского приложения onTokenRefresh() будет вызван? - person Piyush Kukadiya; 28.12.2017

Ребят очень простое решение

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

Примечание. Если ваше приложение использовало токены, которые были удалены с помощью deleteInstanceID, вашему приложению потребуется сгенерировать заменяющие токены.

Вместо удаления идентификатора экземпляра удалите только токен:

String authorizedEntity = PROJECT_ID;
String scope = "GCM";
InstanceID.getInstance(context).deleteToken(authorizedEntity,scope);
person Manav Patadia    schedule 13.02.2017
comment
Не работает для меня. После вызова deleteToken() getToken() возвращает тот же токен, что и раньше, а onTokenRefresh не вызывался. - person Lera; 25.03.2017

Я поддерживаю один флаг в общем преф, который указывает, отправлен ли токен gcm на сервер или нет. На экране-заставке каждый раз, когда я вызываю один метод sendDevicetokenToServer. Этот метод проверяет, не пуст ли идентификатор пользователя и статус отправки 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");
}
person Prashanth Debbadwar    schedule 29.09.2016

FirebaseInstanceIdService

Этот класс устарел. В пользу переопределения onNewToken в FirebaseMessagingService. Как только это будет реализовано, эту службу можно безопасно удалить.

Новый способ сделать это — переопределить метод onNewToken из FirebaseMessagingService.

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>
person Ashwin Valento    schedule 14.06.2019

Для тех, кто ищет способ принудительно обновить свой токен и получить этот способ при вызове NewToken из-за того, что был сгенерирован новый токен, вам просто нужно вызывать это всякий раз, когда вам нужно это сделать:

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(), новый токен будет сгенерирован при следующем открытии приложения пользователем, но это может привести к проблемам, если вам нужно отправлять уведомления прямо во время или после первого использования приложения.

И вызов token() сразу после deleteToken() вызывает проблемы параллелизма, поскольку обе операции являются асинхронными, а token() всегда завершает выполнение перед deleteToken() (поскольку он видит, что токен уже существует, поскольку он еще не удален, и даже не пытается сгенерировать новый из-за этого, в то время как deleteToken() запрашивает серверы Firebase удалить текущий токен).

Вот почему вам нужно вызвать token() после успешного завершения потока deleteToken().

person Piego Il Tempo    schedule 07.07.2021

Это в RxJava2 в сценарии, когда один пользователь выходит из вашего приложения, а другие пользователи входят в систему (одно и то же приложение). логин апи)

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

);
person Aklesh Singh    schedule 21.06.2018

Этот ответ не уничтожает идентификатор экземпляра, вместо этого он может получить текущий. Он также сохраняет обновленный в общих настройках.

Строки.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 или в любой момент, когда вам нужен идентификатор экземпляра.

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.

person Varun Garg    schedule 23.08.2016
comment
Если вы сбрасываете приложение/запускаете его в первый раз, для обновления токена требуется некоторое время. Таким образом, вы получите строку по умолчанию в течение минуты или двух. - person Varun Garg; 23.08.2016

Как я обновляю свой 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

person Gastón Saillén    schedule 11.04.2020

person    schedule
comment
Безумие, этот код решил эту проблему, а не устарел! Не забудьте сначала инициализировать: FirebaseApp.initializeApp(context); - person Ezequiel Adrian; 09.12.2020