开发者

Android实现网络访问拦截器的常见方式

开发者 https://www.devze.com 2025-07-04 10:21 出处:网络 作者: 花花鱼
目录1、接口访问鉴权原型2、客户端调用header鉴权3、拦截器的方式4、三种方式的对比1. 方式一:使用 runblocking 获取值2. 方式二:使用 CoroutineScope + 缓存(推荐)3. 方式三:纯 runBlocking 同步获取5、相关代
目录
  • 1、接口访问鉴权原型
  • 2、客户端调用header鉴权
  • 3、拦截器的方式
  • 4、三种方式的对比
    • 1. 方式一:使用 runblocking 获取值
    • 2. 方式二:使用 CoroutineScope + 缓存(推荐)
    • 3. 方式三:纯 runBlocking 同步获取
  • 5、相关代码
    • 方式一:使用 withContext
    • 方式二:使用 CoroutineScope
    • 方式三:runBlocking阻塞性获取
  • 6、总结

    1、接口访问鉴权原型

    一般我们在访问服务器端的时候,比如登录成功了,会有一个信息的返回,一般会有一个auth值,也有可能是一个sessionid,这样我们就可以带着这个值或者id,去访问一些需要鉴权的接口了。

    2、客户端调用header鉴权

    在android端,我们通常会编写一些拦截器来进行自动加入header,这样就可以去自动加入,不需要手工处理。

    Android实现网络访问拦截器的常见方式

    3、拦截器的方式

    方式一(withContext / runBlocking)

    每次请求都同步获取 jsessionId

    方式二(CoroutineScope + 缓存)

    异步加载并缓存 jsessionId,支持并发控制

    方式三(纯 runBlocking)

    完全阻塞方式获取 jsessionId

    4、三种方式的对比

    Android实现网络访问拦截器的常见方式

    1. 方式一:使用 runBlocking 获取值

    适用场景:如果你能确保该拦截器仅运行在非 UI 线程(如 OkHttp 的后台线程),可以接受。

    不推荐原因:

    若拦截器在主线程执行(如某些测试或调试场景),会导致卡顿甚至 ANR。

    每次请求都重新获取 JSESSIONID,效率低且无必要。

    val jsessionId = runCatching {
        runBlocking(dispatcher) {
            appDataStore.getJSessionId().firstOrNull()
        }
    }.getOrNull()

    2. 方式二:使用 CoroutineScope + 缓存(推荐)

    异步加载,不会阻塞主线程。

    使用缓存避免频繁读取数据源javascript,提高性能。

    支持并发控制(通过 synchronized)。

    实现了 Closeable 接口,便于资源释放。

    推荐作为生产代码使用,尤其适合需要高性能、稳定性强的 App。

    private var cachedJSessionId: String? = null
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
    private fun updateJSessionId() {
        scope.launch {
            appDataStore.getJSessionId().firstOrNull()?.let {
                synchronized(updateLock) {
                    cachedJSessionId = it
                }
            }
        }
    }

    3. 方式三:纯 runBlocking 同步获取

    在任何上下文中使用都会导致线程阻塞。

    如果 appDataStore.getJSessionId() 是耗时操作(如从磁盘读取),极易造成 ANR。无法应对异常情况。

    val jsessionId: String? = runBlocking {
        appDataStore.getJSessionId().firstOrNull()
    }
    

    5、相关代码

    方式一:使用 withContext

    /**
     * 拦截器,用于添加 JSESSIONID 到请求头
     * 在指定的协程调度器 dispatcher 中,从 appDataStore 获取 JSessionId 的值,并忽略可能的异常,最终将结果赋值给 jsessionId。
     * 具体解释如下:
     * runCatching { ... }:捕获代码块中的异常,避免程序崩溃。
     * runBlocking(dispatcher) { ... }:在指定的协程调度器中启动一个阻塞式协程。
     * appDataStore.getJSessionId().firstOrNull():获取 JSessionId 的第一个值,若无数据则返回 null。
     * .getOrNull():从 runCatching 的结果中取出值,若发生异常则返回 null。
     */
    
    class AuthInterceptor @Inject constructor(
        private val appDataStore: AppDataStore,
        private val dispatcher: CoroutineDispatcher = Dispatchers.IO
    ) : Interceptor {
    
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
    
            // 使用 withContext 在 IO 线程获取数据
            val jsessionId = runCatching {
                runBlocking(dispatcher) {
                    appDataStore.getJSessionId().firstOrNull()
                }
            }.getOrNull()
    
            val newRequest = if (jsessionId != null) {
                val cookie = request.header("Cookie").orEmpty()
                val newCookie = if (cookie.isEmpty()) {
                    "JSESSIONID=$jsessionId"
                } else {
                    "$cookie; JSESSIONID=$jsessionId"
                }
    
                request.newBuilder()
                    .header("Cookie", newCookie)
                    .build()
            } else {
                request
            }
    
            return chain.proceed(newRequest)
        }
    }

    该 Kotlin 代码定义了一个 AuthInterceptor类,用于在发起网络请求时自动添加 `JSESSIONID` 到 Cookie 中。其逻辑如下:

    1. 从 AppDataStore中异步获取 `JSESSIONID`;

    2. 若存在 `JSESSIONID`,则将其拼接到请求头的 `Cookie` 字段中;

    3. 最终继续执行修改后的请求。

    方式二:使用 CoroutineScope

    class AuthInterceptor @Inject constructor(
        private val appDataStore: AppDataStore
    ) : Interceptor, Closeable {
    
        private var cachedJSessionId: String? = null
        private val updateLock = Any()
        private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
        init {
            updateJSessionId()
        }
    
        private fun updateJSessionId() {
            scope.launch {
                appDataStore.getJSessionId().firstOrNull()?.let {
                    synchronized(updateLock) {
                        cachedJSessionId = it
                    }
                }
            }
        }
    
        override fun intercept(chain: Interceptojsr.Chain): Response {
            val request = chain.request()
    
            var jsessionId = cachedJSessionId
            if (jsessionId == null) {
                synchronized(updateLock) {
                    jsessionId = cachedJSessionId
                    if (jsessionId == null) {
                        updateJSessionId()
                        return chain.proceed(request)
                    }
                }
            }
    
            val cookieHeader = request.header("Cookie").orEmpty()
            val newCookie = if (cookieHeader.isEmpty()) {
                "JSESSIONID=$jsessionId"
            } else {
                "$cookieHeader; JSESSIONID=$jsessionId"
            }
    
            val newRequest = request.newBuilder()
                .header("Cookie", newCookie)
                .build()
    
            return chain.proceed(newRequest)
        }
    
        // 实现 Closeable 接口,确保资源释放
        override fun close() {
            scope.cancel()
        }
    }

    推荐作为生产代码使用,尤其适合需要高性能、稳定性强的 App。

    该 Kotlin 代码实现了一个 AuthInterceptor用于在 HTTP 请求中自动添加 `JSESSIONID` Cookie。其功能如下:

    1. **构造与初始化**:通过依赖注入获取 AppDataStore用于读取会话 ID,并在初始化时尝试更新会话 ID。

    2. **intercept 函数**:

       - 若已有缓存的 `JSESSIONID`,则将其添加到请求头的 Cookie 中;

       - 若没有缓存,则重新加载会话 ID;

       - 使用锁保证线程安全。

    3. **updateJSessionIdjavascript 函数**:使用协程从 AppDataStore 异步加载会话 ID 并缓存。

    4. **close 函数**:取消协程作用域,释放资源。

    整体作用:为每个请求自动注入最新的 `JSESSIONID`,确保会话状态有效。

    方式三:runBlocking阻塞性获取

    class AuthInterceptor @Inject constructor(
        private val appDataStore: AppDataStore
    ) : Interceptor {
    
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
    
            // 在拦截器中同步获取 jsessionid(假设可以阻塞)
            val jsessionId: String? = runBlocking {
      http://www.devze.com          appDataStore.getJSessionId().firstOrNull()
            }
    
            // 构建新的请求,添加 cookie(仅当 jsessionId 存在时)
            val newRequest = request.newBuilder().apply {
                if (jsessionId != null) {
                    // 获取现有 Cookie 并追加新的 JSESSIONID
                    val existingCookies = request.header("Cookie")
                    val newCookie = "JSESSIONID=$jsessionId"
    
                    val finalCookie = if (existingCookies.isNullOrEmpty()) {
                        newCookie
                    } else {
                        "$existingCookies; $newCookie"
                    }
    
                    addHeader("Cookie", finalCookie)
                }
            }.build()
    
            return chain.proceed(newRequest)
        }
    
    }

    该 Kotlin 代码定义了一个 AuthInterceptor 拦截器,其功能是:

    从 AppDataStore 中同步获取 jsessionId(使用 runBlocking 阻塞等待);

    若 jsessionId 存在,则将其添加到 HTTP 请求头的 Cookjavascriptie 字段中;

    最终构建新请求并继续拦截器链的执行。

    作用:为每个请求自动添加会话标识 JSESSIONID。

    6、总结

    综合上述几种方式,其实测试下来都是可以的,基本上是没有什么区别,但是如果考虑综合因素,或者多环境下的适应性,还是考虑用使用 CoroutineScope + 缓存(推荐)。

    到此这篇关于Android实现网络访问拦截器的常见方式的文章就介绍到这了,更多相关Android拦截器内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号