网络请求
现代 Android 应用通常需要与服务器进行数据交互。本章将介绍如何使用 OkHttp 和 Retrofit 进行网络请求,以及如何处理 JSON 数据。
网络权限
首先,在 AndroidManifest.xml 中添加网络权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application ...>
...
</application>
</manifest>
OkHttp
OkHttp 是 Square 公司开发的高性能 HTTP 客户端,是 Android 网络请求的基础库。
添加依赖
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
}
基本使用
private val client = OkHttpClient()
// 同步 GET 请求(在子线程执行)
fun syncGet(url: String): String {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).execute().use { response ->
return response.body?.string() ?: ""
}
}
// 异步 GET 请求
fun asyncGet(url: String, callback: (String?) -> Unit) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// 请求失败
callback(null)
}
override fun onResponse(call: Call, response: Response) {
// 请求成功
val body = response.body?.string()
callback(body)
}
})
}
POST 请求
// POST JSON 数据
fun postJson(url: String, json: String): String {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = json.toRequestBody(mediaType)
val request = Request.Builder()
.url(url)
.post(body)
.build()
client.newCall(request).execute().use { response ->
return response.body?.string() ?: ""
}
}
// POST 表单数据
fun postForm(url: String, params: Map<String, String>): String {
val formBody = FormBody.Builder().apply {
params.forEach { (key, value) ->
add(key, value)
}
}.build()
val request = Request.Builder()
.url(url)
.post(formBody)
.build()
client.newCall(request).execute().use { response ->
return response.body?.string() ?: ""
}
}
添加请求头
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "Bearer $token")
.addHeader("Content-Type", "application/json")
.build()
拦截器
拦截器可以用于日志记录、添加公共请求头、缓存等:
// 日志拦截器
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
// 自定义拦截器(添加公共请求头)
class AuthInterceptor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
return chain.proceed(request)
}
}
Retrofit
Retrofit 是 Square 公司开发的 REST 客户端,基于 OkHttp,提供了类型安全的 HTTP API 接口定义。
添加依赖
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.google.code.gson:gson:2.10.1")
}
定义 API 接口
// 数据类
data class User(
val id: Int,
val name: String,
val email: String
)
data class LoginRequest(
val username: String,
val password: String
)
data class LoginResponse(
val token: String,
val user: User
)
// API 接口
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
@GET("users/{id}")
suspend fun getUserById(@Path("id") id: Int): User
@GET("users")
suspend fun searchUsers(@Query("name") name: String): List<User>
@POST("auth/login")
suspend fun login(@Body request: LoginRequest): LoginResponse
@POST("users")
suspend fun createUser(@Body user: User): User
@PUT("users/{id}")
suspend fun updateUser(@Path("id") id: Int, @Body user: User): User
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") id: Int)
@GET("users")
suspend fun getUsersWithHeader(
@Header("Authorization") token: String
): List<User>
@FormUrlEncoded
@POST("auth/login")
suspend fun loginForm(
@Field("username") username: String,
@Field("password") password: String
): LoginResponse
}
创建 Retrofit 实例
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
使用 API
class UserViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
fun loadUsers() {
viewModelScope.launch {
_isLoading.value = true
try {
val result = RetrofitClient.apiService.getUsers()
_users.value = result
} catch (e: Exception) {
_error.value = e.message ?: "加载失败"
} finally {
_isLoading.value = false
}
}
}
fun login(username: String, password: String) {
viewModelScope.launch {
_isLoading.value = true
try {
val request = LoginRequest(username, password)
val response = RetrofitClient.apiService.login(request)
// 保存 token,跳转页面
} catch (e: Exception) {
_error.value = e.message ?: "登录失败"
} finally {
_isLoading.value = false
}
}
}
}
在 Activity/Fragment 中使用
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 观察数据
viewModel.users.observe(this) { users ->
// 更新 UI
binding.recyclerView.adapter = UserAdapter(users)
}
viewModel.error.observe(this) { error ->
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
}
viewModel.isLoading.observe(this) { isLoading ->
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
}
// 加载数据
viewModel.loadUsers()
}
}
错误处理
异常类型
sealed class NetworkResult<out T> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val message: String) : NetworkResult<Nothing>()
data object Loading : NetworkResult<Nothing>()
}
suspend fun <T> safeApiCall(apiCall: suspend () -> T): NetworkResult<T> {
return try {
NetworkResult.Success(apiCall())
} catch (e: HttpException) {
NetworkResult.Error("网络错误: ${e.code()}")
} catch (e: IOException) {
NetworkResult.Error("网络连接失败")
} catch (e: Exception) {
NetworkResult.Error(e.message ?: "未知错误")
}
}
// 使用
fun loadUsers() {
viewModelScope.launch {
_result.value = NetworkResult.Loading
_result.value = safeApiCall {
RetrofitClient.apiService.getUsers()
}
}
}
响应状态码处理
fun handleResponse(response: Response<T>): NetworkResult<T> {
return when {
response.isSuccessful -> {
val body = response.body()
if (body != null) {
NetworkResult.Success(body)
} else {
NetworkResult.Error("响应体为空")
}
}
response.code() == 401 -> NetworkResult.Error("未授权,请登录")
response.code() == 403 -> NetworkResult.Error("禁止访问")
response.code() == 404 -> NetworkResult.Error("资源不存在")
response.code() >= 500 -> NetworkResult.Error("服务器错误")
else -> NetworkResult.Error("请求失败: ${response.code()}")
}
}
协程与网络请求
Retrofit 支持 Kotlin 协程,使网络请求代码更加简洁:
// 在 ViewModel 中使用
class MainViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
// 并行请求多个接口
try {
val deferredUsers = async { RetrofitClient.apiService.getUsers() }
val deferredPosts = async { RetrofitClient.apiService.getPosts() }
val users = deferredUsers.await()
val posts = deferredPosts.await()
// 处理数据
} catch (e: Exception) {
// 处理错误
}
}
}
}
// 在 Repository 中使用
class UserRepository {
suspend fun getUsers(): List<User> {
return RetrofitClient.apiService.getUsers()
}
suspend fun getUserById(id: Int): User {
return RetrofitClient.apiService.getUserById(id)
}
}
小结
本章介绍了 Android 网络请求的核心内容:
- 网络权限:添加 INTERNET 权限
- OkHttp:基础 HTTP 客户端,支持同步/异步请求
- Retrofit:类型安全的 REST 客户端,推荐使用
- 错误处理:统一处理网络异常和响应状态
- 协程:简化异步网络请求代码
下一章将学习 Android 架构组件,包括 ViewModel、LiveData 等。