如何在Android中用Ktor-Client进行HTTP请求
Ktor是一个客户端-服务器框架,帮助我们在Kotlin中构建应用程序。它是一个由Kotlin coroutines支持的现代异步框架。
我们可以使用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项目。

一旦项目准备好了,打开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获得的数据。
在这个应用程序中,我们将只获得产品的title 、body 和image 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 。在每种情况下,我们指定响应类型,即get 和post ,分别向服务器发送请求和发布响应。
我们正在向一个服务器执行这个请求。同样地,我们也在从服务器获得数据响应。这意味着,一个糟糕的请求/出错的事情会使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将加载产品title 和description 的值。
该应用程序现在已经准备好了,你可以运行它来测试一切是否如预期的那样工作。

总结
Ktor用于HTTP请求,如获取、发布、删除和更新。它是一种直截了当、易于使用的框架语言,完全建立在轮子线上。它可以用最少的模板代码进行异步编程。
在本教程中,我们已经学会了如何使用Ktor客户端并执行HTTP请求。我们使用了转向的响应,并使用Jetpack Compose显示了整个数据列表,并使用Coil处理了图像响应。