Android实现网络访问拦截器的常见方式
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>1、接口访问鉴权原型</li><li>2、客户端调用header鉴权</li><li>3、拦截器的方式</li><li>4、三种方式的对比</li><ul class="second_class_ul"><li>1. 方式一:使用 runBlocking 获取值</li><li>2. 方式二:使用 CoroutineScope + 缓存(推荐)</li><li>3. 方式三:纯 runBlocking 同步获取</li></ul><li>5、相关代码</li><ul class="second_class_ul"><li>方式一:使用 withContext</li><li>方式二:使用 CoroutineScope</li><li>方式三:runBlocking阻塞性获取</li></ul><li>6、总结</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>1、接口访问鉴权原型</h2><p>一般我们在访问服务器端的时候,比如登录成功了,会有一个信息的返回,一般会有一个auth值,也有可能是一个sessionid,这样我们就可以带着这个值或者id,去访问一些需要鉴权的接口了。</p>
<p class="maodian"></p><h2>2、客户端调用header鉴权</h2>
<p>在android端,我们通常会编写一些拦截器来进行自动加入header,这样就可以去自动加入,不需要手工处理。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202507/2025070408591375.png" /></p>
<p class="maodian"></p><h2>3、拦截器的方式</h2>
<p>方式一(withContext / runBlocking)</p>
<p>每次请求都同步获取 jsessionId</p>
<p>方式二(CoroutineScope + 缓存)</p>
<p>异步加载并缓存 jsessionId,支持并发控制</p>
<p>方式三(纯 runBlocking)</p>
<p>完全阻塞方式获取 jsessionId</p>
<p class="maodian"></p><h2>4、三种方式的对比</h2>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202507/2025070408591346.png" /></p>
<p class="maodian"></p><h3>1. 方式一:使用 runBlocking 获取值</h3>
<p>适用场景:如果你能确保该拦截器仅运行在非 UI 线程(如 OkHttp 的后台线程),可以接受。</p>
<p>不推荐原因:</p>
<p>若拦截器在主线程执行(如某些测试或调试场景),会导致卡顿甚至 ANR。</p>
<p>每次请求都重新获取 JSESSIONID,效率低且无必要。</p>
<div class="jb51code"><pre class="brush:java;">val jsessionId = runCatching {
runBlocking(dispatcher) {
appDataStore.getJSessionId().firstOrNull()
}
}.getOrNull()</pre></div>
<p class="maodian"></p><h3>2. 方式二:使用 CoroutineScope + 缓存(推荐)</h3>
<p>异步加载,不会阻塞主线程。</p>
<p>使用缓存避免频繁读取数据源,提高性能。</p>
<p>支持并发控制(通过 synchronized)。</p>
<p>实现了 Closeable 接口,便于资源释放。</p>
<p><strong>推荐作为生产代码使用,尤其适合需要高性能、稳定性强的 App。</strong></p>
<div class="jb51code"><pre class="brush:java;">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
}
}
}
}</pre></div>
<p class="maodian"></p><h3>3. 方式三:纯 runBlocking 同步获取</h3>
<p>在任何上下文中使用都会导致线程阻塞。</p>
<p>如果 appDataStore.getJSessionId() 是耗时操作(如从磁盘读取),极易造成 ANR。无法应对异常情况。</p>
<div class="jb51code"><pre class="brush:java;">val jsessionId: String? = runBlocking {
appDataStore.getJSessionId().firstOrNull()
}
</pre></div>
<p class="maodian"></p><h2>5、相关代码</h2>
<p class="maodian"></p><h3>方式一:使用 withContext</h3>
<div class="jb51code"><pre class="brush:java;">/**
* 拦截器,用于添加 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)
}
}</pre></div>
<p>该 Kotlin 代码定义了一个 AuthInterceptor类,用于在发起网络请求时自动添加 `JSESSIONID` 到 Cookie 中。其逻辑如下:</p>
<p>1. 从 AppDataStore中异步获取 `JSESSIONID`;</p>
<p>2. 若存在 `JSESSIONID`,则将其拼接到请求头的 `Cookie` 字段中;</p>
<p>3. 最终继续执行修改后的请求。</p>
<p class="maodian"></p><h3>方式二:使用 CoroutineScope</h3>
<div class="jb51code"><pre class="brush:java;">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: Interceptor.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()
}
}</pre></div>
<p><strong>推荐作为生产代码使用,尤其适合需要高性能、稳定性强的 App。</strong></p>
<p>该 Kotlin 代码实现了一个 AuthInterceptor用于在 HTTP 请求中自动添加 `JSESSIONID` Cookie。其功能如下:</p>
<p>1. **构造与初始化**:通过依赖注入获取 AppDataStore用于读取会话 ID,并在初始化时尝试更新会话 ID。</p>
<p>2. **intercept 函数**:</p>
<p> - 若已有缓存的 `JSESSIONID`,则将其添加到请求头的 Cookie 中;</p>
<p> - 若没有缓存,则重新加载会话 ID;</p>
<p> - 使用锁保证线程安全。</p>
<p>3. **updateJSessionId 函数**:使用协程从 AppDataStore 异步加载会话 ID 并缓存。</p>
<p>4. **close 函数**:取消协程作用域,释放资源。</p>
<p>整体作用:为每个请求自动注入最新的 `JSESSIONID`,确保会话状态有效。</p>
<p class="maodian"></p><h3>方式三:runBlocking阻塞性获取</h3>
<div class="jb51code"><pre class="brush:java;">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 {
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)
}
}</pre></div>
<p>该 Kotlin 代码定义了一个 AuthInterceptor 拦截器,其功能是:</p>
<p>从 AppDataStore 中同步获取 jsessionId(使用 runBlocking 阻塞等待);</p>
<p>若 jsessionId 存在,则将其添加到 HTTP 请求头的 Cookie 字段中;</p>
<p>最终构建新请求并继续拦截器链的执行。</p>
<p>作用:为每个请求自动添加会话标识 JSESSIONID。</p>
<p class="maodian"></p><h2>6、总结</h2>
<p>综合上述几种方式,其实测试下来都是可以的,基本上是没有什么区别,但是如果考虑综合因素,或者多环境下的适应性,还是考虑用使用 CoroutineScope + 缓存(推荐)。</p>
<p>到此这篇关于Android实现网络访问拦截器的常见方式的文章就介绍到这了,更多相关Android拦截器内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Android OKHttp拦截器和缓存案例详解</li><li>OkHttp拦截器在Android网络中的使用和工作原理</li><li>Android OKHttp框架的分发器与拦截器源码刨析</li><li>Android OKHttp3拦截器的使用方法</li><li>Android 封装Okhttp+Retrofit+RxJava,外加拦截器实例</li><li>Android的OkHttp包中的HTTP拦截器Interceptor用法示例</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]