跳到正文 Lab后总结 · Jimmy Huang's Blog
← Lab Week 8 Summary 学习 › C6001 › 2026 S1 › FIT5046 Mobile and distributed computing systems › Week 8 Network Connection in Android and Retrofit › Lab Week 8 Summary Lab后总结 Lab后总结 本页目录· 109 条 1. 一句话总结这个 Lab 这次 Lab 教你把 Android App 从“只会显示本地数据”升级成“会通过 Retrofit 访问 Google Custom Search API、解析 JSON、再把结果显示在 Compose UI 上”的网络型 App。
换句话说:
你不是在 App 里提前写死搜索结果,而是让 App 像一个真正的客户端一样,带着 API key、 cx 和用户输入的搜索词 q,去 Google 的 API 请求数据,然后把 Google 返回的 JSON 结果转成 Kotlin 可以使用的对象,最后显示在界面上。 2. 这个 Lab 实际做了什么 2.1 本周 Lab 的整体主线 材料可见内容显示,本周 Lab 的主题是 Retrofit and Google Search 。
这次实验不是继续做本地数据库,也不是直接在 App 里写死数据,而是让 Android App 通过网络请求访问一个外部公开 API。
整体流程可以理解成:
先在 Google Cloud / Programmable Search Engine 相关平台中准备两个关键值: API key
Search engine ID,也叫 cx
在 Android 项目中加入网络请求相关能力: Retrofit
Gson / Gson Converter
Android INTERNET 权限
创建 Kotlin data class
本页目录 ,用来接收 Google Custom Search API 返回的 JSON。
创建 Retrofit Interface,用 @GET 和 @Query 定义 HTTP GET 请求。
创建 Repository,把网络请求逻辑集中放在一个数据层里。
创建 / 使用 ViewModel,让 UI 不直接处理网络请求,而是通过 ViewModel 拿状态和结果。
在 Compose UI 中输入搜索关键词,然后显示 Google 搜索结果中的第一条 snippet。
2.2 PART 1:获取 Google Search API key 和 Search Engine ID 材料可见内容显示,实验的第一部分是准备两个 Google 侧的值。
第一个值: API key你的 App 调用 Google API 时携带的“身份证 / 凭证”。 没有 API key,Google Custom Search API 通常无法确认调用者身份,请求就可能失败。
第二个值: Search engine ID / cx你创建的 Programmable Search Engine 的编号。 API key 说明“谁在调用”。
cx 说明“调用哪一个搜索引擎配置”。
q 才说明“具体搜什么关键词”。
材料可见内容没有展示具体控制台按钮顺序,所以这里不编造具体点击路径。你只需要记住:
2.3 PART 2:在 Android 项目中用 Retrofit 调用 API 材料可见内容显示,第二部分是在 Android 项目中使用 Retrofit 调用 Google API。
步骤 1:添加 Retrofit / Gson 依赖 你需要让 Android 项目具备“发 HTTP 请求”和“解析 JSON”的能力。
如果没有 Retrofit,你仍然可以手写网络请求,但代码会更繁琐、更容易混乱。
如果没有 Gson Converter,你就需要自己手动解析 JSON,初学阶段很容易出错。
材料未显示具体 Gradle 依赖版本,因此这里不写死版本号。
步骤 2:添加 Android INTERNET 权限 Android App 默认不是你想联网就一定能联网。做网络请求时,需要在 AndroidManifest.xml 中声明网络权限。
App 在安装包里先向 Android 系统声明:“我这个 App 需要访问互联网。” 没有这个权限,App 里的 Retrofit 请求即使代码写对,也可能无法正常访问网络。
步骤 3:创建 Model / data class 映射 JSON Google Custom Search API 返回的是 JSON。
JSON 对 Android 来说,本质上是一段结构化文本,例如:
上面这个代码只是帮助理解的示例。材料可见内容只说明要用 Kotlin data class 映射 JSON response,没有显示完整类名和完整字段,因此具体文件名 / 字段名以你的实际 Lab 项目为准。
data class 不是数据库表。它只是用来接住 API 返回数据的“数据盒子”。 这点特别重要,因为材料里还提醒了一个容易混淆的地方:本次上传的 SubjectItems.zip 是 Room CRUD 示例代码,它和本周 Retrofit Lab 不是同一个项目。它可以帮助你理解 Repository / ViewModel 分层思想,但不要把 SubjectDAO、 SubjectDatabase、 SubjectEntity 这些 Room 文件复制进本次 Google Search Lab 项目。
步骤 4:创建 Retrofit Interface 定义 HTTP GET 请求 你写一个 Kotlin Interface,告诉 Retrofit:“我要用什么 HTTP 方法、访问哪个路径、带哪些 query parameters。” 这段代码也是概念解释示例,不代表材料中一定使用完全相同的类名、函数名和返回类型。
当用户输入 Paris 时,真正的请求大概可以理解成:
你不用手动拼接这一整串 URL。Retrofit 会根据 baseUrl、 @GET 和 @Query 帮你拼出来。
步骤 5:创建 Repository 隔离网络请求逻辑 Repository 可以理解为 App 的“数据经理”。
API endpoint 是什么;
需要传哪些参数;
Retrofit service 怎么创建;
返回的 JSON 长什么样;
出错时要怎么处理。
Repository 的作用是把复杂的数据来源包装成简单的方法,例如:
步骤 6:ViewModel 把数据交给 Compose UI ViewModel 可以理解为 UI 背后的“状态管理老师”。
当前是否正在 loading;
搜索结果是什么;
有没有错误信息;
用户输入的关键词是什么;
是否显示第一条 snippet。
为什么不要让 Compose UI 直接请求网络?
因为 UI 的主要职责是“显示”和“响应用户操作”,不应该承担太多数据逻辑。如果 UI 直接做网络请求,会让代码变得很乱,也更难处理重组、旋转屏幕、loading 和 error。
步骤 7:Compose UI 显示第一条 snippet 材料可见内容显示,最终目标是在 App 里输入关键词,并显示 Google 搜索结果中的第一条 snippet。
snippet 可以理解为搜索结果下面那一小段摘要。
例如你在 Google 搜索一个关键词时,每条结果通常有:
标题 title;
链接 link;
简短摘要 snippet。
本 Lab 重点不是做一个完整 Google 搜索 App,而是通过显示第一条 snippet 来证明:
3. 每个核心组件有什么用
3.1 Android App / Compose UI
它是什么 具体 UI 细节材料未完整显示,因此这里是按 Lab 目标做的概念解释。
它负责什么
没有它会发生什么 没有 UI,用户就没有地方输入关键词,也看不到搜索结果。
和其他组件的关系 UI 不应该直接和 Google API 打交道。它应该把用户输入交给 ViewModel,然后等待 ViewModel 给它状态和结果。
3.2 ViewModel
它是什么 ViewModel 是 Android 架构里专门管理 UI 状态的组件。
它在这个 Lab 里负责什么
没有它会发生什么 如果没有 ViewModel,UI 可能会直接写网络请求逻辑,代码会变得混乱。遇到屏幕旋转、重组、加载状态、错误状态时也更难维护。
和其他组件的关系
3.3 Repository
它是什么
它在这个 Lab 里负责什么 它负责调用 Retrofit Service,并把结果整理成 ViewModel 好用的形式。
没有它会发生什么 没有 Repository,ViewModel 可能直接调用 Retrofit Service。短期能跑,但代码层次不清楚,后期难维护。
和其他组件的关系 Repository 像一个数据经理,ViewModel 不需要知道底层数据到底来自网络、数据库还是缓存。
3.4 Retrofit
它是什么 Retrofit 是 Android / JVM 常用的 HTTP 客户端库。它可以把你写的 Kotlin / Java Interface 转换成真正的 HTTP 请求。
它在这个 Lab 里负责什么 它负责根据 baseUrl、 @GET、 @Query 等注解,生成请求并发送到 Google Custom Search API。
没有它会发生什么 没有 Retrofit,你仍然可以用其他网络库或手写 HTTP 请求,但代码会更啰嗦,初学时更难看懂。
和其他组件的关系
3.5 Retrofit Interface
它是什么 Retrofit Interface 是你定义 API 请求规则的地方。
它在这个 Lab 里负责什么 “我要以 GET 方式访问 Google Custom Search API,并且带上 key、cx 和 q。”
没有它会发生什么 没有 Interface,Retrofit 就不知道应该怎么发请求。
和其他组件的关系 Repository 会调用 Interface 里的函数;Retrofit 会根据这个 Interface 生成真正的请求实现。
3.6 Gson / Gson Converter
它是什么 Gson 是 JSON 和 Kotlin / Java object 之间的翻译工具。
Gson Converter 是 Retrofit 使用 Gson 的桥梁。
它在这个 Lab 里负责什么 它负责把 Google 返回的 JSON 转成 Kotlin data class 对象。
没有它会发生什么 没有 Gson Converter,你可能只能拿到原始 JSON 字符串,需要自己一层层解析 items、 snippet 等字段。
和其他组件的关系
3.7 Kotlin data class / Model classes
它是什么 data class 是 Kotlin 中用来装数据的类。
它在这个 Lab 里负责什么 它负责映射 Google Custom Search API 返回的 JSON 结构。
没有它会发生什么 没有 data class,API 返回的数据就很难以类型安全的方式使用。你可能需要手动从 JSON 字符串里找字段。
和其他组件的关系 Gson Converter 会根据 data class 的结构,把 JSON 转成 Kotlin object。
3.8 Google Custom Search API
它是什么 Google Custom Search API 是 Google 提供的搜索接口,可以让程序通过 HTTP 请求获取搜索结果。
它在这个 Lab 里负责什么
没有它会发生什么 没有这个 API,你的 App 就没有外部搜索数据来源。你只能显示本地写死数据,或者换另一个 API。
和其他组件的关系 它是外部服务,Android App 通过 Retrofit 发请求访问它。
3.9 API key
它是什么 API key 是 Google API 用来识别调用者的 key。
它在这个 Lab 里负责什么
没有它会发生什么
和其他组件的关系 它通常作为 query parameter 传给 Google API:
3.10 Search Engine ID / cx
它是什么 cx 是 Programmable Search Engine 的 ID。
它在这个 Lab 里负责什么
没有它会发生什么 Google 不知道你要使用哪个搜索引擎,搜索请求可能无法正常完成。
和其他组件的关系 它通常作为 query parameter 传给 Google API:
3.11 Query keyword / q
它是什么
它在这个 Lab 里负责什么
没有它会发生什么
和其他组件的关系 用户在 Compose UI 输入 q,然后它一路经过 ViewModel、Repository、Retrofit,最终成为 Google API 请求中的 query parameter。
3.12 Android INTERNET permission
它是什么 它是 Android Manifest 中声明 App 需要访问互联网的权限。
它在这个 Lab 里负责什么
没有它会发生什么
和其他组件的关系 它不是 Google API 的 key,也不是搜索引擎 ID。它只是 Android 系统层面的网络访问声明。
3.13 SubjectItems.zip / Room CRUD 示例
它是什么 材料可见内容提醒: SubjectItems.zip 是 Room CRUD 示例代码。
它在这个 Lab 里负责什么 它不是本周 Google Search Lab 的项目主体,只是可以帮助你理解 Repository / ViewModel 分层思想。
没有它会发生什么 没有它不影响完成 Retrofit + Google Search 主线。
和其他组件的关系 不要把 SubjectDAO、 SubjectDatabase、 SubjectEntity 等 Room 文件直接复制进本次 Google Search Lab 项目。
4. 用初学者类比解释 我们把这个 Lab 想象成一个“学校前台帮学生查资料”的故事。
4.1 整体类比
4.2 用户输入关键词
4.3 ViewModel 记录状态 前台老师不会自己跑去 Google 图书馆查资料,而是先在白板上写:
这就是 ViewModel 的作用:管理 UI 状态。
4.4 Repository 安排查询 Repository 不负责显示界面,它只负责组织数据请求。
4.5 Retrofit 负责送请求 Repository 找到快递员 Retrofit,说:
key:工作人员身份证;
cx:图书馆分馆编号;
q:学生要查的问题。
然后它把这些东西组装成一个 HTTP GET 请求。
4.6 Google API 返回 JSON 这个资料包不是普通中文,也不是 Kotlin object,而是 JSON。
4.7 Gson 把 JSON 翻译成 Kotlin object Gson 像翻译员,把 Google 返回的 JSON 翻译成 Kotlin 代码能直接使用的 data class 对象。
4.8 Compose UI 显示结果
5. 重点区分容易混淆的概念
5.1 API key 和 cx 的区别
5.2 Retrofit 和 Google API 的区别 Retrofit 不会搜索。Retrofit 只是“发请求的工具”。真正提供搜索结果的是 Google API。
5.3 Gson 和 data class 的区别
5.4 data class 和 Room Entity 的区别 本周 Lab 的重点是 Retrofit + Google Search,不是 Room 本地数据库。
SubjectDAO
SubjectDatabase
SubjectEntity
当成本周 Lab 的必要代码直接复制进 Google Search Lab。
5.5 Repository 和 ViewModel 的区别 Repository 和 ViewModel 都只是中间层,随便写在哪里都行。 Repository 关心“数据怎么来”;ViewModel 关心“界面现在应该显示什么状态”。
5.6 INTERNET permission 和 API key 的区别
5.7 @GET 和 @Query 的区别
5.8 base URL 和 endpoint path 的区别
5.9 谁能访问谁?
5.10 哪个是入口?哪个是数据库?哪个是权限控制?哪个是网络控制?
6. 这个 Lab 有什么实际用途
6.1 真实 App 基本都会用到 API 天气 App 请求天气 API;
新闻 App 请求新闻 API;
电商 App 请求商品列表 API;
地图 App 请求地点信息 API;
学校 App 请求课程、成绩、通知 API;
AI App 请求后端模型 API。
本 Lab 通过 Google Search API 让你练习最基础的 API 调用流程。
6.2 学会 Retrofit 后,你可以接任何 REST API 虽然这次用的是 Google Custom Search API,但你真正学到的是通用模式:
以后换成天气 API、电影 API、学校后端 API,思路都是类似的。
6.3 Repository + ViewModel 是 Android 项目常见分层 这个 Lab 不只是教你“怎么请求 Google”。
例如你之后做一个 FreshGuard / 食物分享 App,可能会有:
登录 API;
商品列表 API;
聊天 API;
地图 / 位置 API;
图片上传 API;
后端数据库 API。
如果你从一开始就能区分 UI、ViewModel、Repository、API Service,项目会更容易扩展和维护。
6.4 这也是后端开发和 DevOps 的基础能力 从云架构和后端角度看,这个 Lab 其实在训练你理解:
以后你部署自己的后端服务时,Android App 也会用类似方式访问你的 API。
所以本 Lab 是“移动端调用云端 API”的入门训练。
6.5 真实项目中的安全提醒 Lab 阶段为了学习,可能会把 API key 写进项目配置或代码里。
这些不一定是本 Lab 的评分重点,但是真实开发里非常重要。
7. 最后画一张生动形象的示意图 下面是一个适合初学者理解的 Mermaid 架构 / 流程图。
如果你的 Markdown 阅读器支持 Mermaid,它会渲染成图;如果不支持,也可以直接阅读里面的文字和箭头。
7.1 文字版流程图 如果 Mermaid 无法显示,可以看这个文字版:
最后你要记住的核心 这周 Lab 最重要的不是背代码,而是理解 Android App 调用外部 API 的标准结构:
用户输入关键词
→ Compose UI 把关键词交给 ViewModel
→ ViewModel 调用 Repository
→ Repository 调用 Retrofit Interface
→ Retrofit 发送 HTTP GET 请求到 Google Custom Search API
→ Google 返回 JSON
→ Gson + data class 把 JSON 转成 Kotlin object
→ ViewModel 更新状态
→ Compose UI 显示第一条 snippetGoogle API 请求通常不是只带一个搜索词就够了。
它还需要:
API key:证明调用者身份
cx:指定使用哪个 Programmable Search Engine
q:用户真正输入的搜索内容有 INTERNET 权限,不代表 Google API 一定允许你调用。
有 API key,也不代表 Android 系统一定允许 App 联网。
两个条件都要满足,网络请求才更可能成功。https://www.googleapis.com/customsearch/v1?key=你的API_KEY&cx=你的CX&q=ParisCompose UI:负责显示界面和接收用户输入
ViewModel:负责 UI 状态和调用 Repository
Repository:负责数据请求逻辑
Retrofit:负责真正发 HTTP 请求
Google API:负责返回搜索数据App 成功发出了网络请求
Google 成功返回了 JSON
你的 data class 成功接住了结果
你的 ViewModel 成功把结果交给了 UI
Compose UI 成功显示出来了Compose UI → ViewModel → RepositoryViewModel → Repository → Retrofit ServiceRepository 调用 Retrofit Interface
Retrofit 负责把 Interface 方法变成 HTTP 请求
Google API 返回 JSON
Retrofit + Converter 把结果转成 Kotlin objectGoogle API 返回 JSON
→ Gson Converter 解析 JSON
→ 变成 Kotlin data class
→ Repository / ViewModel / UI 可以直接使用对象JSON 里的 items
→ Kotlin 里的 List<SearchItem>
JSON 里的 snippet
→ Kotlin 里的 SearchItem.snippetq=Paris
q=Monash University
q=Android RetrofitRoom CRUD 示例:重点是本地数据库
Google Search Lab:重点是网络 API 请求用户 = 学生
Compose UI = 学校前台窗口
ViewModel = 前台老师的白板 / 状态记录本
Repository = 资料查询管理员
Retrofit = 专门负责送信的快递员
Google Custom Search API = Google 图书馆
API key = 学校工作人员身份证
cx = 指定图书馆分馆编号
q = 学生真正想查的问题
JSON = 图书馆寄回来的资料包
Gson = 把资料包翻译成班级能看懂格式的翻译员
Data class = 资料整理表格
snippet = 查询结果摘要key = 我是谁
cx = 用哪个搜索引擎
q = 搜什么JSON = 外语资料包
Gson = 翻译员
data class = 翻译后的表格模板INTERNET permission 是 Android 这边的门。
API key 是 Google 那边的门。
两个门都要过。@Query("q") query: Stringbase URL = 城市 / 大楼地址
endpoint path = 具体办公室
query parameters = 你要办什么业务、带了哪些材料用户不能直接操作 Google API 的 JSON。
用户通过 Compose UI 输入关键词。
Compose UI 不直接访问 Google API。
ViewModel 调用 Repository。
Repository 调用 Retrofit。
Retrofit 访问 Google Custom Search API。
Google API 返回 JSON。
结果再一路返回 UI。用户 → UI → ViewModel → Repository → Retrofit → Google API准备 endpoint
→ 定义请求方法
→ 添加 query/body/header
→ 发请求
→ 接收 JSON
→ 转成 data class
→ 显示到 UIUI 不要直接做所有事情。
网络请求要放在合适的数据层。
状态管理要交给 ViewModel。客户端如何调用后端服务
后端如何用 JSON 返回数据
API key 如何作为基础访问控制
HTTP GET 如何传递 query parameters
网络权限和外部 API 权限有什么区别Android App
→ Retrofit
→ 你的 FastAPI / Spring Boot / Node.js 后端
→ 数据库
→ JSON response
→ App UIflowchart TB
title["Lab Week 8:Android App 通过 Retrofit 访问 Google Search API"]
subgraph UserSide["用户侧"]
user["👤 用户\n输入搜索关键词 q\n例如:Paris"]
end
subgraph AndroidApp["Android App 内部"]
ui["📱 Compose UI\n搜索框 / 搜索按钮 / 结果显示区"]
vm["🧠 ViewModel\n管理 loading / result / error 状态"]
repo["📦 Repository\n统一处理数据请求"]
service["🧾 Retrofit Interface\n@GET + @Query"]
retrofit["🚚 Retrofit + Gson Converter\n组装 URL\n发送 HTTP 请求\n把 JSON 转 Kotlin object"]
manifest["🔐 AndroidManifest.xml\nINTERNET permission"]
model["🧩 Kotlin data class / Model\nSearchResponse / Item / snippet"]
end
subgraph GoogleSide["Google 侧"]
apiKey["🪪 API key\n证明是谁在调用 API"]
cx["🏷️ Search Engine ID / cx\n指定使用哪个搜索引擎"]
google["🌐 Google Custom Search API\n返回 JSON 搜索结果"]
json["📄 JSON Response\nitems[0].snippet"]
end
user -->|"输入 q"| ui
ui -->|"触发 search(keyword)"| vm
vm -->|"请求数据"| repo
repo -->|"调用 search API 方法"| service
service -->|"带上 key + cx + q"| retrofit
manifest -->|"允许 App 联网"| retrofit
retrofit -->|"HTTP GET 请求"| google
apiKey -->|"key 参数"| google
cx -->|"cx 参数"| google
google -->|"返回 JSON"| json
json -->|"Gson 解析"| model
model -->|"取第一条 snippet"| repo
repo -->|"返回结果"| vm
vm -->|"更新 UI 状态"| ui
ui -->|"显示 snippet"| user
subgraph Blocked["❌ 容易走错 / 被阻止的路径"]
room["Room / SubjectItems.zip\n本地数据库 CRUD 示例"]
noPerm["没有 INTERNET permission"]
noKey["没有 API key 或 cx"]
end
room -. "❌ 不是本 Lab 主项目\n不要复制 DAO / Entity / Database" .-> service
noPerm -. "❌ App 可能无法正常联网" .-> retrofit
noKey -. "❌ Google API 无法确认调用者或搜索引擎" .-> google
core["📌 你要记住的核心区别\nkey = 我是谁\ncx = 用哪个搜索引擎\nq = 搜什么\nRetrofit = 帮 App 发 HTTP 请求\nGson + data class = 把 JSON 翻译成 Kotlin object\nRepository / ViewModel = 把数据请求和 UI 状态分开"]
ui --> core
google --> core
classDef important fill:#E8F3FF,stroke:#2B6CB0,stroke-width:2px,color:#1A365D;
classDef google fill:#E8F8EE,stroke:#2F855A,stroke-width:2px,color:#22543D;
classDef blocked fill:#FFE6E6,stroke:#CC0000,stroke-width:2px,color:#990000;
classDef summary fill:#FFF8DC,stroke:#B7791F,stroke-width:2px,color:#744210;
class ui,vm,repo,service,retrofit,manifest,model important;
class apiKey,cx,google,json google;
class room,noPerm,noKey blocked;
class core summary;[用户输入关键词 q]
↓
[Compose UI:搜索框 + 按钮]
↓
[ViewModel:记录 loading / result / error]
↓
[Repository:统一处理数据请求]
↓
[Retrofit Interface:@GET + @Query]
↓
[Retrofit:拼 URL + 发 HTTP GET]
↓
[Android INTERNET permission:允许 App 联网]
↓
[Google Custom Search API]
↑
├── API key:证明“我是谁”
├── cx:指定“用哪个搜索引擎”
└── q:说明“搜什么”
↓
[JSON Response]
↓
[Gson + data class:转成 Kotlin object]
↓
[取 items[0].snippet]
↓
[ViewModel 更新状态]
↓
[Compose UI 显示第一条 snippet]Room / SubjectItems.zip ──❌──> 不要当成本周 Google Search Lab 主项目代码复制
没有 INTERNET permission ──❌──> App 可能无法正常联网
没有 API key / cx ──❌──> Google API 无法确认调用者或搜索引擎UI 负责显示
ViewModel 负责状态
Repository 负责数据逻辑
Retrofit 负责 HTTP 请求
Gson / data class 负责 JSON 转对象
Google Custom Search API 负责返回搜索结果
API key / cx / q 决定请求能不能正确被 Google 理解key = 我是谁
cx = 用哪个搜索引擎
q = 搜什么
Retrofit = 发请求
Gson = 翻译 JSON
data class = 装数据
Repository = 管数据请求
ViewModel = 管 UI 状态
Compose UI = 给用户看结果< uses-permission android:name= " android.permission.INTERNET " /> { " items " : [ { " title " : " ... " , " link " : " ... " , " snippet " : " ... " } ] } data class SearchResponse( val items: List<SearchItem>? ) data class SearchItem( val title: String?, val link: String?, val snippet: String? ) interface GoogleSearchApiService { @GET( "customsearch/v1" ) suspend fun search( @Query( "key" ) apiKey: String, @Query( "cx" ) searchEngineId: String, @Query( "q" ) query: String ): SearchResponse }