此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。
简介
您构建的几乎所有 Android 应用都需要在某个时候连接到互联网。在本 Codelab 以及后续 Codelab 中,您将构建一款应用,该应用会连接到网络服务,以检索和显示数据。此外,您还会用到在之前有关 ViewModel、LiveData 和 RecyclerView 的 Codelab 中学到的内容。
在此 Codelab 中,您将使用社区开发的库构建网络层。这可以极大地简化数据和图片提取流程,还有助于应用遵循某些 Android 最佳做法,例如在后台线程上加载图片以及缓存已加载的图片。对于代码中的异步或非阻塞部分,例如与网络服务层通信,您将修改该应用以使用 Kotlin 的协程。如果互联网速度较慢或不可用,应用的界面也会更新,以便用户了解相关情况。
您应当已掌握的内容
- 如何创建和使用 fragment。
 - 如何在 fragment 之间导航,以及如何使用 
safeArgs在 fragment 之间传递数据。 - 如何使用架构组件,包括 
ViewModel、ViewModelProvider.Factory、LiveData和LiveData转换。 - 如何使用协程管理长时间运行的任务。
 
学习内容
实践内容
- 修改起始应用以发出网络服务 API 请求,并处理响应。
 - 使用 Retrofit 库为您的应用实现网络层。
 - 使用 Moshi 库将网络服务的 JSON 响应解析为应用的实时数据。
 - 使用 Retrofit 对协程的支持来简化代码。
 
在此 Codelab(和接下来的 Codelab)中,您将使用一款名为 MarsRealEstate 的初始应用,该应用会显示火星上的待售资源。此应用需要连接到网络服务才能检索和显示资源数据,包括价格以及资源是可出售还是可租赁等详细信息。代表各项资源的图片是由 NASA 的火星探测器拍摄的真实照片。

您在此 Codelab 中构建的应用版本不会有很多视觉上的亮点;它侧重于应用的网络层部分,旨在连接到互联网并使用网络服务下载原始资源数据。为了确保正确检索和解析数据,只需在文本视图中输出火星上的资源数:

.
MarsRealEstate 应用的架构包含两大模块:
- 概览 fragment,其中包含使用 
RecyclerView构建的缩略图资源图片网格。 - 详情视图 fragment,其中包含每项资源的相关信息。
 

对于每个 fragment,该应用都有一个 ViewModel。在此 Codelab 中,您将为网络服务创建一个层,ViewModel 将直接与该网络层进行通信。这类似于您在之前的 Codelab 中在 ViewModel 与 Room 数据库通信时执行的操作。
概览 ViewModel 负责发出网络调用以获取火星地产信息。详情 ViewModel 用于保存在详情 fragment 中显示的单个火星地产资源的详细信息。对于每个 ViewModel,您会结合使用 LiveData 和生命周期感知型数据绑定,以在数据发生更改时更新应用界面。
您使用 Navigation 组件在两个 fragment 之间导航,并将选定属性作为参数传递。
在此任务中,您将下载并运行 MarsRealEstate 起始应用,并熟悉该项目的结构。
第 1 步:探索 fragment 和导航
- 下载 MarsRealEstate 起始应用,并在 Android Studio 中打开它。
 - 检查 
app/java/MainActivity.kt。该应用对两个屏幕都使用 fragment,因此 activity 的唯一任务就是加载 activity 的布局。 - 检查 
app/res/layout/activity_main.xml。activity 布局是导航文件中定义的两个 fragment 的宿主。此布局会使用nav_graph资源实例化NavHostFragment及其关联的导航控制器。 - 打开 
app/res/navigation/nav_graph.xml。在这里,您可以看到这两个 fragment 之间的导航关系。导航图StartDestination指向overviewFragment,因此在应用启动时,概览 fragment 会实例化。 
第 2 步:探索 Kotlin 源文件和数据绑定
- 在 Project 窗格中,展开 app > java。请注意,MarsRealEstate 应用有三个软件包文件夹:
detail、network和overview。这些文件夹对应于应用的三大组件:概览 fragment 和详情 fragment,以及网络层代码。
   - 打开 
app/java/overview/OverviewFragment.kt。OverviewFragment会延迟初始化OverviewViewModel,也就是说,OverviewViewModel会在第一次使用时创建。 - 检查 
onCreateView()方法。此方法使用数据绑定膨胀fragment_overview布局,将绑定生命周期所有者设置为自身 (this),并为其设置binding对象中的viewModel变量。由于我们设置了生命周期所有者,因此系统会自动观察数据绑定中使用的任何LiveData是否有任何更改,并相应地更新界面。 - 打开 
app/java/overview/OverviewViewModel。由于响应为LiveData,并且我们已为绑定变量设置了生命周期,因此对其所做的任何更改都将更新应用界面。 - 检查 
init代码块。在创建ViewModel后,它会调用getMarsRealEstateProperties()方法。 - 检查 
getMarsRealEstateProperties()方法。在此起始应用中,此方法包含占位符响应。此 Codelab 的目标是使用您从互联网获得的真实数据更新ViewModel中的LiveData。 - 打开 
app/res/layout/fragment_overview.xml。这是您在此 Codelab 中使用的概览 fragment 的布局,它包含视图模型的数据绑定。它会导入OverviewViewModel,然后将ViewModel的响应绑定到TextView。在后续的 Codelab 中,您需要在RecyclerView中将文本视图替换为图片网格。 - 编译并运行应用。您在此应用的当前版本中只会看到初始响应:“Set the Mars API Response here!”
 
 
火星地产数据以 REST 网络服务的形式存储在网络服务器上。采用 REST 架构的网络服务是使用标准网络组件和协议构建的。
您可通过 URI 以标准化方式向网络服务发出请求。熟悉的网址实际上是一种 URI,在这一整节课程中,它们可以互换使用。例如,在本节课的应用中,您将从以下服务器检索所有数据:
https://android-kotlin-fun-mars-server.appspot.com
如果在浏览器中输入以下网址,您将看到火星上所有可用的地产资源列表!
https://android-kotlin-fun-mars-server.appspot.com/realestate
网络服务的响应通常会采用 JSON 格式,这是一种表示结构化数据的交换格式。您将在下一项任务中详细了解 JSON,但简而言之,JSON 对象是键值对集合,有时称为字典、哈希映射或关联数组。JSON 对象集合是一个 JSON 数组,它是作为网络服务的响应返回的数组。
如需将这些数据传输到您的应用中,应用需要建立网络连接并与相应服务器通信,然后接收响应数据并将其解析为应用可以使用的格式。在此 Codelab 中,您将使用名为 Retrofit 的 REST 客户端库建立此连接。
第 1 步:将 Retrofit 依赖项添加到 Gradle
- 打开 build.gradle (Module: app)。
 - 在 
dependencies部分中,为 Retrofit 库添加以下代码行: 
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
请注意,版本号是在项目 Gradle 文件中单独定义的。第一个依赖项用于 Retrofit 2 库本身,而第二个依赖项则用于 Retrofit 标量转换器。此转换器允许 Retrofit 将 JSON 结果作为 String 返回。这两个库协同工作。
- 点击 Sync Now,以使用新的依赖项重建项目。
 
第 2 步:实现 MarsApiService
Retrofit 会根据网络服务的内容为应用创建网络 API。它从网络服务提取数据,并通过独立的转换器库来路由数据。该库知道如何解码数据,并以实用对象的形式返回这些数据。Retrofit 内置对 XML 和 JSON 等常用网络数据格式的支持。Retrofit 最终会为您创建大部分网络层,包括关键详细信息,例如在后台线程上运行请求。
MarsApiService 类用于保存应用的网络层;也就是说,您的 ViewModel 将使用这个 API 与网络服务通信。您将在该类中实现 Retrofit 服务 API。
- 打开 
app/java/network/MarsApiService.kt。现在,该文件仅包含一项内容:网络服务的基准网址的一个常量。 
private const val BASE_URL =
   "https://android-kotlin-fun-mars-server.appspot.com"- 在该常量的正下方,使用 Retrofit 构建器创建一个 Retrofit 对象。在收到请求时,导入 
retrofit2.Retrofit和retrofit2.converter.scalars.ScalarsConverterFactory。 
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()
Retrofit 至少需要以下两项内容才能构建网络服务 API:网络服务的基础 URI 和转换器工厂。转换器会告知 Retrofit 如何处理它从网络服务获取的数据。在这种情况下,您需要 Retrofit 从网络服务提取 JSON 响应,并将该响应作为 String 返回。Retrofit 包含一个 ScalarsConverter,它支持字符串和其他基元类型,因此,您可以使用 ScalarsConverterFactory 的实例对构建器调用 addConverterFactory()。最后,调用 build() 以创建 Retrofit 对象。
- 在对 Retrofit 构建器的调用的正下方,定义一个接口,该接口定义 Retrofit 如何使用 HTTP 请求与网络服务器通信。在收到请求时,导入 
retrofit2.http.GET和retrofit2.Call。 
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}现在的目标是从网络服务获取 JSON 响应字符串,而您只需要一种方法即可:getProperties()。如需告知 Retrofit 此方法应执行的操作,请使用 @GET 注解,并为该网络服务方法指定路径或端点。在本示例中,端点称为 realestate。调用 getProperties() 方法时,Retrofit 会将端点 realestate 附加到基准网址(由您在 Retrofit 构建器中定义),并创建一个 Call 对象。这个 Call 对象用于启动该请求。
- 在 
MarsApiService接口下,定义一个名为MarsApi的公共对象,以初始化 Retrofit 服务。 
object MarsApi {
    val retrofitService : MarsApiService by lazy {
       retrofit.create(MarsApiService::class.java) }
}Retrofit create() 方法会通过 MarsApiService 接口本身创建 Retrofit 服务。由于此调用的成本很高,并且应用只需要一个 Retrofit 服务实例,因此,您可以使用名为 MarsApi 的公共对象向应用的其余部分公开该服务,然后在其中延迟初始化 Retrofit 服务。现在,所有设置均已完成,每次您的应用调用 MarsApi.retrofitService 时,它都会获得一个实现 MarsApiService 的单例 Retrofit 对象。
第 3 步:在 OverviewViewModel 中调用网络服务
- 打开 
app/java/overview/OverviewViewModel.kt。向下滚动到getMarsRealEstateProperties()方法。 
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}这将在该方法中调用 Retrofit 服务并处理返回的 JSON 字符串。目前,该响应中只有一个占位符字符串。
- 删除用于将响应设置为“Set the Mars API Response here!”的占位符行。
 - 在 
getMarsRealEstateProperties()中,添加如下所示的代码。在收到请求时,导入retrofit2.Callback和com.example.android.marsrealestate.network.MarsApi。MarsApi.retrofitService.getProperties()方法会返回一个Call对象。然后,您可以对该对象调用enqueue(),以在后台线程上启动网络请求。 
MarsApi.retrofitService.getProperties().enqueue(
   object: Callback<String> {
})- 点击 
object一词(带有红色下划线)。依次选择 Code > Implement methods。从列表中选择onResponse()和onFailure()。
Android Studio 在每种方法中都添加了带有 TODO 的代码: 
override fun onFailure(call: Call<String>, t: Throwable) {
       TODO("not implemented")
}
override fun onResponse(call: Call<String>,
   response: Response<String>) {
       TODO("not implemented")
}- 在 
onFailure()中,删除 TODO,并将_response设置为失败消息,如下所示。_response是一个LiveData字符串,用于确定文本视图中显示的内容。每种状态都需要更新_responseLiveData。
当网络服务响应失败时,系统会调用onFailure()回调。对于此响应,请将_response状态设置为"Failure: ",并与Throwable参数中的消息串联起来。 
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}- 在 
onResponse()中,删除 TODO,并将_response设置为响应正文。当请求成功且网络服务返回响应时,系统会调用onResponse()回调。 
override fun onResponse(call: Call<String>,
   response: Response<String>) {
      _response.value = response.body()
}第 4 步:定义互联网权限
- 编译并运行 MarsRealEstate 应用。请注意,该应用会立即关闭并报错。
   - 点击 Android Studio 中的 Logcat 标签页,并记下日志中以如下所示的代码行开头的错误消息:
 
Process: com.example.android.marsrealestate, PID: 10646 java.lang.SecurityException: Permission denied (missing INTERNET permission?)
此错误消息表示您的应用可能缺少 INTERNET 权限。连接到互联网会引起安全问题,因此我们默认应用没有连接互联网。您需要明确告知 Android:该应用需要访问互联网。
- 打开 
app/manifests/AndroidManifest.xml。将下面这行代码添加到<application>标签的前面: 
<uses-permission android:name="android.permission.INTERNET" />- 编译并再次运行应用。如果互联网连接一切正常,您就能看到包含火星资源数据的 JSON 文本。

 - 点按设备或模拟器中的返回按钮关闭应用。
 - 将设备或模拟器设为飞行模式,然后从“最近”菜单中重新打开该应用,或从 Android Studio 中重启该应用。
 

- 再次关闭飞行模式。
 
现在,您将从 Mars 网络服务获得 JSON 响应,这是一个不错的开始。但您实际上需要的是 Kotlin 对象,而不是大型 JSON 字符串。有一个名为 Moshi 的库,它是一个 Android JSON 解析器,可将 JSON 字符串转换为 Kotlin 对象。Retrofit 库具有可与 Moshi 配合使用的转换器,在这里非常适用。
在此任务中,您将配合使用 Moshif 库和 Retrofit,将网络服务的 JSON 响应解析成有用的火星资源 Kotlin 对象。您需要更改应用,而不是显示原始 JSON,而该应用会显示返回的火星资源数。
第 1 步:添加 Moshi 库依赖项
- 打开 build.gradle (Module: app)。
 - 在依赖项部分中,添加如下所示的代码以包含 Moshi 依赖项。与 Retrofit 一样,
$version_moshi是在项目级 Gradle 文件中单独定义的。这些依赖项添加了对核心 Moshi JSON 库的支持,还为 Moshi 提供了 Kotlin 支持。 
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"- 在 
dependencies代码块中,找到 Retrofit 标量转换器所在的代码行: 
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"- 将该代码行更改为使用 
converter-moshi: 
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"- 点击 Sync Now,以使用新的依赖项重建项目。
 
第 2 步:实现 MarsProperty 数据类
您从网络服务中获取的 JSON 响应的示例条目类似于如下所示:
[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]上面显示的 JSON 响应是一个数组(以英文方括号表示)。该数组包含 JSON 对象,这些对象括在英文花括号中。每个对象都包含一组名称值对,用英文冒号分隔。名称会用英文引号引起来中。值可以是数字或字符串,字符串也会用英文引号引起来。例如,此资源的 price 为 $450000,而 img_src 是一个网址,即图片文件在服务器上的位置。
在上面的示例中,请注意每项火星资源条目具有以下 JSON 键和值对:
price:火星资源的价格,用数字表示。id:资源的 ID,用字符串表示。type:"rent"或"buy"。img_src:图片的网址,用字符串表示。
Moshi 会解析此 JSON 数据并将其转换为 Kotlin 对象。为此,它需要一个 Kotlin 数据类来存储解析后的结果,因此下一步是创建该类。
- 打开 
app/java/network/MarsProperty.kt。 - 将现有的 
MarsProperty类定义替换为以下代码: 
data class MarsProperty(
   val id: String, val img_src: String,
   val type: String,
   val price: Double
)请注意,MarsProperty 类中的每个变量都对应于 JSON 对象中的一个键名。为了匹配 JSON 中的类型,您可以为除 price(为 Double)以外的所有值使用 String 对象。Double 可用于表示任何 JSON 数值。
Moshi 解析 JSON 时,它会按名称匹配键,并用适当的值填充数据对象。
- 将 
img_src键所在的代码行替换为如下所示的代码行。在收到请求时,导入com.squareup.moshi.Json。 
@Json(name = "img_src") val imgSrcUrl: String,有时,JSON 响应中的键名可能会使 Kotlin 属性混淆,或者可能与您的编码样式不匹配。例如,在 JSON 文件中,img_src 键使用下划线,而 Kotlin 属性通常使用大写和小写字母(“驼峰式大小写”)。
如需在数据类中使用与 JSON 响应中的键名不同的变量名称,请使用 @Json 注解。在此示例中,数据类中变量的名称为 imgSrcUrl。可以使用 @Json(name = "img_src") 将该变量映射到 JSON 属性 img_src。
第 3 步:更新 MarsApiService 和 OverviewViewModel
添加 MarsProperty 数据类后,您现在可以更新网络 API 和 ViewModel,以包含 Moshi 数据。
- 打开 
network/MarsApiService.kt。您可能会看到ScalarsConverterFactory出现类缺失错误。这是因为您在第 1 步中更改了 Retrofit 依赖项。您很快便可以修复这些错误。 - 在文件顶部,在 Retrofit 构建器的前面添加以下代码以创建 Moshi 实例。在收到请求时,导入 
com.squareup.moshi.Moshi和com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory。 
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()与处理 Retrofit 的操作类似,在这里,您需要使用 Moshi 构建器创建一个 moshi 对象。为了让 Moshi 注解能够在 Kotlin 中正常使用,请添加 KotlinJsonAdapterFactory,然后调用 build()。
- 将 Retrofit 构建器更改为使用 
MoshiConverterFactory而不是ScalarConverterFactory,并传入您刚刚创建的moshi实例。在收到请求时,导入retrofit2.converter.moshi.MoshiConverterFactory。 
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()- 同时删除 
ScalarConverterFactory的导入作业。 
要删除的代码:
import retrofit2.converter.scalars.ScalarsConverterFactory- 更新 
MarsApiService接口,让 Retrofit 返回MarsProperty对象列表,而不是返回Call<String>。 
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}- 打开 
OverviewViewModel.kt。在getMarsRealEstateProperties()方法中,向下滚动到对getProperties().enqueue()的调用。 - 将 
enqueue()的参数从Callback<String>更改为Callback<List<MarsProperty>>。在收到请求时,导入com.example.android.marsrealestate.network.MarsProperty。 
MarsApi.retrofitService.getProperties().enqueue(
   object: Callback<List<MarsProperty>> {- 在 
onFailure()中,将参数从Call<String>更改为Call<List<MarsProperty>>: 
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {- 对 
onResponse()的两个参数进行同样的更改: 
override fun onResponse(call: Call<List<MarsProperty>>,
   response: Response<List<MarsProperty>>) {- 在 
onResponse()的正文中,将现有的对_response.value的赋值替换为如下所示的赋值。由于response.body()现在是一个MarsProperty对象列表,因此该列表的大小就是已解析的资源数。此响应消息会输出资源数: 
_response.value =
   "Success: ${response.body()?.size} Mars properties retrieved"- 确保飞行模式已关闭。编译并运行应用。这一次,消息应显示网络服务返回的资源数:

 
现在,Retrofit API 服务正在运行,但它所用的回调带有的两个回调方法必须由您实现。一种方法用于处理成功情况,另一方法用于处理失败情况,失败结果会报告异常。如果将协程与异常处理机制搭配使用(而不是使用回调),您的代码将变得更高效、更易读。Retrofit 有一个库,可便捷地集成协程。
在此任务中,您将转换网络服务和 ViewModel,以使用协程。
第 1 步:添加协程依赖项
- 打开 build.gradle (Module: app)。
 - 在依赖项部分中,添加对核心 Kotlin 协程库和 Retrofit 协程库的支持:
 
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines" implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
- 点击 Sync Now,以使用新的依赖项重建项目。
 
第 2 步:更新 MarsApiService 和 OverviewViewModel
- 在 
MarsApiService.kt中,更新 Retrofit 构建器以使用CoroutineCallAdapterFactory。完整的构建器现在如下所示: 
private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl(BASE_URL)
        .build()调用适配器为 Retrofit 增添了功能,使其能够创建可返回除默认 Call 类之外的内容的 API。在这种情况下,我们可以使用 CoroutineCallAdapterFactory 将 getProperties() 返回的 Call 对象替换为 Deferred 对象。
- 在 
getProperties()方法中,将Call<List<MarsProperty>>更改为Deferred<List<MarsProperty>>。在收到请求时,导入kotlinx.coroutines.Deferred。完整的getProperties()方法如下所示: 
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>Deferred 接口定义了一个可返回结果值的协程作业(Deferred 继承自 Job)。Deferred 接口包含一个名为 await() 的方法,该方法可让代码在不阻塞的情况下等待,直到相应值准备就绪,然后返回该值。
- 打开 
OverviewViewModel.kt。在init代码块的前面,添加一个协程作业: 
private var viewModelJob = Job()- 使用主调度程序为该新作业创建一个协程作用域:
 
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )Dispatchers.Main 调度程序在运行时会使用界面线程。由于 Retrofit 会在后台线程上执行所有工作,因此没有理由使用其他任何线程的作用域。这样,您在获取结果时就可以轻松更新 MutableLiveData 的值。
- 删除 
getMarsRealEstateProperties()中的所有代码。在这里,您将使用协程,而不是调用enqueue()以及onFailure()和onResponse()回调。 - 在 
getMarsRealEstateProperties()中,启动协程: 
coroutineScope.launch {
}
如需使用 Retrofit 针对网络任务返回的 Deferred 对象,您必须在协程内执行操作,因此您可以在此处启动刚刚创建的协程。您仍在主线程上执行代码,但现在要让协程管理并发。
- 在启动代码块内,对 
retrofitService对象调用getProperties(): 
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()从 MarsApi 服务调用 getProperties() 会对后台线程创建并发起网络调用,从而返回该任务的 Deferred 对象。
- 此外,在启动代码块内,添加一个 
try/catch代码块来处理异常: 
try {
} catch (e: Exception) {
}- 在 
try {}代码块内,对Deferred对象调用await(): 
var listResult = getPropertiesDeferred.await()在相应值准备就绪后,对 Deferred 对象调用 await() 将返回网络调用的结果。await() 方法是非阻塞方法,因此 Mars API 服务会从网络中检索数据,而不会阻塞当前线程,这一点非常重要,因为我们处于界面线程的作用域内。任务完成后,您的代码会从上次中断的位置继续执行。这是在 try {} 内,因此您可以捕获异常。
- 同样,在 
try {}代码块内的await()方法之后,更新成功响应的响应消息: 
_response.value =
   "Success: ${listResult.size} Mars properties retrieved"- 在 
catch {}代码块内,处理故障响应: 
_response.value = "Failure: ${e.message}"
完整的 getMarsRealEstateProperties() 方法现在看上去会像下面这样:
private fun getMarsRealEstateProperties() {
   coroutineScope.launch {
       var getPropertiesDeferred =
          MarsApi.retrofitService.getProperties()
       try {
           _response.value =
              "Success: ${listResult.size} Mars properties retrieved"
       } catch (e: Exception) {
           _response.value = "Failure: ${e.message}"
       }
   }
}- 在该类的底部,使用以下代码添加 
onCleared()回调: 
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}在 ViewModel 被销毁后,数据加载应该会停止,因为使用此 ViewModel 的 OverviewFragment 会消失。如需在 ViewModel 被销毁后停止加载,您可以替换 onCleared() 以取消作业。
- 编译并运行应用。这次的结果与上一个任务(资源数报告)相同,但代码和错误处理机制更为简单。
 
Android Studio 项目:MarsRealEstateNetwork
REST 网络服务
- 网络服务是互联网上的一项服务,可让您的应用发出请求并获取返回的数据。
 - 常见网络服务使用的是 REST 架构。提供 REST 架构的网络服务称为 RESTful 服务。RESTful 网络服务是使用标准网络组件和协议构建的。
 - 您可通过 URI 以标准化方式向 REST 网络服务发出请求。
 - 要使用网络服务,应用必须建立网络连接,然后与该服务进行通信。然后,应用必须接收响应数据,并将该数据解析成应用可以使用的格式。
 - Retrofit 库是一个客户端库,可让应用向 REST 网络服务发出请求。
 - 使用转换器指示 Retrofit 如何处理它发送至网络服务的数据,以及它从网络服务获取的返回数据。例如,
ScalarsConverter转换器会将网络服务数据视为String或其他基元。 - 如需让应用能够连接到互联网,请在 Android 清单中添加 
"android.permission.INTERNET"权限。 
JSON 解析
- 网络服务的响应通常会采用 JSON 格式,这是一种表示结构化数据的常用交换格式。
 - JSON 对象是键值对的集合。此集合有时称为字典、哈希映射或关联数组。
 - JSON 对象集合是一个 JSON 数组。作为网络服务的响应,您会得到一个 JSON 数组。
 - 键值对中的键会用英文引号引起来。值可以是数字或字符串。字符串也会用英文引号引起来。
 - Moshi 库是一种 Android JSON 解析器,可将 JSON 字符串转换为 Kotlin 对象。Retrofit 包含一个可配合 Moshi 使用的转换器。
 - Moshi 可将 JSON 响应中的键与具有相同名称的数据对象中的属性进行匹配。
 - 如需为键使用不同的属性名称,请使用 
@Json注解和 JSON 键名为该属性添加注解。 
Retrofit 和协程
- 调用适配器使 Retrofit 能够创建可返回除默认 
Call类之外的内容的 API。使用CoroutineCallAdapterFactory类可将Call替换为协程Deferred。 - 对 
Deferred对象使用await()方法,可让协程代码在不阻塞的情况下等待,直到相应值准备就绪,然后返回该值。 
Udacity 课程:
Android 开发者文档:
Kotlin 文档:
其他:
此部分列出了在由讲师主导的课程中,学生学习此 Codelab 后可能需要完成的家庭作业。讲师自行决定是否执行以下操作:
- 根据需要布置作业。
 - 告知学生如何提交家庭作业。
 - 给家庭作业评分。
 
讲师可以酌情采纳这些建议,并且可以自由布置自己认为合适的任何其他家庭作业。
如果您是在自学此 Codelab,可随时通过这些家庭作业来检测您的知识掌握情况。
回答以下问题
问题 1
Retrofit 构建网络服务 API 需要具备哪两大关键工具?
▢ 网络服务的基础 URI 以及 GET 查询。
▢ 网络服务的基础 URI 以及转换器工厂。
▢ 与网络服务的网络连接和授权令牌。
▢ 转换器工厂和响应解析器。
问题 2
Moshi 库的用途是什么?
▢ 获取网络服务返回的数据。
▢ 与 Retrofit 交互以发出网络服务请求。
▢ 将网络服务的 JSON 响应解析为 Kotlin 数据对象。
▢ 重命名 Kotlin 对象以匹配 JSON 响应中的键。
问题 3
Retrofit 调用适配器的用途是什么?
▢ 它们使 Retrofit 可以使用协程。
▢ 它们将网络服务响应调整为 Kotlin 数据对象。
▢ 它们将 Retrofit 调用改为网络服务调用。
▢ 它们在 Retrofit 中增添了功能,使其能够返回除默认 Call 类之外的内容。
开始学习下一课:
如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页。