Android — 使用Interceptor和协程实现自动刷新token

在运用端和后端交互时,有些特定的接口需求验证token才能回来正确的成果,例如修正用户信息、获取用户信息等。token通常会设定一个有效期,在有效期内运用才能正常获取成果。当token过期时,用户体会或许受到影响,能够在token过期后主动改写token优化用户体会。

本文简单介绍一下如何运用Interceptor和协程完成主动改写token。

完成主动改写token

运用端通常会在恳求一个需求校验token的接口失利后才感知到token过期。为了防止用户体会受到影响,能够运用OkHttpInterceptor 来阻拦并修正发送恳求和接收呼应的进程。

完成方案如下:

  1. 自定义Interceptor,为需求验证token的接口增加header。
  2. 获取呼应,判别token是否过期。
  3. 假如token过期,发起恳求改写token。假如多个需求验证token的接口并发履行,应该仅履行一次改写token的操作。
  4. 改写token后,带着新token从头发送原恳求获取呼应。

Interceptor示例代码如下:

class ApiInterceptor : Interceptor {
    // 需求验证token的接口的路径调集
    private val checkTokenInterface = arrayListOf(
        "/example/user/info,
        "/example/user/info/edit"
    )
    // 是否正在改写token
    // 运用Volatile,确保多线程获取的值共同
    @Volatile
    private var refreshingToken = false
    private val authHeaderKey = "token-key"
    override fun intercept(chain: Interceptor.Chain): Response {
        // 依据接口路径判别是否需求验证token
        val checkToken = checkTokenInterface.contains(chain.request().url.encodedPath)
        // 需求验证token则增加恳求头,不需求则保持原样
        var request = if (checkToken) {
            chain.request().newBuilder()
                .addHeader(authHeaderKey, "tokenValue")
                .build()
        } else {
            chain.request()
        }
        // 获取呼应
        var response = chain.proceed(request)
        if (checkToken) {
            // 需求验证token,处理呼应,判别token是否过期
            response.body?.let { responseBody ->
                try {
                    if (判别token过期的条件) {
                        runBlocking {
                            if (!refreshingToken) {
                                // 标记为正在改写token
                                refreshingToken = true
                                async {
                                    refreshToken(chain).takeIf { it.isNotEmpty() && it.isNotBlank() }?.let { newToken ->
                                        // 保存newToken
                                    }
                                    // 标记为改写token已结束
                                    refreshingToken = false
                                }.start()
                            }
                            // async{}.await()会等候办法块内的代码履行完毕。
                            // 等到refreshingToken为false时才会履行后续代码。
                            if (async { stopRefreshingToken() }.await()) {
                                // 带着newToken履行原恳求获取呼应
                                response = chain.proceed(chain.request().newBuilder()
                                    .addHeader(authHeaderKey, "newToken")
                                    .build())
                            }
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
        // 回来终究的呼应成果。
        // 改写token也或许失利,此刻回来原呼应,能够让用户从头登录。
        return response
    }
    private fun refreshToken(chain: Interceptor.Chain): String {
        // 创立改写token的恳求
        val refreshTokenRequest = Request.Builder()
            .url("refresh token url")
            .addHeader(authHeaderKey, "old token")
            .get()
            .build()
        return try {
            // 获取呼应并解析取得新token
            val refreshTokenResponse = chain.proceed(refreshTokenRequest)
            refreshTokenResponse.token
        } catch (e: IOException) {
            // 失利时回来空字符串
            "" 
        }
    }
    private suspend fun stopRefreshingToken(): Boolean {
        return if (refreshingToken) {
            // 仍在改写token中,推迟1秒后再次履行此办法
            delay(1000)
            stopRefreshingToken()
        } else {
            true
        }
    }
}

PS: 部分代码涉及到公司的接口所以省略了,实际运用时需求针对需求进行适当的调整。