第9天:网络编程-补充材料——‘MainActivity.kt‘解读

下面是对“第9天:网络编程”该文学习的更深层次的补充材料,对 ‘‘MainActivity.kt’ 文件的理解。
下面对’‘MainActivity.kt’ 文件中每一行进行详细解释:
这段代码实现了一个简单的Android应用,通过网络请求从https://randomuser.me/api/获取随机用户数据,并在界面上显示用户的信息。我们将逐步解析每一部分的功能和作用。

1. 包声明和导入语句

package com.example.networkexample

解释:

  • 包声明package com.example.networkexample 指定了当前文件所在的包。包用于组织代码,避免命名冲突,并提供访问控制。此处,包名为com.example.networkexample

import android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import okhttp3.*
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.IOException

解释:

  • 导入语句import关键字用于引入外部类和库,以便在当前文件中使用。

    • android.os.Bundle:用于在Activity中传递数据,保存状态。

    • android.util.Log:用于日志记录,帮助调试和错误追踪。

    • android.widget.TextView:Android的UI组件,用于显示文本。

    • androidx.appcompat.app.AppCompatActivity:支持库中的Activity基类,提供兼容性支持和现代特性。

    • okhttp3.*:导入OkHttp库的所有类,用于执行HTTP网络请求。

    • com.google.gson.Gson:Gson库的核心类,用于JSON解析。

    • com.google.gson.JsonSyntaxException:Gson抛出的异常,表示JSON语法错误。

    • java.io.IOException:处理输入输出异常,常用于网络请求失败等情况。


2. 数据类定义

// 数据类
data class User(val name: Name)
data class Name(val first: String, val last: String)
data class ApiResponse(val results: List<User>)

解释:

  • 数据类data class是Kotlin中用于存储数据的类,自动提供equalshashCodetoString等方法。

    • data class User(val name: Name)

      • 表示一个用户对象。
      • 包含一个属性name,类型为Name
    • data class Name(val first: String, val last: String)

      • 表示用户的姓名。
      • 包含两个字符串属性:first(名)和last(姓)。
    • data class ApiResponse(val results: List<User>)

      • 表示API响应的结构。
      • 包含一个属性results,是User对象的列表。
      • https://randomuser.me/api/返回的JSON结构对应。

3. 主Activity类

class MainActivity : AppCompatActivity() {

解释:

  • Activity类MainActivity继承自AppCompatActivity,是应用的主要界面。
  • 继承关系:通过继承,MainActivity获得了AppCompatActivity提供的功能,如生命周期管理、兼容性支持等。

3.1. 属性定义

    private val client = OkHttpClient()private val gson = Gson()private lateinit var tvUserInfo: TextView // 用于显示用户信息的TextView

解释:

  • private val client = OkHttpClient()

    • 定义一个私有的只读属性client,实例化OkHttp的OkHttpClient
    • OkHttpClient是执行HTTP请求的核心类,管理网络连接、线程等。
  • private val gson = Gson()

    • 定义一个私有的只读属性gson,实例化Gson对象。
    • Gson用于将JSON字符串解析为Kotlin对象,或将Kotlin对象序列化为JSON。
  • private lateinit var tvUserInfo: TextView

    • 定义一个私有的不可读属性tvUserInfo,类型为TextView
    • 使用lateinit关键字表示稍后会初始化该变量,避免在声明时赋值。
    • 注释说明:用于显示用户信息的TextView

3.2. onCreate方法

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 初始化TextViewtvUserInfo = findViewById(R.id.tv_user_info)fetchRandomUser()}

解释:

  • override fun onCreate(savedInstanceState: Bundle?)

    • onCreateActivity的生命周期方法,在Activity首次创建时调用。
    • override表示此方法重写了父类的方法。
    • savedInstanceState:包含Activity之前的状态信息(如旋转屏幕时保存的数据)。
  • super.onCreate(savedInstanceState)

    • 调用父类的onCreate方法,确保Activity的基础功能得以正常执行。
  • setContentView(R.layout.activity_main)

    • 设置Activity的布局资源,加载res/layout/activity_main.xml文件,定义界面布局。
  • tvUserInfo = findViewById(R.id.tv_user_info)

    • 初始化tvUserInfo变量,使用findViewById查找布局中的TextView组件。
    • R.id.tv_user_info:引用布局文件中TextView的ID,确保正确关联界面组件。
  • fetchRandomUser()

    • 调用自定义方法fetchRandomUser,开始执行网络请求获取随机用户数据。

3.3. fetchRandomUser方法

    private fun fetchRandomUser() {val request = Request.Builder().url("https://randomuser.me/api/").build()client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {Log.e("NetworkExample", "Error fetching user", e)}override fun onResponse(call: Call, response: Response) {response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)}}})}

解释:

  • private fun fetchRandomUser()

    • 定义一个私有方法fetchRandomUser,用于执行网络请求获取随机用户数据。
  • 构建请求

          val request = Request.Builder().url("https://randomuser.me/api/").build()
    
    • 使用OkHttp的Request.Builder()构建HTTP请求。
    • .url("https://randomuser.me/api/"):设置请求的URL为https://randomuser.me/api/
    • .build():完成请求构建,生成Request对象。
  • 执行请求

          client.newCall(request).enqueue(object : Callback {
    
    • 使用OkHttpClientnewCall(request)方法创建一个新的网络调用。
    • .enqueue(object : Callback { ... }):异步执行网络请求,传入一个Callback对象处理响应。
  • Callback接口实现

    • onFailure方法

                  override fun onFailure(call: Call, e: IOException) {Log.e("NetworkExample", "Error fetching user", e)}
      
      • 当网络请求失败时调用。
      • 使用Log.e记录错误信息,标签为"NetworkExample",消息为"Error fetching user",附带异常e
    • onResponse方法

                  override fun onResponse(call: Call, response: Response) {response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)}}
      
      • 当网络请求成功响应时调用。
      • response.body?.let { responseBody -> ... }:如果响应体不为null,执行代码块。
      • val jsonData = responseBody.string():将响应体转换为字符串,存储在jsonData变量中。
      • parseJson(jsonData):调用parseJson方法解析JSON数据。

3.4. parseJson方法

    private fun parseJson(jsonData: String) {try {val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"// 更新UI必须在主线程runOnUiThread {tvUserInfo.text = userInfo}Log.d("NetworkExample", userInfo)} catch (e: JsonSyntaxException) {Log.e("NetworkExample", "Error parsing JSON", e)}}

解释:

  • private fun parseJson(jsonData: String)

    • 定义一个私有方法parseJson,接收一个JSON字符串jsonData,用于解析用户数据并更新UI。
  • 异常处理

          try {// 解析和处理JSON数据} catch (e: JsonSyntaxException) {Log.e("NetworkExample", "Error parsing JSON", e)}
    
    • 使用try-catch块捕获可能的JsonSyntaxException,处理JSON解析错误。
    • 如果发生异常,使用Log.e记录错误信息。
  • 解析JSON数据

              val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"
    
    • gson.fromJson(jsonData, ApiResponse::class.java)
      • 使用Gson将JSON字符串jsonData解析为ApiResponse对象。
      • ApiResponse::class.java指定目标类,告诉Gson解析的目标数据结构。
    • val user = apiResponse.results[0]
      • ApiResponse对象中获取第一个User对象,假设API返回至少一个用户。
    • val userInfo = "User: ${user.name.first} ${user.name.last}"
      • 构建一个字符串userInfo,包含用户的名字和姓氏。
  • 更新UI

              runOnUiThread {tvUserInfo.text = userInfo}
    
    • runOnUiThread { ... }
      • 确保UI更新代码在主线程中执行。
      • tvUserInfo.text = userInfo:将构建的userInfo字符串设置为TextView的文本内容,更新界面显示。
  • 日志记录

              Log.d("NetworkExample", userInfo)
    
    • 使用Log.d记录调试信息,标签为"NetworkExample",消息为用户信息字符串userInfo

4. 代码总结

整体来看,这段代码实现了以下功能:

  1. 初始化

    • 设置Activity的布局,并初始化用于显示用户信息的TextView
  2. 网络请求

    • 使用OkHttp库异步向https://randomuser.me/api/发送GET请求,获取随机用户数据。
  3. 响应处理

    • 成功响应后,读取响应体的JSON字符串,调用parseJson方法解析数据。
    • 解析后的用户信息更新到TextView,并记录日志。
  4. 错误处理

    • 网络请求失败时,记录错误日志。
    • JSON解析失败时,记录错误日志。

5. 更详细的逐行解释

为了更深入理解,每行代码的具体作用和背后的机制,下面逐行进行解释。

5.1. 包声明和导入

package com.example.networkexample
  • 包声明:标识当前文件属于com.example.networkexample包。包名通常基于域名反转,以确保唯一性。
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import okhttp3.*
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.IOException
  • 导入
    • android.os.Bundle:用于传递数据和保存Activity状态。
    • android.util.Log:日志工具类,用于记录调试和错误信息。
    • android.widget.TextView:用于显示文本内容的UI组件。
    • androidx.appcompat.app.AppCompatActivity:支持库中的Activity类,提供兼容性支持。
    • okhttp3.*:导入OkHttp库的所有类,用于执行网络请求。
    • com.google.gson.Gson:Gson库的核心类,用于JSON解析。
    • com.google.gson.JsonSyntaxException:Gson解析JSON时可能抛出的异常,表示JSON语法错误。
    • java.io.IOException:处理输入输出异常,常见于网络请求失败等情况。

5.2. 数据类定义

// 数据类
data class User(val name: Name)
data class Name(val first: String, val last: String)
data class ApiResponse(val results: List<User>)
  • data class User(val name: Name)

    • 表示一个用户对象。
    • 包含一个属性name,类型为Name
  • data class Name(val first: String, val last: String)

    • 表示用户的姓名。
    • 包含first(名)和last(姓)两个字符串属性。
  • data class ApiResponse(val results: List<User>)

    • 表示API响应的数据结构。
    • 包含一个属性results,是User对象的列表。
    • 对应于https://randomuser.me/api/的JSON响应结构。

5.3. MainActivity类定义

class MainActivity : AppCompatActivity() {
  • 类定义:定义一个名为MainActivity的类,继承自AppCompatActivity
  • 继承关系:继承自AppCompatActivity,意味着MainActivity拥有所有AppCompatActivity的功能,如生命周期管理、主题支持等。

5.4. 属性定义

    private val client = OkHttpClient()private val gson = Gson()private lateinit var tvUserInfo: TextView // 用于显示用户信息的TextView
  • private val client = OkHttpClient()

    • 定义一个私有的只读属性client,实例化OkHttp的OkHttpClient
    • OkHttpClient是执行HTTP请求的核心类,负责管理网络连接、线程池等。
  • private val gson = Gson()

    • 定义一个私有的只读属性gson,实例化Gson对象。
    • Gson用于将JSON字符串解析为Kotlin对象,或将Kotlin对象序列化为JSON字符串。
  • private lateinit var tvUserInfo: TextView

    • 定义一个私有的、稍后初始化的变量tvUserInfo,类型为TextView
    • lateinit关键字表示变量会在稍后初始化,避免在声明时赋值。
    • 注释说明该TextView用于显示用户信息。

5.5. onCreate方法

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 初始化TextViewtvUserInfo = findViewById(R.id.tv_user_info)fetchRandomUser()}
  • 方法声明

    • override fun onCreate(savedInstanceState: Bundle?):重写ActivityonCreate方法。
    • savedInstanceState:包含Activity之前保存的状态信息。
  • super.onCreate(savedInstanceState)

    • 调用父类的onCreate方法,确保Activity的基础功能正常执行。
  • setContentView(R.layout.activity_main)

    • 设置Activity的布局资源,加载res/layout/activity_main.xml文件,定义界面布局。
  • tvUserInfo = findViewById(R.id.tv_user_info)

    • 使用findViewById方法查找布局中的TextView,并将其赋值给tvUserInfo变量。
    • R.id.tv_user_info:引用布局文件中TextView的ID,确保正确关联UI组件。
  • fetchRandomUser()

    • 调用自定义方法fetchRandomUser,开始执行网络请求获取随机用户数据。

5.6. fetchRandomUser方法

    private fun fetchRandomUser() {val request = Request.Builder().url("https://randomuser.me/api/").build()client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {Log.e("NetworkExample", "Error fetching user", e)}override fun onResponse(call: Call, response: Response) {response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)}}})}
  • 方法声明

    • private fun fetchRandomUser():定义一个私有方法fetchRandomUser,用于执行网络请求获取随机用户数据。
  • 构建HTTP请求

          val request = Request.Builder().url("https://randomuser.me/api/").build()
    
    • 使用OkHttp的Request.Builder()创建一个新的HTTP请求。
    • .url("https://randomuser.me/api/"):设置请求的URL为https://randomuser.me/api/,这是一个提供随机用户数据的API。
    • .build():完成请求构建,生成一个Request对象。
  • 执行网络请求

          client.newCall(request).enqueue(object : Callback { ... })
    
    • 使用OkHttpClientnewCall(request)方法创建一个新的网络调用。
    • .enqueue(object : Callback { ... }):异步执行网络请求,并提供一个匿名的Callback对象来处理响应或失败。
  • Callback接口实现

    • onFailure方法

                  override fun onFailure(call: Call, e: IOException) {Log.e("NetworkExample", "Error fetching user", e)}
      
      • 当网络请求失败时调用。
      • 使用Log.e记录错误信息:
        • 标签(TAG)为"NetworkExample",方便在Logcat中过滤日志。
        • 消息为"Error fetching user"
        • 传递异常对象e,记录详细的错误信息。
    • onResponse方法

                  override fun onResponse(call: Call, response: Response) {response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)}}
      
      • 当网络请求成功响应时调用。
      • response.body?.let { responseBody -> ... }
        • 如果响应体不为null,执行代码块。
        • val jsonData = responseBody.string():将响应体转换为字符串,存储在jsonData变量中。
        • parseJson(jsonData):调用自定义方法parseJson,解析JSON数据。

5.7. parseJson方法

    private fun parseJson(jsonData: String) {try {val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"// 更新UI必须在主线程runOnUiThread {tvUserInfo.text = userInfo}Log.d("NetworkExample", userInfo)} catch (e: JsonSyntaxException) {Log.e("NetworkExample", "Error parsing JSON", e)}}
  • 方法声明

    • private fun parseJson(jsonData: String):定义一个私有方法parseJson,接收一个字符串参数jsonData,用于解析JSON数据。
  • 异常处理

          try {// 解析和处理JSON数据} catch (e: JsonSyntaxException) {Log.e("NetworkExample", "Error parsing JSON", e)}
    
    • 使用try-catch块捕获可能的JsonSyntaxException,这是Gson解析JSON时可能抛出的异常,表示JSON语法错误。
    • 如果发生异常,使用Log.e记录错误信息,标签为"NetworkExample",消息为"Error parsing JSON",并附带异常e
  • 解析JSON数据

              val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"
    
    • val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)

      • 使用Gson将JSON字符串jsonData解析为ApiResponse对象。
      • ApiResponse::class.java:指定目标类,告诉Gson解析的目标数据结构。
    • val user = apiResponse.results[0]

      • ApiResponse对象中获取第一个User对象,假设API返回至少一个用户。
      • results是一个List<User>[0]获取第一个元素。
    • val userInfo = "User: ${user.name.first} ${user.name.last}"

      • 构建一个字符串userInfo,包含用户的名字和姓氏。
      • 使用字符串模板${}插入变量值。
  • 更新UI

              runOnUiThread {tvUserInfo.text = userInfo}
    
    • runOnUiThread { ... }
      • 确保UI更新代码在主线程中执行,因为Android不允许在后台线程中直接修改UI组件。
      • tvUserInfo.text = userInfo:将构建的userInfo字符串设置为TextView的文本内容,更新界面显示。
  • 日志记录

              Log.d("NetworkExample", userInfo)
    
    • 使用Log.d记录调试信息,标签为"NetworkExample",消息为用户信息字符串userInfo
    • 这有助于在Logcat中查看获取的用户信息,验证数据是否正确解析。

6. 完整代码回顾

让我们再看一次完整的代码,并结合上述解释进行理解:

package com.example.networkexampleimport android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import okhttp3.*
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.IOException// 数据类
data class User(val name: Name)
data class Name(val first: String, val last: String)
data class ApiResponse(val results: List<User>)class MainActivity : AppCompatActivity() {private val client = OkHttpClient()private val gson = Gson()private lateinit var tvUserInfo: TextView // 用于显示用户信息的TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 初始化TextViewtvUserInfo = findViewById(R.id.tv_user_info)fetchRandomUser()}private fun fetchRandomUser() {val request = Request.Builder().url("https://randomuser.me/api/").build()client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {Log.e("NetworkExample", "Error fetching user", e)}override fun onResponse(call: Call, response: Response) {response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)}}})}private fun parseJson(jsonData: String) {try {val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"// 更新UI必须在主线程runOnUiThread {tvUserInfo.text = userInfo}Log.d("NetworkExample", userInfo)} catch (e: JsonSyntaxException) {Log.e("NetworkExample", "Error parsing JSON", e)}}
}

主要功能流程:

  1. 应用启动

    • MainActivity被创建,onCreate方法被调用。
    • 设置布局文件activity_main.xml
    • 初始化TextView``tvUserInfo,用于显示用户信息。
    • 调用fetchRandomUser方法,开始网络请求。
  2. 执行网络请求

    • 构建一个指向https://randomuser.me/api/的GET请求。
    • 使用OkHttp的OkHttpClient执行异步请求,提供Callback对象处理响应。
  3. 处理网络响应

    • 请求失败
      • onFailure方法中,记录错误日志。
    • 请求成功
      • onResponse方法中,读取响应体的JSON字符串。
      • 调用parseJson方法解析JSON数据。
  4. 解析JSON数据

    • 使用Gson将JSON字符串解析为ApiResponse对象。
    • ApiResponse中提取第一个User对象,构建用户信息字符串。
    • 在主线程中更新TextView,显示用户信息。
    • 记录调试日志,显示获取的用户信息。
  5. 错误处理

    • 捕获JSON解析异常,记录错误日志。

7. 扩展功能建议

为了增强应用的功能和用户体验,以下是一些扩展建议:

7.1. 显示更多用户信息

当前代码仅显示用户的名字。你可以扩展数据类和UI,显示更多信息,如电子邮件、头像等。

  • 扩展数据类

    data class User(val name: Name, val email: String, val picture: Picture)
    data class Picture(val large: String, val medium: String, val thumbnail: String)
    
  • 更新布局文件

    • 添加ImageView用于显示用户头像。
    • 添加TextView用于显示电子邮件。
  • 使用图像加载库

    • 使用如Glide或Picasso库加载和显示用户头像。

7.2. 使用Kotlin协程简化异步代码

Kotlin协程提供了更简洁和高效的方式处理异步任务,替代传统的回调模式。

  • 引入协程依赖

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
    
  • 重构网络请求代码

    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.launch
    import kotlinx.coroutines.withContextprivate fun fetchRandomUser() {CoroutineScope(Dispatchers.IO).launch {try {val request = Request.Builder().url("https://randomuser.me/api/").build()val response = client.newCall(request).execute()val jsonData = response.body?.string()jsonData?.let {val apiResponse = gson.fromJson(it, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"withContext(Dispatchers.Main) {tvUserInfo.text = userInfo}Log.d("NetworkExample", userInfo)}} catch (e: Exception) {Log.e("NetworkExample", "Error fetching user", e)}}
    }
    

7.3. 改善错误处理和用户反馈

  • 在UI上显示错误信息

    • 当网络请求失败或解析失败时,在TextView中显示友好的错误信息。
  • 添加加载指示器

    • 在网络请求期间显示一个进度条或加载指示器,提升用户体验。

7.4. 使用ViewModel和LiveData

  • 引入Android Architecture Components
    • 使用ViewModelLiveData管理数据和UI状态,提升代码的可维护性和生命周期感知。

8. 常见问题与解决

8.1. Gson导入显示为红色

如果在IDE中import com.google.gson.Gson显示为红色,可能是因为Gson库未正确添加或Gradle同步失败。请确保以下几点:

  1. 添加Gson依赖
    应用级build.gradle文件中添加:

    implementation "com.google.code.gson:gson:2.10.1"
    
  2. 同步Gradle
    添加依赖后,点击“Sync Now”按钮,确保Gradle成功同步。

  3. 检查仓库配置
    项目级build.gradle文件中,确保包含mavenCentral()仓库:

    buildscript {repositories {google()mavenCentral()}dependencies {classpath "com.android.tools.build:gradle:8.5.1"classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0"}
    }allprojects {repositories {google()mavenCentral()}
    }
    
  4. 清理和重建项目

    • 选择Build > Clean Project,然后Build > Rebuild Project
  5. 无效化缓存并重启

    • 选择File > Invalidate Caches / Restart...,然后点击Invalidate and Restart

8.2. 网络请求失败或不显示数据

  • 检查网络权限

    • 确保在AndroidManifest.xml中添加了网络权限:
      <uses-permission android:name="android.permission.INTERNET"/>
      
  • 检查API URL

    • 确认https://randomuser.me/api/是否可访问。可以在浏览器中打开URL,查看是否返回有效的JSON数据。
  • 查看Logcat日志

    • 打开Logcat窗口,过滤标签为"NetworkExample",查看是否有错误日志。
    • 例如,Error fetching userError parsing JSON
  • 检查UI组件初始化

    • 确保TextView的ID在布局文件中正确设置,并在代码中正确引用。

9. 扩展示例:显示更多用户信息

为了让应用更具交互性和视觉效果,以下是如何扩展代码以显示更多用户信息,如头像和电子邮件。

9.1. 修改数据类

// 扩展数据类
data class User(val name: Name, val email: String, val picture: Picture)
data class Picture(val large: String, val medium: String, val thumbnail: String)

9.2. 更新布局文件

编辑activity_main.xml,添加ImageView和更多的TextView

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/scroll_main"android:layout_width="match_parent"android:layout_height="match_parent"android:padding="16dp"tools:context=".MainActivity"><LinearLayoutandroid:id="@+id/ll_main"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><!-- 用户头像 --><ImageViewandroid:id="@+id/iv_avatar"android:layout_width="150dp"android:layout_height="150dp"android:layout_gravity="center"android:scaleType="centerCrop"android:src="@mipmap/ic_launcher" /><!-- 用户信息 --><TextViewandroid:id="@+id/tv_user_info"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Fetching user data..."android:textSize="18sp"android:textStyle="bold"android:layout_marginTop="16dp" /><!-- 用户电子邮件 --><TextViewandroid:id="@+id/tv_user_email"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Email: "android:textSize="16sp"android:layout_marginTop="8dp" /></LinearLayout>
</ScrollView>

9.3. 更新MainActivity.kt

添加ImageView和显示电子邮件的逻辑。使用Glide加载头像图片。

更新后的MainActivity.kt
package com.example.networkexampleimport android.os.Bundle
import android.util.Log
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import okhttp3.*
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.IOException
import com.bumptech.glide.Glide// 数据类
data class User(val name: Name, val email: String, val picture: Picture)
data class Name(val first: String, val last: String)
data class Picture(val large: String, val medium: String, val thumbnail: String)
data class ApiResponse(val results: List<User>)class MainActivity : AppCompatActivity() {private val client = OkHttpClient()private val gson = Gson()private lateinit var tvUserInfo: TextViewprivate lateinit var tvUserEmail: TextViewprivate lateinit var ivAvatar: ImageViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 初始化视图tvUserInfo = findViewById(R.id.tv_user_info)tvUserEmail = findViewById(R.id.tv_user_email)ivAvatar = findViewById(R.id.iv_avatar)fetchRandomUser()}private fun fetchRandomUser() {val request = Request.Builder().url("https://randomuser.me/api/").build()client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {Log.e("NetworkExample", "Error fetching user", e)runOnUiThread {tvUserInfo.text = "Failed to fetch user data."}}override fun onResponse(call: Call, response: Response) {response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)} ?: run {Log.e("NetworkExample", "Response body is null")runOnUiThread {tvUserInfo.text = "No data received."}}}})}private fun parseJson(jsonData: String) {try {val apiResponse = gson.fromJson(jsonData, ApiResponse::class.java)val user = apiResponse.results[0]val userInfo = "User: ${user.name.first} ${user.name.last}"val userEmail = "Email: ${user.email}"val avatarUrl = user.picture.large// 更新UI必须在主线程runOnUiThread {tvUserInfo.text = userInfotvUserEmail.text = userEmail// 使用Glide加载用户头像Glide.with(this).load(avatarUrl).placeholder(R.mipmap.ic_launcher).into(ivAvatar)}Log.d("NetworkExample", "$userInfo, $userEmail")} catch (e: JsonSyntaxException) {Log.e("NetworkExample", "Error parsing JSON", e)runOnUiThread {tvUserInfo.text = "Error parsing data."}}}
}

解释:

  • 导入Glide库

    import com.bumptech.glide.Glide
    
    • Glide是一个流行的Android图像加载库,用于高效地加载和缓存图像。
  • 扩展数据类

    data class User(val name: Name, val email: String, val picture: Picture)
    data class Picture(val large: String, val medium: String, val thumbnail: String)
    
    • 增加email属性,存储用户的电子邮件。
    • 增加picture属性,包含用户头像的不同尺寸URL。
  • 初始化额外的UI组件

    private lateinit var tvUserEmail: TextView
    private lateinit var ivAvatar: ImageView
    
    • 定义tvUserEmailivAvatar,分别用于显示电子邮件和用户头像。
  • onCreate方法中初始化新组件

        tvUserEmail = findViewById(R.id.tv_user_email)ivAvatar = findViewById(R.id.iv_avatar)
    
    • 使用findViewById方法初始化tvUserEmailivAvatar,确保与布局文件中的ID对应。
  • 处理响应体为null的情况

                  response.body?.let { responseBody ->val jsonData = responseBody.string()parseJson(jsonData)} ?: run {Log.e("NetworkExample", "Response body is null")runOnUiThread {tvUserInfo.text = "No data received."}}
    
    • 如果响应体为null,记录错误日志,并在UI上显示“未接收到数据”。
  • 扩展parseJson方法

              val userEmail = "Email: ${user.email}"val avatarUrl = user.picture.large
    
    • User对象中提取emailpicture.large(大尺寸头像URL)。
              runOnUiThread {tvUserInfo.text = userInfotvUserEmail.text = userEmail// 使用Glide加载用户头像Glide.with(this).load(avatarUrl).placeholder(R.mipmap.ic_launcher).into(ivAvatar)}
    
    • 在主线程中更新UI:
      • 设置tvUserInfo的文本为用户信息。
      • 设置tvUserEmail的文本为用户电子邮件。
      • 使用Glide加载头像图片到ImageView
        • .with(this):指定上下文。
        • .load(avatarUrl):指定要加载的图像URL。
        • .placeholder(R.mipmap.ic_launcher):设置加载过程中的占位图。
        • .into(ivAvatar):将加载的图像显示到ivAvatar中。
  • 处理响应体为null

                      } ?: run {Log.e("NetworkExample", "Response body is null")runOnUiThread {tvUserInfo.text = "No data received."}}
    
    • 如果响应体为空,记录错误日志并在UI上显示“未接收到数据”。
  • 完善错误处理

                  runOnUiThread {tvUserInfo.text = "Failed to fetch user data."}
    
    • onFailure方法中,不仅记录日志,还在UI上显示错误信息,提升用户体验。

9.4. 添加Glide依赖

为了使用Glide加载图像,需要在应用级build.gradle文件中添加Glide依赖:

dependencies {implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.0"implementation "androidx.core:core-ktx:1.10.1"implementation "androidx.appcompat:appcompat:1.6.1"implementation "com.google.android.material:material:1.10.0"implementation "androidx.constraintlayout:constraintlayout:2.1.4"// OkHttp 和 Gson 依赖implementation "com.squareup.okhttp3:okhttp:4.10.0"implementation "com.google.code.gson:gson:2.10.1"// Glide 依赖implementation 'com.github.bumptech.glide:glide:4.15.1'kapt 'com.github.bumptech.glide:compiler:4.15.1' // 如果使用Kotlin注解处理testImplementation 'junit:junit:4.13.2'androidTestImplementation 'androidx.test.ext:junit:1.1.5'androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

解释:

  • 添加Glide依赖

    • implementation 'com.github.bumptech.glide:glide:4.15.1':引入Glide库。
    • kapt 'com.github.bumptech.glide:compiler:4.15.1':Glide的注解处理器,用于生成代码。如果使用Kotlin,需要应用kotlin-kapt插件。
  • 应用kotlin-kapt插件
    应用级build.gradle文件顶部添加:

    plugins {id 'com.android.application'id 'kotlin-android'id 'kotlin-kapt' // 添加这一行
    }
    
  • 同步Gradle
    添加依赖后,点击“Sync Now”按钮,确保Gradle成功同步,Glide库被集成到项目中。


10. 运行和测试

10.1. 连接设备或启动模拟器

确保连接了一台Android设备或启动了一个Android模拟器,以便运行和测试应用。

10.2. 运行应用

点击Android Studio顶部的“Run”按钮,选择目标设备,编译并安装应用。

10.3. 观察UI变化

  • 应用启动后,界面上应显示“Fetching user data…”。
  • 几秒钟后,TextView应更新为类似“User: John Doe”和“Email: john.doe@example.com”的信息,并在ImageView中显示用户的头像。

10.4. 查看Logcat日志

打开Logcat窗口,过滤标签为"NetworkExample",查看调试信息:

D/NetworkExample: User: John Doe, Email: john.doe@example.com
  • 确认日志中输出了正确的用户信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/465497.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DataX 的安装配置和使用 (详细版)

1&#xff0c;上传解压 1&#xff0c;开始上传安装包到你虚拟机上放置安装包的文件夹 2&#xff0c;开始解压 ,配置环境变量 1、上传 /opt/modules 2、解压 tar -zxvf datax.tar.gz -C /opt/installs 3、修改 vi /etc/profile 配置环境变量&#xff1a; export DAT…

蓝桥杯第21场小白入门赛补题

5.蓝桥派对 思路 &#xff1a;一个区间与多少个其他区间有关联&#xff0c;先对所有区间左端点和右端点从小到大排序&#xff0c;对于每个询问&#xff0c;我们先算出[1,r]这个区间里有多少个区间的起点即区间总数&#xff0c;使用upper_bound函数&#xff0c;然后使用lower_bo…

Linux篇(常见入门命令)

目录 一、开启终端 二、Linux命令格式 1. 什么是Linux 的命令&#xff1f; 三、Linux下的命令补全 四、切换用户 五、uname&#xff1a;查看操作系统信息 六、ls&#xff1a;查看目录下文件 1. 用法一 2. 用法二 3. 用法三 七、pwd&#xff1a;显示当前路径 八、cd&…

7.qsqlquerymodel 与 qtableview使用

目录 qtableview 委托QStyledItemDelegateQAbstractItemDelegateCheckBoxItemDelegate使用 qtableview 委托 //设置单元格委托 void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *itemDelegate() const;//设置列委托 void setItemDelegateForCol…

AMD显卡低负载看视频掉驱动(chrome edge浏览器) 高负载玩游戏却稳定 解决方法——关闭MPO

2024.11.6更新 关闭MPO有点用但是还是驱动掉到恶心&#xff0c;找到终极方法了视频输出直接插主板走核显&#xff0c;稳得一笔&#xff0c;3dmark跑了个分几乎没变化。核显负责桌面浏览器&#xff0c;独显就专心只跑游戏。等24.11驱动再看看 问题 折磨的开始是天下苦黄狗久矣&…

VS2022远程连接调试编译Linux环境下的C++代码

工具&#xff1a;VS2022 虚拟机&#xff1a;RHEL 8.0 一、下载必要工具 1.VS2022组件安装 打开VS2022Installer&#xff0c;点击修改下载必要工具。 选择Linux 和嵌入式开发&#xff0c;然后点击右下角的修改&#xff01; 等待安装........ 安装完成后&#xff0c;创建Linu…

AI笔筒操作说明及应用场景

AI笔筒由来&#xff1a; 在快节奏的现代办公环境中&#xff0c;我们一直在寻找既能提升效率、增添便利&#xff0c;又能融入企业文化、展现个人品味的桌面伙伴。为此&#xff0c;我们特推出专为追求卓越、注重细节的您设计的AI笔筒礼品版&#xff0c;它集高科技与实用性于一身…

爱普生 SG–WriterⅡ 石英可编程手工烧录器

在电子制造与研发的复杂世界中&#xff0c;爱普生 SG–WriterⅡ 石英可编程手工烧录器犹如一把神奇的钥匙&#xff0c;开启了石英晶振编程的无限可能&#xff0c;为众多领域的电子设备注入了精准与稳定的灵魂。 作为手工烧录器&#xff0c;SG–WriterⅡ 独具特色。在当今多样化…

数据库->索引

目录 一、索引是什么 二、索引的数据结构 1.HASH 2.二叉搜索树 3.N叉树(B树) 4.B树 5.B树与B树的区别 三、MYSQL的页 1.页文件头与页文件尾 2.页主体 3.页目录 4.数据页头 四、B在MYSQL索引中的应用 1.应用 2.计算三层树⾼的B树可以存放多少条记录 五、索引分类…

mongodb 按条件进行备份和恢复

在宝塔面板环境下&#xff0c;可以在定时任务设置备份mongodb但是存在缺陷&#xff0c;mongodb如果存储日志&#xff0c;一定时间后会特别巨大&#xff0c;全量备份会导致服务器卡死并很快耗尽磁盘空间&#xff0c;按一定的条件对进行&#xff0c;按天备份数据是必须的。我们用…

从SRE视角透视DevOps的构建精髓

SRE 侧重系统稳定性&#xff0c;DevOps 强调开发运维协作。SRE 实践助力DevOps&#xff0c;提升系统稳定性与团队协作效率。 SRE 运用软件工程的原理&#xff0c;将系统管理员的手工任务自动化&#xff0c;负责运维由系统组件构成的服务&#xff0c;确保服务稳定运行。SRE职责涵…

【数据库】elasticsearch

1、架构 es会为每个索引创建一定数量的主分片和副本分片。 分片&#xff08;Shard&#xff09;&#xff1a; 将索引数据分割成多个部分&#xff0c;每个部分都是一个独立的索引。 主要目的是实现数据的分布式存储和并行处理&#xff0c;从而提高系统的扩展性和性能。 在创建索…

深度学习基础知识-编解码结构理论超详细讲解

编解码结构&#xff08;Encoder-Decoder&#xff09;是一种应用广泛且高效的神经网络架构&#xff0c;最早用于序列到序列&#xff08;Seq2Seq&#xff09;任务&#xff0c;如机器翻译、图像生成、文本生成等。随着深度学习的发展&#xff0c;编解码结构不断演变出多种模型变体…

spark-on-k8s 介绍

spark-on-k8s 介绍 摘要 最近一段时间都在做与spark相关的项目&#xff0c;主要是与最近今年比较火的隐私计算相结合&#xff0c;主要是在机密计算领域使用spark做大数据分析、SQL等业务&#xff0c;从中也了解到了一些spark的知识&#xff0c;现在做一个简单的总结&#xff…

探索PickleDB:Python中的轻量级数据存储利器

文章目录 探索PickleDB&#xff1a;Python中的轻量级数据存储利器1. 背景&#xff1a;为什么选择PickleDB&#xff1f;2. PickleDB是什么&#xff1f;3. 如何安装PickleDB&#xff1f;4. 简单的库函数使用方法创建和打开数据库设置数据获取数据删除数据保存数据库 5. 应用场景与…

【华硕天选5开机黑屏只有鼠标,调用资源管理器也无法黑屏状态的一种解决方式】

华硕天选5开机黑屏只有鼠标&#xff0c;调用资源管理器也无法黑屏状态的一种解决方式 1.问题描述2.解决方法3.重启如下图 1.问题描述 华硕天选5开机黑屏只有鼠标&#xff0c;调用资源管理器&#xff08;ctrlalt.&#xff09;也无法黑屏状态。 2.解决方法 ctrl shitf10 就能正…

【详细 工程向】基于Smart3D的五镜头相机三维重建

数据部分&#xff1a; 数据要求 &#xff08;1&#xff09;每条行带至少从 3 个不同的视角进行拍摄。 &#xff08;2&#xff09;相邻相片之间的重叠度通常要求大于三分之二。 &#xff08;3&#xff09;不同拍摄视角之间夹角应该少于 15 度。 &#xff08;4&#xff09;通…

使用 RabbitMQ 有什么好处?

大家好&#xff0c;我是锋哥。今天分享关于【使用 RabbitMQ 有什么好处&#xff1f;】面试题。希望对大家有帮助&#xff1b; 使用 RabbitMQ 有什么好处&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RabbitMQ 是一种流行的开源消息代理&#xff0c;广…

【自用】时序数据库、时序数据库,IOTDB官方文档笔记

什么叫时序数据&#xff1f; 万物互联的今天&#xff0c;物联网场景、工业场景等各类场景都在进行数字化转型&#xff0c;人们通过在各类设备上安装传感器对设备的各类状态进行采集。如电机采集电压、电流&#xff0c;风机的叶片转速、角速度、发电功率&#xff1b;车辆采集经…

线程池执行流程

线程池执行流程 1、如果当前在运行的线程数不超过核心线程数&#xff0c;那么则创建新的核心线程去执行任务 2、如果当前在运行的线程数超过了核心线程数&#xff0c;那么就将任务加入到队列中去 3、如果任务队列已经满了&#xff0c;那么就创建非核心线程去执行当前任务 4…