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

Android 12下okhttp3调用addHeader报非空参数为null的崩溃问题排查

根因分析
  1. 竞态条件问题
    你代码中多次调用userDataManager的读取方法,这一系列操作不具备原子性。如果两次读取之间有其他线程(比如用户登出、token清理逻辑)修改了userDataManager中的存储数据,就会出现前置判断非空,但后续读取实际返回空的情况。
  2. 平台类型隐式空值
    如果UserDataManager.getAuthToken()、RefreshTokenService.getNewTokenUsingRefreshToken()是Java实现的方法,且没有添加@NonNull注解,Kotlin会将返回值识别为平台类型,允许你赋值给声明为非空的accessToken变量。当方法实际返回null时,运行时accessToken就会变成null,传入OkHttp的addHeader方法时就会触发Kotlin的非空参数检查,抛出你遇到的异常。该问题仅在特定触发条件下才会出现,所以本地测试难以复现。
  3. 刷新token异常未捕获
    你在runBlocking中执行刷新token逻辑时没有捕获异常,如果刷新请求出现网络错误、token无效等异常,会导致后续逻辑执行异常,甚至出现预期外的空值。
修复方案
  1. 消除竞态条件,统一添加空值兜底
    一次性读取用户数据避免多次读取的不一致问题,同时对所有可能返回空的取值逻辑做强制兜底,确保最终传给addHeader的参数绝对非空,参考修改后的代码:
class RequestInterceptor @Inject constructor(
    private val userDataManager: UserDataManager, 
    private val refreshTokenService: RefreshTokenService
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        // 一次性读取用户数据,避免多次读取的竞态问题
        val userAccessData = userDataManager.getUserAccessData()
        var accessToken = ""

        if (userAccessData != null) {
            val authToken = userDataManager.getAuthToken() ?: ""
            accessToken = "Bearer $authToken"

            if (userDataManager.isAuthTokenExpired()) {
                accessToken = ""
                try {
                    runBlocking {
                        val newToken = refreshTokenService.getNewTokenUsingRefreshToken() ?: ""
                        accessToken = "Bearer $newToken"
                    }
                } catch (e: Exception) {
                    // 刷新token异常兜底
                    accessToken = ""
                }
            }
        }

        val original = chain.request()
        val builder = original.newBuilder()
            .method(original.method, original.body)
            // 最后再加一层兜底,确保header值绝对非空
            .addHeader("Authorization", accessToken.takeIf { it.isNotEmpty() } ?: "")
        val newRequest = builder.build()

        return chain.proceed(newRequest)
    }
}
  1. 统一OkHttp依赖版本
    检查项目依赖树,确保所有模块引入的OkHttp版本一致,避免编译期和运行期的OkHttp版本不同,导致方法参数非空校验逻辑不匹配。
  2. Android 12适配调整
    如果你的userDataManager底层用的是SharedPreferences,建议替换为DataStore。Android 12对SharedPreferences的异步提交、跨进程读取逻辑做了变更,容易出现数据读取不一致的问题,刚好可能触发空值场景。
定位建议

如果需要进一步确认问题,可以在addHeader调用前增加日志,打印accessToken的实际值和类型,崩溃后通过Firebase日志即可确认空值的来源:

Timber.d("Authorization value: $accessToken, isNull: ${accessToken == null}")

内容的提问来源于stack exchange,提问作者Craxx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 09:45:03