我正在将我的应用程序从 GCM 迁移到 FCM。
当新用户安装我的应用程序时,onTokenRefresh()
会自动调用。问题是用户尚未登录(没有用户 ID)。
onTokenRefresh()
用户登录后如何触发?
我正在将我的应用程序从 GCM 迁移到 FCM。
当新用户安装我的应用程序时,onTokenRefresh()
会自动调用。问题是用户尚未登录(没有用户 ID)。
onTokenRefresh()
用户登录后如何触发?
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() 已弃用。用于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);
}
}
尝试实施FirebaseInstanceIdService
以获取刷新令牌。
访问注册令牌:
您可以通过扩展FirebaseInstanceIdService来访问令牌的值。确保已将服务添加到manifest中,然后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 更改所必需的,原因如下:
系统将限制所有设备的刷新事件,以避免应用程序服务器因令牌更新而过载。
尝试以下方式:
您可以在主线程之外的任何地方调用FirebaseInstanceID.getToken()(无论是服务、AsyncTask 等),将返回的令牌存储在本地并将其发送到您的服务器。然后,无论何时
onTokenRefresh()
调用,您都会再次调用 FirebaseInstanceID.getToken(),获取一个新令牌,并将其发送到服务器(可能还包括旧令牌,以便您的服务器可以删除它,用新令牌替换它) .
伙计们,它有非常简单的解决方案
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);
对于那些正在寻找一种方法来强制刷新令牌并以这种方式调用 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() 的原因。
我在共享首选项中维护一个标志,指示 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");
}
FirebaseMessaging.getInstance().getToken().addOnSuccessListener(new
OnSuccessListener<String>() {
@Override
public void onSuccess(String newToken) {
....
}
});
FirebaseInstanceIdService
此类已弃用。支持在 FirebaseMessagingService 中覆盖 onNewToken。实施后,可以安全地删除此服务。
这样做的新方法是覆盖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>
这是在 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
);
此答案不会破坏实例 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
的服务在自动启动时不会被调用(源码)。而是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 方法来进一步增强此功能。
我如何更新我的 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
[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();
}
}
}
作为解决这些问题的一般方法:我无法通过所有 stackoverflow 文章解决我的这个问题版本。不过,对我有帮助的是使用 Android Studio-Tools-Firebase 中的助手。在我的情况下,构建底座文件中缺少库。