GraphQL 集成:什么是 GraphQL、Apollo Kotlin 客户端、与 Retrofit 对比
说到网络层,大家第一反应肯定是 Retrofit + OkHttp。这套组合拳确实能打,但说实话,它并不是万能的。我在几年前接手过一个项目,后端接口动不动就返回几十个字段,而前端只需要其中三四个。每次看到那坨 JSON,我都觉得带宽在哭泣。后来我们引入了 GraphQL,情况才好转。
这一章,我们就来聊聊 GraphQL 在 Android 端的落地。我会从概念讲起,然后带你上手 Apollo Kotlin 客户端,最后再跟 Retrofit 做个对比。嗯,保证都是实战干货。
什么是 GraphQL?
GraphQL 是一种 API 查询语言,由 Facebook 在 2012 年内部开发,2015 年开源。它的核心思想是:让客户端精确指定需要的数据。
你想想看,传统的 REST API 是服务端定义好返回结构,客户端只能全盘接收。而 GraphQL 反过来——客户端写查询语句,服务端按需返回。说白了,就是「你要什么,我给你什么」。
核心特点:
- 精确获取:只返回客户端请求的字段,不多不少
- 单一端点:所有操作都通过一个 /graphql 端点完成
- 强类型系统:Schema 定义了所有可查询的类型和关系
- 实时订阅:原生支持 WebSocket 订阅,实现实时推送
举个例子。假设我们要获取用户信息和他的订单列表。用 REST 的话,你可能需要调两个接口:/users/1 和 /users/1/orders。用 GraphQL,一个查询搞定:
query {
user(id: "1") {
name
email
orders {
id
total
items {
name
price
}
}
}
}
返回的数据结构跟查询语句一模一样。这就是 GraphQL 最迷人的地方——查询即文档。
Apollo Kotlin 客户端
在 Android 端集成 GraphQL,目前最成熟的选择就是 Apollo Kotlin 客户端。它由 Apollo GraphQL 公司维护,支持 Kotlin 协程和 Flow,跟我们的技术栈完美契合。
快速上手
第一步,在 build.gradle.kts 中添加依赖:
// 项目级 build.gradle.kts
plugins {
id("com.apollographql.apollo3") version "3.8.2"
}
// 模块级 build.gradle.kts
dependencies {
implementation("com.apollographql.apollo3:apollo-runtime:3.8.2")
implementation("com.apollographql.apollo3:apollo-coroutines-support:3.8.2")
}
apollo {
service("default") {
packageName.set("com.example.graphql")
}
}
第二步,定义 GraphQL 查询文件。在 src/main/graphql 目录下创建 GetUser.graphql:
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
avatar
}
}
第三步,Apollo 会根据这个文件自动生成 Kotlin 代码。然后你就可以在 ViewModel 中愉快地调用了:
class UserViewModel : ViewModel() {
private val apolloClient = ApolloClient.Builder()
.serverUrl("https://api.example.com/graphql")
.build()
fun getUser(id: String): Flow<UiState<User>> = flow {
emit(UiState.Loading)
val response = apolloClient
.query(GetUserQuery(id = id))
.toFlow()
.collect { result ->
val user = result.data?.user?.let {
User(it.id, it.name, it.email, it.avatar)
}
emit(UiState.Success(user))
}
}.catch { e ->
emit(UiState.Error(e.message ?: "未知错误"))
}
}
我个人习惯把 ApolloClient 做成单例,用依赖注入管理。这样测试的时候也方便 mock。
小技巧:Apollo 支持自动缓存。你可以配置 NormalizedCache,这样相同的查询不会重复请求网络。我在项目中用这个功能减少了 30% 的网络开销。
与协程和 Flow 的整合
Apollo Kotlin 对协程的支持非常原生。它提供了 toFlow() 扩展函数,可以把查询结果转成 Flow。配合 catch 和 retry 操作符,错误处理变得很优雅。
我曾经遇到过一个坑:Apollo 默认的 toFlow() 在查询失败时会直接抛异常,不会发射错误状态。后来我发现需要手动处理 ApolloResponse.exception:
apolloClient.query(query).toFlow().collect { response ->
if (response.hasErrors()) {
// 处理 GraphQL 层面的错误
emit(UiState.Error(response.errors?.first()?.message ?: "GraphQL error"))
} else if (response.exception != null) {
// 处理网络层面的错误
emit(UiState.Error(response.exception.message ?: "Network error"))
} else {
// 正常数据
emit(UiState.Success(response.data))
}
}
与 Retrofit 对比
很多同学会问:有了 Retrofit,为什么还要用 GraphQL?它们不是替代关系,而是互补关系。我整理了一个对比表格,方便你理解:
| 维度 | Retrofit + REST | Apollo + GraphQL |
|---|---|---|
| 数据获取 | 服务端定义返回结构,客户端全盘接收 | 客户端精确指定字段,按需返回 |
| 端点数量 | 多个端点(/users, /orders, /products...) | 单一端点(/graphql) |
| 类型安全 | 需要手动定义 DTO 和解析器 | 自动生成 Kotlin 代码,编译期类型检查 |
| 缓存策略 | 通常配合 OkHttp 缓存或 Room | 内置 NormalizedCache,自动去重 |
| 实时推送 | 需要额外集成 WebSocket 库 | 原生支持 Subscription |
| 学习曲线 | 低,大部分开发者熟悉 | 中等,需要理解 Schema 和查询语法 |
| 适用场景 | 简单 CRUD、第三方 API 集成 | 复杂数据关系、多端复用、实时场景 |
说白了,如果你的项目只是简单的列表展示、表单提交,Retrofit 完全够用。但如果你遇到以下情况,GraphQL 会更合适:
- 数据关系复杂:比如社交应用,用户、帖子、评论、点赞互相嵌套
- 多端复用:同一个 API 要服务 Android、iOS、Web,每个端需要的字段不同
- 带宽敏感:移动端网络环境差,能省一点是一点
- 实时需求:聊天、通知、实时数据看板
注意:GraphQL 不是银弹。它的查询语句可能很复杂,服务端实现也有一定门槛。如果你的后端团队不熟悉 GraphQL,强行引入反而会增加沟通成本。我建议先在小模块试点,验证效果后再推广。
知识体系结构图
下面这张图展示了 GraphQL 在 Android 网络层中的位置,以及它与 Retrofit 的关系:
从这张图可以看得很清楚:Apollo Kotlin 和 Retrofit 都位于客户端与后端之间,但它们的通信方式完全不同。Apollo 走的是单一端点、精确查询的路线;Retrofit 走的是多端点、全量返回的路线。选择哪个,取决于你的业务场景。
我的建议
如果你现在正在搭建一个新项目,我的建议是:不要盲目追求新技术。先评估团队的技术栈和后端的支持情况。
- 如果后端已经提供了 GraphQL 接口,那 Apollo Kotlin 是首选。它的类型安全和自动代码生成能省不少事。
- 如果后端是传统的 REST 接口,Retrofit 依然是王者。别为了用 GraphQL 而强行改造后端。
- 如果项目既有 REST 又有 GraphQL,两者可以共存。我在一个项目中同时用了 Retrofit 和 Apollo,互不干扰。
嗯,这一章的内容就到这里。GraphQL 是个好东西,但要用对地方。下一章我们会深入 Apollo 的缓存机制,看看它是如何自动管理数据状态的。