为什么使用拦截器?

我最近在开始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

Android 网络请求超时处理 android网络请求数据拦截_json


在这个目录中创建一个名为testresponse.json

Android 网络请求超时处理 android网络请求数据拦截_android_02


在这个文件中,粘贴您想要以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用于确保主线程在发出网络请求时不会被阻塞,并用于管理生命周期。