为什么使用拦截器?
我最近在开始Android开发人员实习时发现的最真实的用例。对于每一个新的特性,产品api的新端点都必须被创建来向应用程序提供数据,在api还没有准备好使用之前,Android团队使用拦截器来模拟网络请求,并提供虚拟响应,以便继续进行开发工作。
创建一个新的Android Studio项目。在应用程序级构建中,gradle文件添加以下依赖项。
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2'
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
同时在Android清单文件中添加Internet权限
android:name="android.permission.INTERNET"/>
项目的布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Hello World!"
android:textColor="@color/black"
android:textSize="32sp"
app:layout_constraintBottom_toTopOf="@+id/bt_request"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bt_request"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Get"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_activity" />
</androidx.constraintlayout.widget.ConstraintLayout>
这是一个简单的布局,包含一个按钮和一个文本视图,我们将通过单击按钮进行网络调用,并在文本视图中显示数据。
在继续之前,让我们首先在项目中添加一个测试响应,拦截器将在拦截请求后提供该响应
按照给定的步骤创建一个名为raw
在这个目录中创建一个名为testresponse.json
在这个文件中,粘贴您想要以json格式提供的响应。
{
"title": "Feature Title",
"id": "Feature Id"
}
让我们开始编写拦截器类的主要部分
创建一个新的Kotlin类并将其命名为DemoInterceptor并从Interceptor类继承它,实现成员,它应该如下所示。
package com.example.interceptordemo
import okhttp3.Interceptor
import okhttp3.Response
class DemoInterceptor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
TODO("Not yet implemented")
}
}
我们的目标是拦截网络请求并在testresponse.json中提供响应。
package com.example.interceptordemo
import okhttp3.Interceptor
import okhttp3.Response
class DemoInterceptor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val uri = chain.request().url.toUrl()
when {
uri.toString()
.endsWith("feature") -> {
// provide test response
}
}
return chain.proceed(chain.request())
}
}
1、文件testresponse.json位于raw文件夹中的resources目录中,要访问它,我们需要应用程序上下文。
2、我们还需要解析返回的响应中的.json文件。
package com.example.interceptordemo
import android.content.Context
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.io.BufferedReader
class DemoInterceptor(private val context: Context) : Interceptor {
private var contentType = "application/json"
override fun intercept(chain: Interceptor.Chain): Response {
val uri = chain.request().url.toUrl()
when {
uri.toString()
.contains("https://www.experimnetapi.com/feature") -> {
return getResponse(chain, R.raw.testresponse)
}
}
return chain.proceed(chain.request())
}
fun getResponse(chain: Interceptor.Chain, resId: Int): Response {
val jsonString = this.context.resources
.openRawResource(resId)
.bufferedReader()
.use(BufferedReader::readText)
val builder = Response.Builder()
builder.request(chain.request())
builder.protocol(Protocol.HTTP_2)
builder.addHeader("content-type", contentType)
builder.body(
jsonString.
toByteArray().
toResponseBody(contentType.toMediaTypeOrNull())
)
builder.code(200)
builder.message(jsonString)
return builder.build()
}
}
我们需要一个映射json响应的类
package com.example.interceptordemo
/**
* Data class to for testresponse.json
* */
data class FeatureResponse(
val id: String,
val title: String
)
让我们制作api接口
package com.example.interceptordemo
import retrofit2.Response
import retrofit2.http.GET
interface ExperimentApi {
// api we want to test
@GET("feature")
suspend fun getFeature(): Response<FeatureResponse>
}
进行api调用
我们将在MainActivity.class中执行此操作。
package com.example.interceptordemo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.example.interceptordemo.databinding.ActivityMainBinding
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val builder = OkHttpClient()
.newBuilder()
.addInterceptor(DemoInterceptor(applicationContext))
.build()
val experiment_api by lazy {
Retrofit.Builder()
.baseUrl("https://www.experimnetapi.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(builder)
.build()
.create(ExperimentApi::class.java)
}
binding.btRequest.setOnClickListener {
lifecycleScope.launch {
Log.e("info", Thread.currentThread().toString())
val response = experiment_api.getFeature()
binding.tvActivity.text = response.body()?.title
}
}
}
}
1、我们首先构建了OkHttp客户端的一个实例,并将DemoInterceptor添加到它的上下文中。
2、然后我们将OkHttp客户端提供给我们的改型实例。
3、最后,我们在我们的按钮上添加了一个点击监听器,该监听器通过改造进行网络调用,并将数据放在textview中。
注意:lifecycleScope用于确保主线程在发出网络请求时不会被阻塞,并用于管理生命周期。