如何在Android中使用Ktor-Client进行HTTP请求

2,678 阅读8分钟

如何在Android中用Ktor-Client进行HTTP请求

Ktor是一个客户端-服务器框架,帮助我们在Kotlin中构建应用程序。它是一个由Kotlin coroutines支持的现代异步框架。

Ktor可以与OkHttpRetrofit等网络库相比。

我们可以使用Ktor向API发出HTTP网络请求,并将响应返回给应用程序。使用Ktor客户端,将网络功能添加到用传统UI工具包或Jetpack Compose开发的应用程序中是很简单的。

Ktor的一个最亲密的亲戚是Retrofit,它也被用来消费Android中的API。Retrofit是一个基于java的Android库,可用于开发基于Android和iOS的应用程序。尽管如此,Retrofit在这些平台上的实现仍然是不同的。

另一方面,Ktor是一个异步的HTTP客户端,可以在多个平台上运行。Ktor客户端是为各种平台设计的,如Android、Native(iOS和桌面)、JVM和JavaScript。Ktor是建立在Kotlin多平台移动(KMM)上。这意味着你可以用Kotlin创建iOS和Android应用程序,并为这两个平台共享很大一部分Kotlin代码。

Kotlin多平台移动使用Kotlin作为基础代码。有了这个,如果你想在安卓和iOS上共享代码,你必须使用Kotlin库。Ktor客户端是一个基于Kotlin的库,从而使其更容易实现KMM原则。

目标

本指南将帮助你进一步了解Ktor。我们将设置Ktor客户端,向JSON API发出HTTP请求,并使用Jetpack Compose显示数据。Compose允许我们在使用Ktor客户端时减少模板代码。这简化并加快了Android UI的开发。

我们将使用Ktor来处理这个产品的JSON数据的请求和响应。

先决条件

要继续学习本教程,你需要具备以下条件。

  • 在你的机器上安装了Android Studio。你还应该熟悉如何使用IDE。
  • 之前有工作和编写基于Kotlin的代码的知识。
  • 基本的Jetpack Compose知识。

设置一个Jetpack Compose Android项目

要使用Jetpack Compose,你需要创建一个有Jetpack Compose工具包的项目。要做到这一点,请导航到你的Android Studio,并创建一个新的Empty Compose项目。

compose-activity

一旦项目准备好了,打开AndroidManifest.xml ,添加互联网权限,如下图所示。

<uses-permission android:name="android.permission.INTERNET"/>

向API发出HTTP请求需要互联网许可。

添加所需的库

让我们添加所有我们需要处理和显示数据的必要库。我们需要以下的库。

Ktor的依赖性

Ktor有许多库,你可以根据你想实现的意图来使用。在这个应用程序中,我们将使用下面的库。

//Ktor dependencies
def ktor_version = '1.6.4'
implementation "io.ktor:ktor-client-core:$ktor_version"

// HTTP engine: The HTTP client used to perform network requests.

implementation "io.ktor:ktor-client-android:$ktor_version"

// The serialization engine used to convert objects to and from JSON.
implementation "io.ktor:ktor-client-serialization:$ktor_version"

// Logging
implementation "io.ktor:ktor-client-logging:$ktor_version"

要使用Ktor,你首先需要添加Ktor核心依赖。然后添加其他依赖项,如用于处理和执行网络请求的HTTP客户端引擎依赖项。由于我们是在Android上构建的,所以我们要添加Android的特定功能。对于iOS,我们将使用一个iOS依赖项。

implementation "io.ktor:ktor-client-okhttp:$ktorVersion" 我们仍然可以提供相同的HTTP引擎来处理网络请求。

我们也在添加Ktor序列化依赖。这将把请求和响应的有效载荷处理为JSON,并从/到你的数据模型进行序列化,使用kotlinx 序列化。

你也可以添加Ktor日志依赖。这将记录Ktor客户端所做的一切。最重要的是,它在控制台中打印出请求和响应。这将帮助你在出错时调试你的客户端和网络请求。

Kotlinx序列化依赖

我们已经设置了Ktor序列化依赖项来序列化我们的数据。这将允许我们在Kotlinx中向API发布JSON数据或从响应中获取JSON数据到一个数据类。

然后我们将使用Kotlinx序列化插件来序列化和反序列化JSON数据。

def serialization_version = '1.3.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"

我们还需要在app.gradle 文件中应用这个插件。

plugins {
    id 'org.jetbrains.kotlin.plugin.serialization'
}

Coil图像的依赖性

Coil是一个由Kotlin coroutines支持的快速、轻量级、易于使用的Android图像加载库。我们使用的数据包含图片URL。我们将使用Coil来解析并在我们的应用程序中加载带有图像URL的响应。

//Coil Image
implementation "io.coil-kt:coil-compose:1.4.0"

设置build.gradle 项目文件

为了使用上述Kotlinx插件,我们需要设置一个classpath,以便Android能够找到这个插件并使用它。在你的build.gradle 项目文件中添加以下classpath。

classpath "org.jetbrains.kotlin:kotlin-serialization:1.5.21"

上述过程完成后,点击sync now ,下载依赖项,并跳转到构建你的客户端应用程序。

设置数据模型

一个模型是一个数据传输对象。它有数据类,代表我们从API得到的东西。我们将处理返回的数据,并在Jetpack Compose composables中显示。

为了设置这个数据传输对象,创建一个Models 包。在这个包内创建一个新的Kotlin数据类,并将其命名为ResponseModel 。这里我们将表示我们打算从这个API获得的数据。

在这个应用程序中,我们将只获得产品的titlebodyimage URL。

下面是ResponseModel 的样子。

@Serializable
data class ResponseModel(
    val title: String,
    val description: String,
    val image: String
)

请注意,我们使用@Serializable 注解来表示这个类是可序列化的。有了这个,序列化插件就知道我们要序列化这个类了。

在你的Models 包中创建另一个数据类,并将其命名为RequestModel 。这将代表我们想要发送到服务器的请求数据。

@Serializable
data class RequestModel(
    val title: String,
    val description: String,
    val image: String
)

设置API端点

我们需要指定数据来源的端点。这是一个基本的URL,将帮助我们访问这个JSON数据。继续创建一个Network 包,在这个包中你将创建一个新的Kotlin对象文件,名为ApiRoutes

这就是我们要设置API端点的方式。

object ApiRoutes {
    private const val BASE_URL = "https://fakestoreapi.com"
    const val PRODUCTS = "$BASE_URL/products"
}

在这里,我们有一个基本的域来承载这些数据。然后我们将这个域映射到这个数据在/products 中托管的路径。

处理API数据

现在让我们创建一些函数来帮助我们处理这些数据,这样Android就可以通过Jetpack Compose来访问这些数据并显示内容。

前往Network 包,创建一个新的Kotlin接口,命名为ApiService ,如下面的代码块所示。

interface ApiService {

    suspend fun getProducts(): List<ResponseModel>

    suspend fun createProducts(productRequest: RequestModel): ResponseModel?

    companion object {
        fun create(): ApiService {
            return ApiServiceImpl(
                client = HttpClient(Android) {
                    // Logging
                    install(Logging) {
                        level = LogLevel.ALL
                    }
                    // JSON
                    install(JsonFeature) {
                        serializer = KotlinxSerializer(json)
                       //or serializer = KotlinxSerializer()
                    }
                    // Timeout
                    install(HttpTimeout) {
                        requestTimeoutMillis = 15000L
                        connectTimeoutMillis = 15000L
                        socketTimeoutMillis = 15000L
                    }
                    // Apply to all requests
                    defaultRequest {
                        // Parameter("api_key", "some_api_key")
                        // Content Type
                        if (method != HttpMethod.Get) contentType(ContentType.Application.Json)
                        accept(ContentType.Application.Json)
                    }
                }
            )
        }

        private val json = kotlinx.serialization.json.Json {
            ignoreUnknownKeys = true
            isLenient = true
            encodeDefaults = false
        }
    }
}

在这里,我们要创建两个函数,getProducts() 来返回产品列表,createProducts() 来创建一个产品。

然后我们创建了一个HTTP客户端的实例,定义了Ktor客户端的基本信息和功能。例如,我们正在设置Ktor的日志功能。这将记录所有的Ktor请求和响应,在调试我们的应用程序时帮助你。

我们还添加了KotlinxSerializer ,对返回的JSON数据进行序列化和反序列化。此外,如果服务器需要很长的时间来响应,我们添加了HttpTimeout ,定义了服务器接收请求的时间,连接到服务器的超时,以及Socket(读和写)的超时。

现在我们需要使用Ktor客户端来实现实际的网络调用。如下图所示,在Network 包内创建一个新的Kotlin类,名为ApiServiceImpl

class ApiServiceImpl(
    private val client: HttpClient
) : ApiService {

    override suspend fun getProducts(): List<ResponseModel> {
        return try {
            client.get { url(ApiRoutes.PRODUCTS) }
        } catch (ex: RedirectResponseException) {
            // 3xx - responses
            println("Error: ${ex.response.status.description}")
            emptyList()
        } catch (ex: ClientRequestException) {
            // 4xx - responses
            println("Error: ${ex.response.status.description}")
            emptyList()
        } catch (ex: ServerResponseException) {
            // 5xx - response
            println("Error: ${ex.response.status.description}")
            emptyList()
        }
    }

    override suspend fun createProducts(productRequest: RequestModel): ResponseModel? {
        return try {

            client.post<ResponseModel> {
                url(ApiRoutes.PRODUCTS)
                body = productRequest
            }
        } catch (ex: RedirectResponseException) {
            // 3xx - responses
            println("Error: ${ex.response.status.description}")
            null
        } catch (ex: ClientRequestException) {
            // 4xx - responses
            println("Error: ${ex.response.status.description}")
            null
        } catch (ex: ServerResponseException) {
            // 5xx - response
            println("Error: ${ex.response.status.description}")
            null
        }
    }
}

createProducts()getProducts() 这两个方法是注入的构造函数。我们使用private val client: HttpClientobject 来进行网络调用,以便从API端点获得产品列表。

这将发送一个异步客户端来执行HTTP请求,这些请求使用KtorHttpClientEngine 。在每种情况下,我们指定响应类型,即getpost ,分别向服务器发送请求和发布响应。

我们正在向一个服务器执行这个请求。同样地,我们也在从服务器获得数据响应。这意味着,一个糟糕的请求/出错的事情会使Ktor抛出一个异常。因此,我们需要处理和捕捉任何异常,如未处理的重定向异常、服务器错误异常和不良客户端请求异常。

添加Jetpack Compose UI

到目前为止,我们已经准备好了数据实例,而且我们已经处理了所有的请求和来自服务器的响应。现在我们需要在Jetpack Compose组件中填充这些数据。

通常情况下,为了在Android应用程序中显示大量的项目,你会设置一个RecyclerView适配器。使用Jetpack Compose,你不需要设置这个适配器,因为你不会使用任何XML视图。

Jetpack Compose允许我们使用LazyColumn ,取代了典型的RecyclerView。你甚至不需要一个View holder类,因为Jetpack Compose允许你只用几行代码就能实现同样的事情。

让我们看看我们如何使用Jetpack ComposeLazyColumn 来设置这个产品列表。LazyColumn 允许你以垂直方向显示列表项目。而如果你想实现水平方向,你使用LazyRow

首先,我们需要访问我们的客户数据。就在onCreate() ,添加我们的API数据,如下图所示。

private val apiService by lazy {
    ApiService.create()
}

这将创建一个新的懒人实例,使用指定的初始化函数初始化器和默认的线程安全模式LazyThreadSafetyMode.SYNCHRONIZED

创建一个空的initialValue ,返回一个可观察的快照状态,在没有定义数据源的情况下随时间产生数值。这将允许客户端在数据被填充到设定的Jetpack Compose状态时异步地访问数据源。

此外,我们将添加一个producer ,返回定义的数据源的值。在应用程序主题块内添加以下代码块。

val products = produceState(
    initialValue = emptyList<ResponseModel>(),
    producer = {
        value = apiService.getProducts()
        }
)

现在让我们来设置LazyColumn 。我们将在Surface{} 内完成这个工作。

LazyColumn {
    items(products.value) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .padding(
                    bottom = 6.dp,
                    top = 6.dp,
                )
                .background(Color.Gray)) {

            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(4.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                //set the image url
                val painter = rememberImagePainter(
                    data = it.image,
                    builder = {
                        error(R.drawable.ic_launcher_background)
                    }
                )

                Image(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(150.dp),
                    contentScale = ContentScale.Crop,
                    contentDescription = "Coil Image",
                    painter = painter
                )
                Spacer(
                    modifier = Modifier
                        .height(4.dp)
                )
                Text(
                    text = it.title,
                    fontSize = 18.sp
                )
                Spacer(
                    modifier = Modifier
                        .height(4.dp)
                )
                Text (
                    text = it.description,
                    fontSize = 12.sp
                )
            }
        }
    }
}

这一栏类似于一个实际的RecyclerView。在这里,我们使用Jetpack Compose来填充数据,以更少的代码获得一个干净的、简约的用户界面。

我们将每个项目包裹在一个Box ,它有三个属性,Image 和两个Text 块。

首先,我们将处理返回的数据并获得image 的值。返回的字符串是一个从服务器加载图片的URL。为了加载这些图像,我们使用前面提到的Coil图像加载器。在这里,我们设置图像URL,传递参数data ,并将返回的图像URL的值赋给它。这将启动一个图像请求。

如果它返回true ,我们继续处理这个响应,并使用Painter将图像加载到Image composable中。这将创建一个可组合的,布置和绘制一个给定的Painter。这将试图根据Painter的固有尺寸和Modifier 参数来确定可组合的尺寸。

如果它返回false ,我们就跳过执行请求,建立一个可选的lambda,配置请求,并设置一个错误的可画资源。Text composable将加载产品titledescription 的值。

该应用程序现在已经准备好了,你可以运行它来测试一切是否如预期的那样工作。

ktor-client

总结

Ktor用于HTTP请求,如获取、发布、删除和更新。它是一种直截了当、易于使用的框架语言,完全建立在轮子线上。它可以用最少的模板代码进行异步编程。

在本教程中,我们已经学会了如何使用Ktor客户端并执行HTTP请求。我们使用了转向的响应,并使用Jetpack Compose显示了整个数据列表,并使用Coil处理了图像响应。