You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 7/10设备FirebaseInstanceIdReceiver触发ANR问题排查及FCM Token上传100%成功率实现咨询

解决FCM Token上传引发的ANR问题并实现100%上传成功率

首先来拆解你遇到的两个核心问题:无堆栈ANR和Token上传成功率低,我会一步步给你分析原因并给出落地的优化方案。


一、先解决ANR问题:根源是依赖冲突+冗余逻辑

你的ANR日志指向FirebaseInstanceIdReceiver这个广播接收器,结合你的依赖和代码来看,问题出在这两点:

1. 废弃库导致的依赖冲突

你同时引入了firebase-iid和firebase-messaging,但从Firebase Messaging 21.0.0版本开始,firebase-iid就已经被完全废弃,它的所有功能都整合到了firebase-messaging中。同时引入这两个库会导致类加载冲突,干扰广播接收器的正常执行,最终触发无堆栈的ANR。

修复:移除废弃依赖
删掉gradle里的firebase-iid,只保留firebase-messaging:

// 删掉这行:implementation 'com.google.firebase:firebase-iid:21.1.0'
implementation 'com.google.firebase:firebase-messaging:23.0.6'

2. onNewToken里的冗余逻辑

onNewToken方法本身已经直接提供了新生成的Token,但你又调用了FirebaseMessaging.getInstance().getToken(),这会额外发起异步请求,和广播接收器的生命周期产生冲突——广播接收器的onReceive(包括关联的回调)必须在10秒内完成,否则系统就会判定为ANR。

修复:直接使用onNewToken传入的Token

@Override
public void onNewToken(@NonNull String token) {
    super.onNewToken(token);
    // 直接用传入的token,不要再调用getToken()
    new TokenUploader().uploadToken(token);
}

二、提升Token上传成功率:重构上传逻辑

你的上传代码存在几个明显的缺陷,导致大量设备上传失败:

  • 账号为空时直接返回,没有重试机制
  • 无间隔连续重试,容易被服务器限流
  • 异常捕获后无日志,无法排查问题
  • 用裸线程处理后台任务,无法应对设备重启、网络中断等场景

优化方案:用WorkManager实现可靠的后台上传

WorkManager是Android官方推荐的后台任务处理库,它能自动处理网络状态、设备重启后的任务恢复,非常适合这种需要保证送达的场景。

1. 添加WorkManager依赖

implementation "androidx.work:work-runtime:2.8.1"

2. 编写Token上传Worker类

这个类负责实际的上传逻辑,包含指数退避重试、缓存机制和日志记录:

public class TokenUploadWorker extends Worker {
    private static final String TAG = "TokenUploadWorker";
    public static final String KEY_TOKEN = "token";

    public TokenUploadWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        String token = getInputData().getString(KEY_TOKEN);
        if (TextUtils.isEmpty(token)) {
            Log.e(TAG, "Token为空,无法上传");
            return Result.failure();
        }

        Account account = getCurrentAccount(); // 替换成你获取账号的逻辑
        if (account == null) {
            // 账号未登录,缓存Token,等登录后再重试
            saveTokenToSharedPrefs(token);
            Log.w(TAG, "账号未就绪,缓存Token待后续上传");
            return Result.success();
        }

        boolean uploadSuccess = false;
        int retryCount = 0;
        long backoffDelay = 1000; // 初始重试延迟1秒

        while (retryCount < 4 && !uploadSuccess) {
            try {
                JSONObject params = new JSONObject();
                params.put("email", account.getEmail());
                params.put("password", getPassword()); // 注意:这里传明文密码不安全,建议改用认证Token
                params.put("key", getApiKey());
                params.put("fcm_token", token);

                JSONObject response = sendRequestToApi("POST", params, "/fcm_route");
                if (response != null && response.has("fcmId")) {
                    uploadSuccess = true;
                    clearCachedToken(); // 上传成功,清除缓存
                    Log.d(TAG, "Token上传成功");
                } else {
                    Log.w(TAG, "第" + (retryCount+1) + "次上传失败:响应无效");
                }
            } catch (Exception e) {
                Log.e(TAG, "第" + (retryCount+1) + "次上传失败", e);
            }

            if (!uploadSuccess) {
                retryCount++;
                try {
                    Thread.sleep(backoffDelay);
                    backoffDelay *= 2; // 指数退避,每次延迟翻倍
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return Result.failure();
                }
            }
        }

        if (uploadSuccess) {
            return Result.success();
        } else {
            // 4次重试都失败,缓存Token,后续通过WorkManager自动重试或登录后触发
            saveTokenToSharedPrefs(token);
            Log.e(TAG, "所有上传尝试失败,缓存Token");
            return Result.retry();
        }
    }

    // 保存Token到SharedPreferences
    private void saveTokenToSharedPrefs(String token) {
        SharedPreferences prefs = getApplicationContext().getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE);
        prefs.edit().putString("cached_token", token).apply();
    }

    // 清除缓存的Token
    private void clearCachedToken() {
        SharedPreferences prefs = getApplicationContext().getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE);
        prefs.edit().remove("cached_token").apply();
    }

    // 替换成你的账号获取逻辑
    private Account getCurrentAccount() {
        // TODO: 实现获取当前登录账号的逻辑
        return null;
    }

    // 替换成你的密码获取逻辑(建议改用认证Token,避免明文传输)
    private String getPassword() {
        // TODO: 实现密码获取逻辑
        return null;
    }

    // 替换成你的API Key获取逻辑
    private String getApiKey() {
        // TODO: 实现API Key获取逻辑
        return null;
    }

    // 替换成你的网络请求逻辑(建议用OkHttp等成熟库,处理网络异常)
    private JSONObject sendRequestToApi(String method, JSONObject params, String route) throws Exception {
        // TODO: 实现网络请求
        return null;
    }
}

3. 修改MyFirebaseMessagingService,提交WorkManager任务

@Override
public void onNewToken(@NonNull String token) {
    super.onNewToken(token);
    // 配置任务约束:仅在有网络时执行
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build();

    OneTimeWorkRequest uploadRequest = new OneTimeWorkRequest.Builder(TokenUploadWorker.class)
            .setConstraints(constraints)
            .setInputData(new Data.Builder()
                    .putString(TokenUploadWorker.KEY_TOKEN, token)
                    .build())
            .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
            .build();

    WorkManager.getInstance(this).enqueue(uploadRequest);
}

4. 登录成功后触发缓存Token的上传

在用户登录成功的回调里,检查是否有缓存的未上传Token,然后触发上传:

// 登录成功的回调方法中
SharedPreferences prefs = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE);
String cachedToken = prefs.getString("cached_token", null);
if (!TextUtils.isEmpty(cachedToken)) {
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build();

    OneTimeWorkRequest uploadRequest = new OneTimeWorkRequest.Builder(TokenUploadWorker.class)
            .setConstraints(constraints)
            .setInputData(new Data.Builder()
                    .putString(TokenUploadWorker.KEY_TOKEN, cachedToken)
                    .build())
            .build();

    WorkManager.getInstance(this).enqueue(uploadRequest);
}

三、关键优化点总结

  1. 移除废弃依赖:避免库冲突引发的ANR
  2. 简化onNewToken逻辑:直接使用传入的Token,避免冗余异步请求
  3. 用WorkManager管理后台任务:自动处理网络状态、设备重启,保证任务不丢失
  4. 指数退避重试:避免服务器限流,提升重试成功率
  5. Token缓存机制:账号未登录时缓存Token,登录后自动上传
  6. 完善日志:方便排查上传失败的原因

内容的提问来源于stack exchange,提问作者Sujith S Manjavana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 21:57:48