98.网络请求回调的实现方式

一、什么是网络请求回调?

当我们向服务器发起网络请求时,这个网络请求本身通常是耗时操作。为了避免阻塞主线程(UI 线程),我们会采用异步请求的方式。在异步请求中,请求发送后不会立即返回结果,而是等到响应到达或者请求失败后,通过一个回调函数将结果通知给调用者。
回调函数通常包括两个部分:

  1. 成功时的回调(例如返回获取到的数据);
  2. 失败时的回调(例如返回错误信息)。

二、为何采用回调?

  1. 避免阻塞:网络请求可能需要几百毫秒甚至更长时间,使用回调能够保证 UI 的响应性。
  2. 解耦请求与处理逻辑:回调使得网络请求与处理返回结果的代码分离,可以让代码更灵活和模块化。
  3. 异步执行:异步回调允许多个请求并行执行,而不用等待前一个请求完成。

三、如何在 Kotlin 中实现网络请求回调

我们以 OkHttp 为例,OkHttp 是一个非常流行且强大的网络请求库。下面我们将创建两个文件,一个封装网络请求的工具类 NetworkClient,一个展示如何使用回调发起网络请求的 MainActivity。


以下是完整示例:

NetworkClient.kt:

package com.example.myapplication

import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException

/**
 * NetworkClient 用于封装网络请求逻辑,并提供异步 GET 请求方法。
 */
/*
创建单例对象:
OkHttpClient 实例: NetworkClient 内部通常会维护一个 OkHttpClient 实例 (如代码所示)
OkHttpClient 的创建和配置涉及一些资源,例如线程池、连接池等。 如果每次发起网络请求都创建一个新的 OkHttpClient 实例
会造成资源浪费,降低效率。 单例模式可以确保整个应用只创建一个 OkHttpClient 实例,从而更好地管理这些资源。
 */
object NetworkClient {

    /*
    声明为private:这有助于封装和保护 OkHttpClient 实例,防止被意外修改。
    OkHttpClient(): 这是一个构造函数调用,用于创建一个 OkHttpClient 类的实例
    OkHttpClient 是一个用于发送 HTTP 请求的库,它提供了很多配置选项,例如超时时间、连接池、拦截器等。
     */
    private val client = OkHttpClient()

    /**
     * 发起 GET 网络请求。
     *
     * @param url 请求地址
     * @param onSuccess 请求成功时的回调,传入服务器响应字符串
     * @param onFailure 请求失败时的回调,传入异常信息
     */
    /*
    url 是参数的名称,表示网络请求的 URL 地址。

    onSuccess 是参数的名称,表示请求成功时的回调函数。

    (response: String) -> Unit 是参数的类型,表示 onSuccess 必须是一个函数
        该函数接收一个 String 类型的参数 (表示服务器返回的响应数据),并且不返回任何值 (返回 Unit)。
    onFailure 是参数的名称,表示请求失败时的回调函数。
    (exception: Exception) -> Unit 是参数的类型,表示 onFailure 必须是一个函数
        该函数接收一个 Exception 类型的参数 (表示发生的异常),并且不返回任何值 (返回 Unit)。

    Request 是 OkHttp 库中的一个类,用于表示一个 HTTP 请求。
    Builder() 是 Request 类的一个内部类,用于构建 Request 对象。
    .url() 是 Request.Builder 类的一个方法,用于设置请求的 URL 地址。
     */
    fun get(
        url: String,
        onSuccess: (response: String) -> Unit,
        onFailure: (exception: Exception) -> Unit
    ) {
        // 创建请求对象
        val request = Request.Builder()
            .url(url)
            .build()

        // 使用 OkHttp 的异步 enqueue 方法进行网络请求
        /*
        newCall(request): 这是 OkHttpClient 类的一个方法,用于创建一个新的 Call 对象。 Call 对象代表一个可以执行的请求。

        enqueue(callback): 这是 Call 接口的一个方法,用于将请求放入 OkHttp 的异步执行队列中
        callback 参数是一个 Callback 对象,用于接收请求的结果。

        当请求失败时 (例如网络连接错误、服务器返回错误状态码),OkHttp 会调用该方法。

        e: IOException: 这是 onFailure 方法的第二个参数,表示发生的 IOException 异常
        IOException 通常表示 I/O 相关的错误,例如网络连接错误、文件读写错误等。
         */
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                // 请求失败时调用 onFailure 回调
                onFailure(e)
            }

            /*
             当请求成功时 (服务器返回 200-300 之间的状态码),OkHttp 会调用该方法。

             response: Response: 这是 onResponse 方法的第二个参数,表示服务器返回的 Response 对象
              Response 对象包含了响应的所有信息 (例如状态码、Header、Body 等)。

              response.body: 这是 Response 对象的一个属性,用于获取响应体 (response body)。 响应体包含了服务器返回的数据
              response.body 的类型是 ResponseBody?,表示响应体可能为空 (null)。

              responseBody 是 lambda 表达式的参数,表示从响应体中读取的字符串数据。
              作用: 安全地从响应体中读取字符串数据,并在响应体不为空的情况下执行一段代码。


             ?: 这是 Kotlin 的 Elvis 操作符
             如果 response.body?.string() 的结果为 null (表示响应体为空),则执行 Elvis 操作符右侧的代码。

             Response 是 OkHttp 库中的一个类,它代表了服务器对 HTTP 请求的响应
             简单来说,当你发送一个 HTTP 请求给服务器后,服务器会返回一个 Response 对象,这个对象包含了服务器返回的所有信息。

            Response 对象是由 OkHttp 库在接收到服务器的响应后创建的。
            更具体地说,当你在 enqueue 方法中注册的 Callback 的 onResponse 方法被调用时,OkHttp 已经完成了以下步骤:
            发送请求: OkHttp 按照你创建的 Request 对象,将 HTTP 请求发送到指定的服务器。
            接收响应: 服务器处理完你的请求后,会返回一个 HTTP 响应。
            创建 Response 对象: OkHttp 接收到服务器的响应后,会解析响应头和响应体,并将这些信息封装到一个 Response 对象中。
            调用 onResponse: OkHttp 将创建好的 Response 对象作为参数传递给 onResponse 方法。
             */
            override fun onResponse(call: Call, response: Response) {
                // 注意 response.body 是一个属性,不是方法调用
                response.body?.string()?.let { responseBody ->
                    // 请求成功时调用 onSuccess 回调
                    onSuccess(responseBody)
                } ?: onFailure(IOException("Empty Response"))
            }
        })
    }
}

main.kt:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast

/**
 * MainActivity 展示了如何使用 NetworkClient 进行网络请求
 * 并通过回调机制处理请求结果。
 */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 假设你的布局文件为 activity_main.xml
        setContentView(R.layout.activity_main)

        // 发起网络请求
        NetworkClient.get(
            url = "https://jsonplaceholder.typicode.com/posts/1",
            onSuccess = { response ->
                // 请求成功后,通过 runOnUiThread 切换到主线程更新 UI
                runOnUiThread {
                    Toast.makeText(this, "请求成功:$response", Toast.LENGTH_LONG).show()
                }
                Log.d("MainActivity", "Response: $response")
            },
            onFailure = { exception ->
                // 请求失败后,同样切换到主线程更新 UI
                runOnUiThread {
                    Toast.makeText(this, "请求失败:${exception.message}", Toast.LENGTH_LONG).show()
                }
                Log.e("MainActivity", "Error", exception)
            }
        )
    }
}

四、详细讲解

  1. 在 NetworkClient.kt 中

    • 我们定义一个单例对象 NetworkClient,用来封装网络请求逻辑。
    • 使用 OkHttpClient 创建一个实例,对象内部管理请求线程等。
    • 定义一个 get 方法,接收请求地址以及两个 lambda 表达式(分别为 onSuccess 和 onFailure 回调):
      • onSuccess:在请求成功并拿到响应数据时执行。
      • onFailure:在遇到请求异常或失败时执行。
    • 调用 OkHttpClient 的 newCall(request).enqueue() 方法,enqueue 方法会在后台线程执行请求,并在回调中调用 onFailure 或 onResponse。
  2. 在 MainActivity.kt 中

    • 演示如何调用 NetworkClient.get 发起一个网络请求。
    • 请求成功后回调 onSuccess,其中使用 runOnUiThread 切回主线程进行 UI 更新。
    • 请求失败后同样切回主线程显示错误信息。

五、注意事项

  • OkHttp 的响应体只能调用一次,例如 response.body()?.string() 读取后会关闭流,因此需要注意调用时机。
  • 在回调函数中,网络请求回调是在工作线程中执行的,如果需要更新 UI,需要切回主线程(例如使用 runOnUiThread)。
  • 异步回调是基于回调函数链式调用的方式,代码可能会稍显零散,可以考虑使用 Kotlin 协程实现更加简洁的异步代码。

需要在 build.gradle 文件中添加 OkHttp 的依赖:

dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}

下面是我的完整的:

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace = "com.example.myapplication"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.example.myapplication"
        minSdk = 24
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)

    // 添加 OkHttp 依赖,用于进行网络请求
    implementation("com.squareup.okhttp3:okhttp:4.10.0")

    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
}

然后这个网络请求别忘了添加:

  1. 在 <manifest> 标签内添加下面这一行代码:

    <uses-permission android:name="android.permission.INTERNET" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值