Android Kotlin 基础知识 08.1:从互联网获取数据

此 Codelab 是“Android Kotlin 基础知识”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘此课程的价值。“Android Kotlin 基础知识”Codelab 着陆页列出了所有课程 Codelab。

简介

您构建的几乎所有 Android 应用都需要在某个时候连接到互联网。在本 Codelab 以及后续 Codelab 中,您将构建一款应用,该应用会连接到网络服务,以检索和显示数据。此外,您还会用到在之前有关 ViewModelLiveDataRecyclerView 的 Codelab 中学到的内容。

在此 Codelab 中,您将使用社区开发的库构建网络层。这可以极大地简化数据和图片提取流程,还有助于应用遵循某些 Android 最佳做法,例如在后台线程上加载图片以及缓存已加载的图片。对于代码中的异步或非阻塞部分,例如与网络服务层通信,您将修改该应用以使用 Kotlin 的协程。如果互联网速度较慢或不可用,应用的界面也会更新,以便用户了解相关情况。

您应当已掌握的内容

  • 如何创建和使用 fragment。
  • 如何在 fragment 之间导航,以及如何使用 safeArgs 在 fragment 之间传递数据。
  • 如何使用架构组件,包括 ViewModelViewModelProvider.FactoryLiveDataLiveData 转换。
  • 如何使用协程管理长时间运行的任务。

学习内容

  • 什么是 REST 网络服务。
  • 使用 Retrofit 库连接到互联网上的 REST 网络服务,并获取响应。
  • 使用 Moshi 库将 JSON 响应解析为数据对象。

实践内容

  • 修改起始应用以发出网络服务 API 请求,并处理响应。
  • 使用 Retrofit 库为您的应用实现网络层。
  • 使用 Moshi 库将网络服务的 JSON 响应解析为应用的实时数据。
  • 使用 Retrofit 对协程的支持来简化代码。

在此 Codelab(和接下来的 Codelab)中,您将使用一款名为 MarsRealEstate 的初始应用,该应用会显示火星上的待售资源。此应用需要连接到网络服务才能检索和显示资源数据,包括价格以及资源是可出售还是可租赁等详细信息。代表各项资源的图片是由 NASA 的火星探测器拍摄的真实照片。

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

.

MarsRealEstate 应用的架构包含两大模块:

  • 概览 fragment,其中包含使用 RecyclerView 构建的缩略图资源图片网格。
  • 详情视图 fragment,其中包含每项资源的相关信息。

对于每个 fragment,该应用都有一个 ViewModel。在此 Codelab 中,您将为网络服务创建一个层,ViewModel 将直接与该网络层进行通信。这类似于您在之前的 Codelab 中在 ViewModelRoom 数据库通信时执行的操作。

概览 ViewModel 负责发出网络调用以获取火星地产信息。详情 ViewModel 用于保存在详情 fragment 中显示的单个火星地产资源的详细信息。对于每个 ViewModel,您会结合使用 LiveData 和生命周期感知型数据绑定,以在数据发生更改时更新应用界面。

您使用 Navigation 组件在两个 fragment 之间导航,并将选定属性作为参数传递。

在此任务中,您将下载并运行 MarsRealEstate 起始应用,并熟悉该项目的结构。

第 1 步:探索 fragment 和导航

  1. 下载 MarsRealEstate 起始应用,并在 Android Studio 中打开它。
  2. 检查 app/java/MainActivity.kt。该应用对两个屏幕都使用 fragment,因此 activity 的唯一任务就是加载 activity 的布局。
  3. 检查 app/res/layout/activity_main.xml。activity 布局是导航文件中定义的两个 fragment 的宿主。此布局会使用 nav_graph 资源实例化 NavHostFragment 及其关联的导航控制器。
  4. 打开 app/res/navigation/nav_graph.xml。在这里,您可以看到这两个 fragment 之间的导航关系。导航图 StartDestination 指向 overviewFragment,因此在应用启动时,概览 fragment 会实例化。

第 2 步:探索 Kotlin 源文件和数据绑定

  1. Project 窗格中,展开 app > java。请注意,MarsRealEstate 应用有三个软件包文件夹:detailnetworkoverview。这些文件夹对应于应用的三大组件:概览 fragment 和详情 fragment,以及网络层代码。
  2. 打开 app/java/overview/OverviewFragment.ktOverviewFragment 会延迟初始化 OverviewViewModel,也就是说,OverviewViewModel 会在第一次使用时创建。
  3. 检查 onCreateView() 方法。此方法使用数据绑定膨胀 fragment_overview 布局,将绑定生命周期所有者设置为自身 (this),并为其设置 binding 对象中的 viewModel 变量。由于我们设置了生命周期所有者,因此系统会自动观察数据绑定中使用的任何 LiveData 是否有任何更改,并相应地更新界面。
  4. 打开 app/java/overview/OverviewViewModel。由于响应为 LiveData,并且我们已为绑定变量设置了生命周期,因此对其所做的任何更改都将更新应用界面。
  5. 检查 init 代码块。在创建 ViewModel 后,它会调用 getMarsRealEstateProperties() 方法。
  6. 检查 getMarsRealEstateProperties() 方法。在此起始应用中,此方法包含占位符响应。此 Codelab 的目标是使用您从互联网获得的真实数据更新 ViewModel 中的 LiveData
  7. 打开 app/res/layout/fragment_overview.xml。这是您在此 Codelab 中使用的概览 fragment 的布局,它包含视图模型的数据绑定。它会导入 OverviewViewModel,然后将 ViewModel 的响应绑定到 TextView。在后续的 Codelab 中,您需要在 RecyclerView 中将文本视图替换为图片网格。
  8. 编译并运行应用。您在此应用的当前版本中只会看到初始响应:“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

  1. 打开 build.gradle (Module: app)
  2. dependencies 部分中,为 Retrofit 库添加以下代码行:
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"


请注意,版本号是在项目 Gradle 文件中单独定义的。第一个依赖项用于 Retrofit 2 库本身,而第二个依赖项则用于 Retrofit 标量转换器。此转换器允许 Retrofit 将 JSON 结果作为 String 返回。这两个库协同工作。

  1. 点击 Sync Now,以使用新的依赖项重建项目。

第 2 步:实现 MarsApiService

Retrofit 会根据网络服务的内容为应用创建网络 API。它从网络服务提取数据,并通过独立的转换器库来路由数据。该库知道如何解码数据,并以实用对象的形式返回这些数据。Retrofit 内置对 XML 和 JSON 等常用网络数据格式的支持。Retrofit 最终会为您创建大部分网络层,包括关键详细信息,例如在后台线程上运行请求。

MarsApiService 类用于保存应用的网络层;也就是说,您的 ViewModel 将使用这个 API 与网络服务通信。您将在该类中实现 Retrofit 服务 API。

  1. 打开 app/java/network/MarsApiService.kt。现在,该文件仅包含一项内容:网络服务的基准网址的一个常量。
private const val BASE_URL =
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. 在该常量的正下方,使用 Retrofit 构建器创建一个 Retrofit 对象。在收到请求时,导入 retrofit2.Retrofitretrofit2.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 对象。

  1. 在对 Retrofit 构建器的调用的正下方,定义一个接口,该接口定义 Retrofit 如何使用 HTTP 请求与网络服务器通信。在收到请求时,导入 retrofit2.http.GETretrofit2.Call
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}

现在的目标是从网络服务获取 JSON 响应字符串,而您只需要一种方法即可:getProperties()。如需告知 Retrofit 此方法应执行的操作,请使用 @GET 注解,并为该网络服务方法指定路径或端点。在本示例中,端点称为 realestate。调用 getProperties() 方法时,Retrofit 会将端点 realestate 附加到基准网址(由您在 Retrofit 构建器中定义),并创建一个 Call 对象。这个 Call 对象用于启动该请求。

  1. 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 中调用网络服务

  1. 打开 app/java/overview/OverviewViewModel.kt。向下滚动到 getMarsRealEstateProperties() 方法。
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}

这将在该方法中调用 Retrofit 服务并处理返回的 JSON 字符串。目前,该响应中只有一个占位符字符串。

  1. 删除用于将响应设置为“Set the Mars API Response here!”的占位符行。
  2. getMarsRealEstateProperties() 中,添加如下所示的代码。在收到请求时,导入 retrofit2.Callbackcom.example.android.marsrealestate.network.MarsApi

    MarsApi.retrofitService.getProperties() 方法会返回一个 Call 对象。然后,您可以对该对象调用 enqueue(),以在后台线程上启动网络请求。
MarsApi.retrofitService.getProperties().enqueue(
   object: Callback<String> {
})
  1. 点击 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")
}
  1. onFailure() 中,删除 TODO,并将 _response 设置为失败消息,如下所示。_response 是一个 LiveData 字符串,用于确定文本视图中显示的内容。每种状态都需要更新 _response LiveData

    当网络服务响应失败时,系统会调用 onFailure() 回调。对于此响应,请将 _response 状态设置为 "Failure: ",并与 Throwable 参数中的消息串联起来。
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}
  1. onResponse() 中,删除 TODO,并将 _response 设置为响应正文。当请求成功且网络服务返回响应时,系统会调用 onResponse() 回调。
override fun onResponse(call: Call<String>,
   response: Response<String>) {
      _response.value = response.body()
}

第 4 步:定义互联网权限

  1. 编译并运行 MarsRealEstate 应用。请注意,该应用会立即关闭并报错。
  2. 点击 Android Studio 中的 Logcat 标签页,并记下日志中以如下所示的代码行开头的错误消息:
Process: com.example.android.marsrealestate, PID: 10646
java.lang.SecurityException: Permission denied (missing INTERNET permission?)

此错误消息表示您的应用可能缺少 INTERNET 权限。连接到互联网会引起安全问题,因此我们默认应用没有连接互联网。您需要明确告知 Android:该应用需要访问互联网。

  1. 打开 app/manifests/AndroidManifest.xml。将下面这行代码添加到 <application> 标签的前面:
<uses-permission android:name="android.permission.INTERNET" />
  1. 编译并再次运行应用。如果互联网连接一切正常,您就能看到包含火星资源数据的 JSON 文本。
  2. 点按设备或模拟器中的返回按钮关闭应用。
  3. 将设备或模拟器设为飞行模式,然后从“最近”菜单中重新打开该应用,或从 Android Studio 中重启该应用。


  1. 再次关闭飞行模式。

现在,您将从 Mars 网络服务获得 JSON 响应,这是一个不错的开始。但您实际上需要的是 Kotlin 对象,而不是大型 JSON 字符串。有一个名为 Moshi 的库,它是一个 Android JSON 解析器,可将 JSON 字符串转换为 Kotlin 对象。Retrofit 库具有可与 Moshi 配合使用的转换器,在这里非常适用。

在此任务中,您将配合使用 Moshif 库和 Retrofit,将网络服务的 JSON 响应解析成有用的火星资源 Kotlin 对象。您需要更改应用,而不是显示原始 JSON,而该应用会显示返回的火星资源数。

第 1 步:添加 Moshi 库依赖项

  1. 打开 build.gradle (Module: app)
  2. 在依赖项部分中,添加如下所示的代码以包含 Moshi 依赖项。与 Retrofit 一样,$version_moshi 是在项目级 Gradle 文件中单独定义的。这些依赖项添加了对核心 Moshi JSON 库的支持,还为 Moshi 提供了 Kotlin 支持。
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
  1. dependencies 代码块中,找到 Retrofit 标量转换器所在的代码行:
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
  1. 将该代码行更改为使用 converter-moshi
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
  1. 点击 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 数据类来存储解析后的结果,因此下一步是创建该类。

  1. 打开 app/java/network/MarsProperty.kt
  2. 将现有的 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 时,它会按名称匹配键,并用适当的值填充数据对象。

  1. 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 数据。

  1. 打开 network/MarsApiService.kt。您可能会看到 ScalarsConverterFactory 出现类缺失错误。这是因为您在第 1 步中更改了 Retrofit 依赖项。您很快便可以修复这些错误。
  2. 在文件顶部,在 Retrofit 构建器的前面添加以下代码以创建 Moshi 实例。在收到请求时,导入 com.squareup.moshi.Moshicom.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()

与处理 Retrofit 的操作类似,在这里,您需要使用 Moshi 构建器创建一个 moshi 对象。为了让 Moshi 注解能够在 Kotlin 中正常使用,请添加 KotlinJsonAdapterFactory,然后调用 build()

  1. 将 Retrofit 构建器更改为使用 MoshiConverterFactory 而不是 ScalarConverterFactory,并传入您刚刚创建的 moshi 实例。在收到请求时,导入 retrofit2.converter.moshi.MoshiConverterFactory
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()
  1. 同时删除 ScalarConverterFactory 的导入作业。

要删除的代码:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. 更新 MarsApiService 接口,让 Retrofit 返回 MarsProperty 对象列表,而不是返回 Call<String>
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}
  1. 打开 OverviewViewModel.kt。在 getMarsRealEstateProperties() 方法中,向下滚动到对 getProperties().enqueue() 的调用。
  2. enqueue() 的参数从 Callback<String> 更改为 Callback<List<MarsProperty>>。在收到请求时,导入 com.example.android.marsrealestate.network.MarsProperty
MarsApi.retrofitService.getProperties().enqueue(
   object: Callback<List<MarsProperty>> {
  1. onFailure() 中,将参数从 Call<String> 更改为 Call<List<MarsProperty>>
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
  1. onResponse() 的两个参数进行同样的更改:
override fun onResponse(call: Call<List<MarsProperty>>,
   response: Response<List<MarsProperty>>) {
  1. onResponse() 的正文中,将现有的对 _response.value 的赋值替换为如下所示的赋值。由于 response.body() 现在是一个 MarsProperty 对象列表,因此该列表的大小就是已解析的资源数。此响应消息会输出资源数:
_response.value =
   "Success: ${response.body()?.size} Mars properties retrieved"
  1. 确保飞行模式已关闭。编译并运行应用。这一次,消息应显示网络服务返回的资源数:

现在,Retrofit API 服务正在运行,但它所用的回调带有的两个回调方法必须由您实现。一种方法用于处理成功情况,另一方法用于处理失败情况,失败结果会报告异常。如果将协程与异常处理机制搭配使用(而不是使用回调),您的代码将变得更高效、更易读。Retrofit 有一个库,可便捷地集成协程。

在此任务中,您将转换网络服务和 ViewModel,以使用协程。

第 1 步:添加协程依赖项

  1. 打开 build.gradle (Module: app)
  2. 在依赖项部分中,添加对核心 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"
  1. 点击 Sync Now,以使用新的依赖项重建项目。

第 2 步:更新 MarsApiService 和 OverviewViewModel

  1. MarsApiService.kt 中,更新 Retrofit 构建器以使用 CoroutineCallAdapterFactory。完整的构建器现在如下所示:
private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl(BASE_URL)
        .build()

调用适配器为 Retrofit 增添了功能,使其能够创建可返回除默认 Call 类之外的内容的 API。在这种情况下,我们可以使用 CoroutineCallAdapterFactorygetProperties() 返回的 Call 对象替换为 Deferred 对象。

  1. getProperties() 方法中,将 Call<List<MarsProperty>> 更改为 Deferred<List<MarsProperty>>。在收到请求时,导入 kotlinx.coroutines.Deferred。完整的 getProperties() 方法如下所示:
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>

Deferred 接口定义了一个可返回结果值的协程作业(Deferred 继承自 Job)。Deferred 接口包含一个名为 await() 的方法,该方法可让代码在不阻塞的情况下等待,直到相应值准备就绪,然后返回该值。

  1. 打开 OverviewViewModel.kt。在 init 代码块的前面,添加一个协程作业:
private var viewModelJob = Job()
  1. 使用主调度程序为该新作业创建一个协程作用域:
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )

Dispatchers.Main 调度程序在运行时会使用界面线程。由于 Retrofit 会在后台线程上执行所有工作,因此没有理由使用其他任何线程的作用域。这样,您在获取结果时就可以轻松更新 MutableLiveData 的值。

  1. 删除 getMarsRealEstateProperties() 中的所有代码。在这里,您将使用协程,而不是调用 enqueue() 以及 onFailure()onResponse() 回调。
  2. getMarsRealEstateProperties() 中,启动协程:
coroutineScope.launch {

}


如需使用 Retrofit 针对网络任务返回的 Deferred 对象,您必须在协程内执行操作,因此您可以在此处启动刚刚创建的协程。您仍在主线程上执行代码,但现在要让协程管理并发。

  1. 在启动代码块内,对 retrofitService 对象调用 getProperties()
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()

MarsApi 服务调用 getProperties() 会对后台线程创建并发起网络调用,从而返回该任务的 Deferred 对象。

  1. 此外,在启动代码块内,添加一个 try/catch 代码块来处理异常:
try {

} catch (e: Exception) {

}
  1. try {} 代码块内,对 Deferred 对象调用 await()
var listResult = getPropertiesDeferred.await()

在相应值准备就绪后,对 Deferred 对象调用 await() 将返回网络调用的结果。await() 方法是非阻塞方法,因此 Mars API 服务会从网络中检索数据,而不会阻塞当前线程,这一点非常重要,因为我们处于界面线程的作用域内。任务完成后,您的代码会从上次中断的位置继续执行。这是在 try {} 内,因此您可以捕获异常。

  1. 同样,在 try {} 代码块内的 await() 方法之后,更新成功响应的响应消息:
_response.value =
   "Success: ${listResult.size} Mars properties retrieved"
  1. 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}"
       }
   }
}
  1. 在该类的底部,使用以下代码添加 onCleared() 回调:
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}

ViewModel 被销毁后,数据加载应该会停止,因为使用此 ViewModelOverviewFragment 会消失。如需在 ViewModel 被销毁后停止加载,您可以替换 onCleared() 以取消作业。

  1. 编译并运行应用。这次的结果与上一个任务(资源数报告)相同,但代码和错误处理机制更为简单。

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 类之外的内容。

开始学习下一课:8.2 从互联网加载和显示图片

如需本课程中其他 Codelab 的链接,请参阅“Android Kotlin 基础知识”Codelab 着陆页