凌峰创科服务平台

Android如何高效从服务器获取数据?

客户端-服务器模型

在开始之前,理解基本模型很重要:

Android如何高效从服务器获取数据?-图1
(图片来源网络,侵删)
  • 客户端: 你的 Android App,它负责向服务器发送请求,并接收和解析服务器返回的数据。
  • 服务器: 运行在远程计算机上的应用程序,它接收客户端的请求,处理业务逻辑(如查询数据库),然后将数据以标准格式(通常是 JSON)返回给客户端。
  • 通信协议: 客户端和服务器之间“对话”的语言,最常用的是 HTTP/HTTPS,HTTP 请求方法中,GET 用于获取数据,POST 用于提交数据。

实现方式演进:从传统到现代

获取数据的方式随着 Android 开发的发展而不断演进,主要有以下几种方式:

  1. 传统方式: HttpURLConnection (Java 原生)
  2. 现代流行方式: Retrofit + OkHttp (行业标准)
  3. 现代便捷方式: Android Jetpack 的 ViewModel + LiveData / Flow

我们将重点讲解最推荐的 Retrofit 方式,并简要提及其他方式。


使用 Retrofit (强烈推荐)

Retrofit 是一个类型安全的 HTTP 客户端,由 Square 公司开发,它极大地简化了网络请求的代码,让你可以用简洁的 Java/Kotlin 接口来定义 API。

步骤 1:添加依赖

app/build.gradle.kts (或 build.gradle) 文件中添加 Retrofit 和 Gson (用于 JSON 解析) 的依赖。

Android如何高效从服务器获取数据?-图2
(图片来源网络,侵删)
// build.gradle.kts (Kotlin DSL)
dependencies {
    // Retrofit
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    // Retrofit Gson 转换器
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    // OkHttp 日志拦截器 (用于调试网络请求)
    implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
}

步骤 2:添加网络权限

app/src/main/AndroidManifest.xml 文件中添加网络访问权限。

<manifest ...>
    <!-- 必须添加的权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
        ...
    </application>
</manifest>

步骤 3:创建数据模型

根据服务器返回的 JSON 结构,创建对应的 Kotlin 数据类,Retrofit 会自动将 JSON 映射到这些类。

服务器返回的 JSON 如下:

[
  {
    "userId": 1,
    "id": 1,: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  ...
]

你可以创建对应的 Post 数据类:

Android如何高效从服务器获取数据?-图3
(图片来源网络,侵删)
// Post.kt
data class Post(
    val userId: Int,
    val id: Int,
    val title: String,
    val body: String
)

步骤 4:创建 Retrofit API 接口

定义一个接口,用注解来描述 API 的端点、请求方法、路径和参数。

// ApiService.kt
import retrofit2.Call
import retrofit2.http.GET
interface ApiService {
    // @GET 表示这是一个 GET 请求
    // "posts" 是 API 的相对路径
    // suspend 关键字使这个函数可以在协程中挂起,返回 List<Post>
    @GET("posts")
    suspend fun getPosts(): List<Post>
}
  • @GET: 指定 HTTP 请求方法为 GET。
  • @Path("id"): 用于 URL 路径中的变量,如 @GET("posts/{id}")
  • @Query("param"): 用于 URL 查询参数,如 @GET("posts") 可以搭配 @Query("page") page: Int
  • @Body: 用于 POST 请求,发送一个对象作为请求体。
  • suspend: 将函数标记为挂起函数,使其可以在协程中使用,这是现代 Android 开发的推荐做法。

步骤 5:创建 Retrofit 实例并构建 API 服务

// RetrofitClient.kt
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
    private const val BASE_URL = "https://jsonplaceholder.typicode.com/" // 示例 API
    val instance: ApiService by lazy {
        // 1. 创建日志拦截器,方便调试
        val logging = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }
        // 2. 创建 OkHttp 客户端
        val okHttpClient = OkHttpClient.Builder()
            .addInterceptor(logging)
            .build()
        // 3. 构建 Retrofit 实例
        val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create()) // 添加 Gson 转换器
            .client(okHttpClient)
            .build()
        // 4. 创建 API 服务的实现
        retrofit.create(ApiService::class.java)
    }
}

步骤 6:在 ViewModel 或 Activity/Fragment 中调用 API

最佳实践:在 ViewModel 中调用

ViewModel 负责为 UI 提供数据,并且能在配置更改(如屏幕旋转)时存活下来,是处理网络请求的理想场所。

添加 lifecycle-viewmodel-ktx 依赖:

implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")

然后创建 ViewModel:

// MyViewModel.kt
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
    // 使用 MutableLiveData 来存储和更新数据
    private val _posts = MutableLiveData<List<Post>>()
    val posts: LiveData<List<Post>> = _posts
    // 错误信息
    private val _error = MutableLiveData<String>()
    val error: LiveData<String> = _error
    init {
        fetchPosts()
    }
    private fun fetchPosts() {
        // viewModelScope.launch 会自动在 ViewModel 销毁时取消协程,防止内存泄漏
        viewModelScope.launch {
            try {
                // 调用 Retrofit API
                val response = RetrofitClient.instance.getPosts()
                _posts.postValue(response) // 更新 LiveData
            } catch (e: Exception) {
                _error.postValue("获取数据失败: ${e.message}")
            }
        }
    }
}

在 Activity/Fragment 中观察数据并更新 UI

// MyActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MyActivity : AppCompatActivity() {
    // 使用 Ktx 库轻松获取 ViewModel 实例
    private val viewModel: MyViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
        val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
        val adapter = PostAdapter()
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = adapter
        // 观察 LiveData 的变化
        viewModel.posts.observe(this) { posts ->
            posts?.let {
                // 数据更新时,通知适配器
                adapter.submitList(it)
            }
        }
        // 观察错误信息
        viewModel.error.observe(this) { errorMessage ->
            errorMessage?.let {
                Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
            }
        }
    }
}

使用 Android Jetpack (ViewModel + Flow)

上面的示例已经结合了 ViewModel,现代开发中,我们更倾向于使用 Kotlin 的 Flow 来处理异步数据流,因为它能更优雅地处理背压和生命周期。

修改 ViewModel 以使用 Flow

你需要添加 lifecycle-runtime-ktx 依赖:

implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")

修改后的 ViewModel:

// MyViewModelWithFlow.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class MyViewModelWithFlow : ViewModel() {
    // 使用 StateFlow 来持有状态,它具有生命周期感知能力
    private val _posts = MutableStateFlow<List<Post>>(emptyList())
    val posts: StateFlow<List<Post>> = _posts
    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow<Boolean> = _isLoading
    private val _error = MutableStateFlow<String?>(null)
    val error: StateFlow<String?> = _error
    init {
        fetchPosts()
    }
    private fun fetchPosts() {
        viewModelScope.launch {
            _isLoading.value = true
            try {
                val response = RetrofitClient.instance.getPosts()
                _posts.value = response
                _error.value = null // 清除错误
            } catch (e: Exception) {
                _error.value = "获取数据失败: ${e.message}"
            } finally {
                _isLoading.value = false
            }
        }
    }
}

在 Activity/Fragment 中观察 StateFlow

// MyActivityWithFlow.kt
// ... (其他代码相同)
viewModel.posts.observe(this) { posts ->
    adapter.submitList(posts)
}
viewModel.isLoading.observe(this) { isLoading ->
    // 显示或隐藏加载指示器
    progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
}
viewModel.error.observe(this) { error ->
    error?.let {
        Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
    }
}

传统方式 HttpURLConnection (不推荐用于新项目)

这种方式代码繁琐,需要手动处理线程、输入流和 JSON 解析,容易出错,仅作为了解。

// 在后台线程中执行
Thread {
    try {
        val url = URL("https://jsonplaceholder.typicode.com/posts")
        val urlConnection = url.openConnection() as HttpURLConnection
        urlConnection.requestMethod = "GET"
        // 检查响应码
        if (urlConnection.responseCode == HttpURLConnection.HTTP_OK) {
            val inputStream = urlConnection.inputStream
            val response = inputStream.bufferedReader().use { it.readText() }
            // 1. 手动解析 JSON (可以使用 Gson 或 Moshi 库辅助)
            // val posts = Gson().fromJson(response, Array<Post>::class.java)
            // 2. 切换到主线程更新 UI
            runOnUiThread {
                // 将解析后的数据设置给 UI
                textView.text = response.substring(0, 200) // 示例:只显示前200个字符
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}.start()

缺点:

  • 必须手动创建后台线程。
  • 代码冗长,易出错。
  • 没有现代化的特性,如协程支持、类型安全等。

总结与最佳实践

特性 Retrofit + ViewModel + Flow HttpURLConnection
代码简洁性 极高,接口定义清晰 低,代码冗长
类型安全 ,编译时检查 API 低,字符串硬编码
异步处理 优雅,使用协程/Flow 繁琐,需手动管理线程
可维护性 ,易于扩展和修改 低,耦合度高
生态系统 强大,与 OkHttp、Gson/Moshi 等无缝集成 弱,需自行整合工具
推荐度 强烈推荐 (现代 Android 开发标准) 仅用于学习或极简单场景

最终推荐的工作流

  1. 定义 API: 使用 Retrofit 接口清晰描述你的后端 API。
  2. 创建数据模型: 为每个 JSON 响应创建对应的 Kotlin 数据类。
  3. 构建网络层: 创建一个单例的 Retrofit 客户端。
  4. 处理数据逻辑:
    • ViewModel 中,使用 viewModelScope.launch 启动一个协程。
    • 在协程中调用 Retrofit API。
    • 使用 StateFlowLiveData 来存储和暴露数据状态(成功、加载中、错误)。
  5. 更新 UI:
    • ActivityFragment 中,观察 ViewModel 暴露的 LiveDataStateFlow
    • 在回调中更新 UI 组件(如 RecyclerViewProgressBar、显示错误信息)。

遵循这个模式,你的网络请求代码将变得健壮、可测试且易于维护。

分享:
扫描分享到社交APP
上一篇
下一篇