D09_Retrofit接口与数据转换

Android 学习笔记 D09|接入 Retrofit:从一段 JSON 到一张文章卡片

摘要:接入玩 Android 首页接口,拆开 HTTP 调用、响应 DTO、业务校验和界面模型,分别定位断网、协议错误、业务失败与解析失败。

标签:Android、Retrofit、OkHttp、网络请求、数据模型

本文对应 28 天课程 D09,以及《第一行代码》第 3 版第 11 章网络技术。今天要完成的变化是把 FakeRepository 换成真实网络来源,同时尽量保持列表界面的调用方式稳定。文中是教学片段,依赖、导包和 UI 连接需要结合工程补齐,未宣称已编译或已完成线上验证。

1. 先查看契约,再定义接口

"接口能在浏览器打开"只能证明某次 HTTP 访问有响应。接入 App 前还要确认请求方法、Base URL、参数位置、页码起点和返回包裹结构。玩 Android 首页采用 GET,路径为 article/list/{page}/json,请求页码从零开始;需要 Manifest 的网络权限。下面只读取文章列表需要的字段,不把整份返回内容搬进界面。玩 Android 开放 API

Retrofit 根据注解构造接口调用并转换响应,OkHttp 承担底层 HTTP 工作。二者之间还存在转换器:服务器返回的 JSON 字节必须按选定的序列化规则变成对象。本文选择 Kotlin Serialization,项目就应配置对应插件、运行库和匹配的 Retrofit 转换器,不再混入另一套字段注解。Retrofit 接口声明

从数据流看,卡片不应知道外层有 errorCode,也不应负责决定缺失作者显示什么。DTO 反映接口,领域或界面模型反映本地使用需求,映射是边界。这个分离能让下一次字段变化局限在数据层,也方便用受控输入测试转换规则。

2. 写出最小的 DTO 和映射

以下示例假设已配置 Json { ignoreUnknownKeys = true },Base URL 为 https://www.wanandroid.com/。Serializable 来自 Kotlin Serialization;Retrofit HTTP 注解来自 retrofit2.http。省略构建器,避免复制未知版本组合。

kotlin 复制代码
@Serializable
data class FeedEnvelope(
    val errorCode: Int,
    val errorMsg: String = "",
    val data: PageDto? = null
)

@Serializable
data class PageDto(val datas: List<ArticleDto>, val over: Boolean)

@Serializable
data class ArticleDto(
    val id: Long,
    val title: String,
    val author: String? = null,
    val shareUser: String? = null,
    val link: String
)

data class Article(val id: Long, val title: String, val author: String, val url: String)
data class ArticlePage(val items: List<Article>, val hasMore: Boolean)
class BusinessFailure(message: String) : Exception(message)

interface FeedApi {
    @GET("article/list/{page}/json")
    suspend fun page(@Path("page") page: Int): FeedEnvelope
}

fun ArticleDto.toArticle(decodeTitle: (String) -> String) = Article(
    id = id,
    title = decodeTitle(title),
    author = author?.takeIf { it.isNotBlank() }
        ?: shareUser?.takeIf { it.isNotBlank() } ?: "匿名作者",
    url = link
)

suspend fun loadPage(api: FeedApi, page: Int): ArticlePage {
    val body = api.page(page)
    if (body.errorCode != 0) throw BusinessFailure(body.errorMsg)
    val data = body.data ?: throw BusinessFailure("响应缺少文章数据")
    val items = data.datas.map { dto ->
        dto.toArticle { title ->
            HtmlCompat.fromHtml(title, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
        }
    }
    return ArticlePage(items, hasMore = !data.over)
}

标题转换使用 AndroidX HtmlCompat,这里把接口标题当作带实体或标记的展示文本,转换为纯文本;它不意味着任何外部 HTML 都可以安全放进 WebView。作者依次使用作者字段、分享者字段和本地兜底,这是本文的显示规则。若产品只承认作者字段,应修改映射和对应检查,不能默默把两种身份混成同一概念。

id、标题、链接仍然是必需字段,没有用一个随意的默认值掩盖协议变化。缺少关键身份的文章无法可靠用于列表 key、收藏主键和详情路由;是否跳过错误记录必须另定规则并记录诊断。ignoreUnknownKeys 只忽略额外字段,不代表必需字段缺失或类型错误也会自动被修复。

3. 四类失败为什么要分开

断网可能让调用在取得响应前失败;HTTP 404 或 500 是收到响应但状态不满足成功约定;HTTP 200 且业务码非零,说明接口业务拒绝或处理失败;JSON 类型不匹配则可能在转换时抛出解析异常。这个顺序帮助定位责任层,不能只用"返回了东西"判断成功。

示例让 Retrofit 直接返回响应体,非成功 HTTP 状态会通过异常体现;如果接口改成 Response<FeedEnvelope>,调用方就要检查状态与 body,不能继续套用完全相同的逻辑。Retrofit Kotlin 支持

Repository 可以把上述可预期失败转换为项目统一的错误类型,再交给 ViewModel 决定界面显示。诊断信息与面向用户的信息也不同:用户需要"网络不可用,请重试",排错需要异常类别、请求页码和关联标识。不要在博客截图里贴令牌或完整个人响应,公开教学数据同样只保留必要字段。

4. 一次可重复的数据转换实验

准备三个本地 JSON 样本:正常文章、作者空白但有分享者、标题含 &amp;。逐个送入同一转换链,预期第三个标题展示普通与号,第二个按约定展示分享者。随后故意删除文章 ID,预期转换失败或进入明确的无效记录分支,不能悄悄造出相同的零 ID。

再通过 Fake 或自己控制的测试服务分别提供业务错误、HTTP 错误和损坏 JSON。每次仅改变一个条件,记录错误在哪一层出现、界面显示什么、是否仍保留旧列表。不要为了制造异常向公开服务集中发送请求,也不要把 Fake 成功当作真实网络联调已经完成。

最后在工程里切换 Fake 与 Remote。若切换时必须修改每个卡片,说明界面依赖泄漏了;理想变化是 Repository 的装配位置以及必要的加载状态,而不是整套 UI 改写。这种可替换性也是接下来分页和离线缓存的基础。

5. 原创面试问答与追问

问一:为什么不直接把 DTO 传给 Compose? 可以在极小样例里这样做,但接口结构会渗透到 UI。追问:分模型是否一定更好?应按变化边界取舍,本项目有作者兜底、标题解码和本地收藏,映射有具体价值。

问二:HTTP 200 等于列表加载成功吗? 不等于,还要检查业务码、数据结构与转换结果。追问:业务失败该显示空列表吗?不该,成功但无数据和请求未成功是不同状态。

问三:Retrofit 与 OkHttp 可以互换吗? 它们主要职责不同,前者组织声明与转换,后者处理 HTTP 调用。追问:请求头在哪里添加?可按调用范围用声明参数或统一拦截器,不能把所有变化都塞进 UI。

这些问题为本课程原创自测,由面试鸭题库中的线程、异步基础延伸到网络实践,不表示该题库包含独立的 Retrofit 或 HTTP 专题,未复刻题库答案。

6. D09 验收与阅读衔接

验收时拿一条文章,说明它的 ID、标题、作者和 URL 从哪里来;再任选一种失败,解释它发生在 HTTP、业务还是解析层。至少保存一份经过脱敏的输入输出对照,证明规则可以重复执行。对应书本第 11 章网络请求与解析;下一课只增加分页状态,不重新发明另一套 DTO 与网络入口。

相关推荐
电商API_180079052472 小时前
闲鱼商品列表API接口商品详情API接口请求示例(含一份真实响应数据示例)
android·数据挖掘·网络爬虫·跨境电商·反向海淘
我命由我123452 小时前
Android 广播 - 组件通信
android·java·开发语言·android studio·android jetpack·android-studio·android runtime
阿俊-全栈开发2 小时前
LikeShop 单商户标准版商品管理二开:分类、SKU 规格与库存联动实现
android·likeshop·likeshop开源商城
用户2181697049305 小时前
Kotlin 语法二
android
妖星5 小时前
android 源码分析surfaceFinger是如何通过设置属性启动bootanim
android
JoyMonitor5 小时前
APK mmap 内存增长定位:pagemap 与 DEX 结构联合定位方法
android
有什么事5 小时前
永久虚拟免费云手机能实现吗,本地安卓容器与云端托管的技术边界
android·智能手机
妖星5 小时前
Android 源码 .rc 语法详解
android