凌峰创科服务平台

Android如何与JSON服务器端交互?

  1. 服务器端: 创建一个 API,它能接收 HTTP 请求,并返回 JSON 格式的数据。
  2. Android 客户端: 在 Android 应用中发起网络请求,接收服务器返回的 JSON 数据,并将其解析成 Android 可以使用的对象。

第一部分:服务器端 - 如何设计一个 JSON API

服务器端技术栈有很多选择,Node.js (Express), Java (Spring Boot), Python (Django/Flask), PHP (Laravel) 等,无论使用哪种技术,核心原则是相似的。

Android如何与JSON服务器端交互?-图1
(图片来源网络,侵删)

API 设计

一个典型的 API 端点看起来像这样:https://api.example.com/v1/users

  • HTTP 方法:

    • GET: 从服务器获取数据(最常用)。
    • POST: 向服务器发送数据以创建新资源。
    • PUT/PATCH: 更新服务器上的现有资源。
    • DELETE: 删除服务器上的资源。
  • 响应格式: 服务器返回的数据必须是纯文本的 JSON 格式。

示例:使用 Node.js (Express) 创建一个简单的 API

这是一个非常流行的轻量级服务器框架。

Android如何与JSON服务器端交互?-图2
(图片来源网络,侵删)

安装 Express:

npm init -y
npm install express

创建 server.js 文件:

const express = require('express');
const app = express();
const PORT = 3000;
// 中间件,用于解析 JSON 请求体
app.use(express.json());
// 模拟一个数据库
let users = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob', email: 'bob@example.com' }
];
// 1. GET 请求:获取所有用户列表
app.get('/api/users', (req, res) => {
    res.setHeader('Content-Type', 'application/json');
    res.status(200).json(users);
});
// 2. GET 请求:根据 ID 获取单个用户
app.get('/api/users/:id', (req, res) => {
    const userId = parseInt(req.params.id);
    const user = users.find(u => u.id === userId);
    if (user) {
        res.setHeader('Content-Type', 'application/json');
        res.status(200).json(user);
    } else {
        res.status(404).json({ message: 'User not found' });
    }
});
// 3. POST 请求:创建一个新用户
app.post('/api/users', (req, res) => {
    const newUser = {
        id: users.length + 1,
        name: req.body.name,
        email: req.body.email
    };
    users.push(newUser);
    res.setHeader('Content-Type', 'application/json');
    res.status(201).json(newUser);
});
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

运行服务器:

node server.js

你可以使用 Postman 或浏览器访问 http://localhost:3000/api/users 来获取 JSON 数据。


第二部分:Android 客户端 - 如何与 JSON API 交互

在 Android 中,处理网络请求和 JSON 解析有几种方式,从最基础的到现代推荐的。

使用 HttpURLConnection (传统方式)

这是 Android SDK 自带的,不需要额外依赖,但代码量较多,需要手动处理线程和解析。

步骤:

  1. 添加网络权限 (AndroidManifest.xml):

    <uses-permission android:name="android.permission.INTERNET" />

    <application> 标签中添加 android:usesCleartextTraffic="true" (如果你的 API 是 http 而不是 https)。

  2. 创建数据模型类: 这个类必须与服务器返回的 JSON 结构完全对应,推荐使用 Kotlinx SerializationMoshi 等库,但这里为了简单,我们手动创建。

    // User.kt
    data class User(
        val id: Int,
        val name: String,
        val email: String
    )
  3. 创建网络请求工具类: 注意:网络操作不能在主线程执行,否则会抛出 NetworkOnMainThreadException,我们使用 ThreadCoroutine

    import android.os.Bundle
    import android.util.Log
    import androidx.appcompat.app.AppCompatActivity
    import java.io.BufferedReader
    import java.io.InputStreamReader
    import java.net.HttpURLConnection
    import java.net.URL
    class MainActivity : AppCompatActivity() {
        private val TAG = "MainActivity"
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            // 在后台线程发起请求
            Thread {
                fetchUsers()
            }.start()
        }
        private fun fetchUsers() {
            val apiUrl = "http://10.0.2.2:3000/api/users" // Android 模拟器访问本地电脑的地址是 10.0.2.2
            try {
                val url = URL(apiUrl)
                val urlConnection = url.openConnection() as HttpURLConnection
                urlConnection.requestMethod = "GET"
                urlConnection.connectTimeout = 15000
                urlConnection.readTimeout = 15000
                if (urlConnection.responseCode == HttpURLConnection.HTTP_OK) {
                    val inputStream = urlConnection.inputStream
                    val reader = BufferedReader(InputStreamReader(inputStream))
                    val response = StringBuilder()
                    var line: String?
                    while (reader.readLine().also { line = it } != null) {
                        response.append(line)
                    }
                    reader.close()
                    inputStream.close()
                    // 解析 JSON 并在主线程更新 UI
                    // 注意:这里为了简化,省略了 JSON 解析部分,后面会讲
                    Log.d(TAG, "Response: $response")
                } else {
                    Log.e(TAG, "Error: ${urlConnection.responseCode}")
                }
            } catch (e: Exception) {
                Log.e(TAG, "Exception: ${e.message}")
            }
        }
    }

HttpURLConnection 的缺点很明显:代码冗长,解析 JSON 非常繁琐。


使用现代网络库 (强烈推荐)

现代库极大地简化了网络请求和 JSON 解析。

添加依赖

在你的 build.gradle (Module: app) 文件中添加 Retrofit 和 Moshi 的依赖。

// build.gradle (Module: app)
// Retrofit for networking
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// Moshi for JSON parsing
implementation 'com.squareup.moshi:moshi:1.14.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.14.0' // For Kotlin support
// Kotlin Coroutines for asynchronous programming
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
// Retrofit adapter for Coroutines
implementation 'com.squareup.retrofit2:adapter-coroutines:0.9.2'

设置 Retrofit

创建一个 Retrofit 实例,告诉它你的 API 基础 URL 和如何解析 JSON (使用 Moshi)。

// ApiClient.kt
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
object ApiClient {
    private const val BASE_URL = "http://10.0.2.2:3000/"
    val instance: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    }
}

定义 API 接口

创建一个 Kotlin 接口,用注解来描述你的 API 端点,Retrofit 会实现这个接口。

// ApiService.kt
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
interface ApiService {
    @GET("api/users")
    suspend fun getUsers(): List<User> // 使用 suspend 函数,可以在协程中直接调用
    @GET("api/users/{id}")
    suspend fun getUserById(@Path("id") id: Int): User
    @POST("api/users")
    suspend fun createUser(@Body user: User): User // @Body 表示请求体是 User 对象
}

在 Activity/ViewModel 中使用

你可以像调用普通函数一样发起网络请求,代码非常简洁。

// MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 获取 ApiService 实例
        val apiService = ApiClient.instance.create(ApiService::class.java)
        // 在协程作用域内发起网络请求
        lifecycleScope.launch {
            try {
                // 调用 API
                val users = apiService.getUsers()
                // 在主线程更新 UI
                users.forEach { user ->
                    Log.d(TAG, "Fetched User: ${user.name}, Email: ${user.email}")
                }
            } catch (e: HttpException) {
                // 处理 HTTP 错误 (404, 500)
                Log.e(TAG, "HTTP Error: ${e.code()} - ${e.message()}")
            } catch (e: Exception) {
                // 处理其他错误 (例如网络连接问题)
                Log.e(TAG, "Network Error: ${e.message}")
            }
        }
    }
}

总结与最佳实践

特性 HttpURLConnection Retrofit + Moshi
易用性 低,代码繁琐 高,接口定义清晰
功能 基础功能 强大,支持同步/异步、拦截器、适配器等
JSON 解析 需手动或使用其他库 (如 Gson) 集成 Moshi/Gson,自动转换
线程管理 需手动处理 (AsyncTask, Thread, Handler) 通过 Callsuspend 函数自动处理
推荐度 仅用于简单项目或不想引入第三方库时 强烈推荐,是现代 Android 开发的标准

最佳实践建议:

  1. 使用现代库: 直接选择 Retrofit + Moshi/Gson + Kotlin Coroutines 的组合,这是目前 Android 官方和社区最推崇的方案。

  2. 将网络逻辑与 UI 分离: 在 ViewModel 中进行网络请求,而不是在 ActivityFragment 中。ViewModel 可以帮助配置更改(如屏幕旋转)时保持数据。

  3. 使用协程: suspend 函数让异步代码看起来像同步代码,大大提高了可读性。

  4. 处理错误: 始终捕获 HttpException 来处理服务器返回的错误状态码(如 404, 500),并捕获 IOException 来处理网络连接问题。

  5. 添加数据转换器: 使用 Moshi 或 Gson 的 @Json 注解,可以灵活处理 JSON 和 Kotlin 数据模型之间的字段名不匹配等问题。

    // User.kt
    import com.squareup.moshi.Json
    data class User(
        @Json(name = "id") val userId: Int, // JSON 中的 "id" 映射到 Kotlin 的 "userId"
        @Json(name = "name") val userName: String, // JSON 中的 "name" 映射到 "userName"
        val email: String
    )
分享:
扫描分享到社交APP
上一篇
下一篇