凌峰创科服务平台

Android上传数据到服务器,如何实现高效稳定?

目录

  1. 核心概念:HTTP 请求
  2. Android 网络权限
  3. 上传数据的主要方式
    • 上传键值对 (Form Data)
    • 上传文件 (Multipart)
    • 上传 JSON 数据
  4. 主流网络库对比与选择
    • 原生 HttpURLConnection (不推荐,但作为基础了解)
    • OkHttp (强烈推荐,现代 Android 开发标准)
    • Retrofit (基于 OkHttp,更高级的封装)
  5. 完整代码示例 (使用 OkHttp)
    • 示例 1: 上传键值对和文件
    • 示例 2: 上传纯 JSON 数据
  6. 高级主题与最佳实践
    • 处理大文件与进度显示
    • 在后台执行上传 (使用 WorkManager)
    • 错误处理与重试机制
    • 安全性 (HTTPS)
    • 调试网络请求

核心概念:HTTP 请求

数据上传本质上是向服务器发送一个 HTTP 请求,最常见的两种方法是:

Android上传数据到服务器,如何实现高效稳定?-图1
(图片来源网络,侵删)
  • POST: 用于提交数据,通常用于创建新资源,上传文件、表单数据等都使用 POST
  • PUT: 用于更新已存在的资源。

我们这里主要关注 POST 请求。


Android 网络权限

在任何网络操作之前,你必须在 AndroidManifest.xml 文件中声明网络权限。

<!-- 允许应用完全访问网络 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 如果你的应用运行在 Android 9 (API 28) 或更高版本,默认情况下,应用只能使用 HTTPS。
     如果你的服务器还在使用 HTTP,你需要添加这个标签来允许 HTTP。
     强烈建议在生产环境中使用 HTTPS。 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

上传数据的主要方式

上传键值对

这是最简单的形式,类似于 HTML 表单,上传用户名和密码。

  • Content-Type: application/x-www-form-urlencoded
  • 数据格式: key1=value1&key2=value2

上传文件

当你需要上传图片、视频、文档等文件时,需要使用 multipart/form-data

Android上传数据到服务器,如何实现高效稳定?-图2
(图片来源网络,侵删)
  • Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
  • 数据格式: 一个包含多个部分(part)的请求体,每个部分可以是文本字段(如用户名)或二进制文件(如图片)。boundary 是一个独特的字符串,用于分隔各个部分。

上传 JSON 数据

当你的后端 API 期望接收一个 JSON 对象时,使用这种方式。

  • Content-Type: application/json
  • 数据格式: { "key1": "value1", "key2": "value2" }

主流网络库对比与选择

特性 HttpURLConnection OkHttp Retrofit
易用性 较低,需要手动处理流、线程等 ,提供了简洁的 API 非常高,用接口和注解定义 API
功能 基础功能 非常强大:连接池、拦截器、GZIP、WebSocket 等 非常强大:基于 OkHttp,增加了类型安全、异步/同步支持、RxJava/Coroutines 集成
性能 一般 优秀,自动管理连接池,复用 TCP 连接,性能高 优秀,底层是 OkHttp
推荐场景 简单 demo 或对依赖有严格限制的项目 绝大多数项目,特别是需要处理复杂请求、拦截器或 WebSocket 的项目 构建大型、结构化 API 客户端的首选,代码最清晰、最易维护
  • 新手入门或简单任务:直接使用 OkHttp
  • 专业开发、构建大型 App:使用 Retrofit,它会让你的网络层代码无比优雅和健壮。

完整代码示例 (使用 OkHttp)

OkHttp 是 Google 推荐的网络库,下面我们通过两个例子来展示如何使用它。

示例 1: 上传键值对和文件 (Multipart)

假设我们要上传一个用户名、一张头像图片。

添加 OkHttp 依赖

app/build.gradle 文件的 dependencies 代码块中添加:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0") // 请使用最新版本
}

准备上传的文件

// 从设备相册或相机获取图片的 URI
val imageUri: Uri = ... 
// 将 URI 转换为 File 对象
val file = File(imageUri.path)

编写上传逻辑

import okhttp3.*
import java.io.File
import java.io.IOException
class UploadService {
    private val client = OkHttpClient()
    // 定义一个回调接口,用于通知上传结果
    interface UploadCallback {
        fun onSuccess(response: String)
        fun onFailure(e: IOException)
    }
    fun uploadFileWithParams(username: String, imageFile: File, callback: UploadCallback) {
        // 1. 创建 MultipartBody.Builder
        val requestBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM) // 设置表单类型
            .addFormDataPart("username", username) // 添加普通文本字段
            .addFormDataPart(
                "avatar", // 服务器期望的文件字段名
                imageFile.name, // 文件名
                RequestBody.create("image/jpeg".toMediaTypeOrNull(), imageFile) // 添加文件
            )
            .build()
        // 2. 创建 Request
        val request = Request.Builder()
            .url("https://your-api-endpoint.com/upload") // 替换成你的服务器地址
            .post(requestBody)
            .build()
        // 3. 异步执行请求
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                // 请求失败,在主线程中回调
                callback.onFailure(e)
            }
            override fun onResponse(call: Call, response: Response) {
                // 请求成功
                response.use { // 使用 use 确保响应体被关闭
                    if (response.isSuccessful) {
                        val responseBody = response.body?.string()
                        // 在主线程中回调
                        callback.onSuccess(responseBody ?: "Success with no body")
                    } else {
                        // 服务器返回了错误码 (如 404, 500)
                        val errorBody = response.body?.string()
                        callback.onFailure(IOException("Server returned ${response.code}: $errorBody"))
                    }
                }
            }
        })
    }
}

在 Activity 或 ViewModel 中调用

// 在你的 Activity 或其他地方
val uploadService = UploadService()
val username = "JohnDoe"
val imageFile = File(...) // 获取到的文件
uploadService.uploadFileWithParams(username, imageFile, object : UploadService.UploadCallback {
    override fun onSuccess(response: String) {
        // 切换到主线程更新 UI
        runOnUiThread {
            Toast.makeText(this@YourActivity, "上传成功: $response", Toast.LENGTH_SHORT).show()
            // 可以在这里解析服务器返回的 JSON
        }
    }
    override fun onFailure(e: IOException) {
        // 切换到主线程更新 UI
        runOnUiThread {
            Toast.makeText(this@YourActivity, "上传失败: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }
})

示例 2: 上传纯 JSON 数据

假设我们要向服务器提交一个用户信息对象。

import okhttp3.*
import java.io.IOException
class JsonUploadService {
    private val client = OkHttpClient()
    private val mediaType = "application/json; charset=utf-8".toMediaType()
    interface JsonUploadCallback {
        fun onSuccess(response: String)
        fun onFailure(e: IOException)
    }
    fun uploadUser(user: User, callback: JsonUploadCallback) {
        // 1. 将 User 对象转换为 JSON 字符串
        val jsonBody = user.toJson() // 假设你有一个扩展函数或使用 Gson/Moshi
        // 2. 创建 RequestBody
        val requestBody = RequestBody.create(mediaType, jsonBody)
        // 3. 创建 Request
        val request = Request.Builder()
            .url("https://your-api-endpoint.com/users")
            .post(requestBody)
            .build()
        // 4. 异步执行
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback.onFailure(e)
            }
            override fun onResponse(call: Call, response: Response) {
                response.use {
                    if (response.isSuccessful) {
                        callback.onSuccess(response.body!!.string())
                    } else {
                        callback.onFailure(IOException("Error: ${response.code}"))
                    }
                }
            }
        })
    }
}
// 假设的 User 数据类
data class User(val name: String, val email: String)
// 假设的 JSON 序列化扩展函数
fun User.toJson(): String {
    // 实际项目中请使用 Gson, Moshi 或 Kotlinx Serialization
    return """{"name": "$name", "email": "$email"}"""
}

高级主题与最佳实践

处理大文件与进度显示

OkHttp 本身不直接支持上传进度监听,但可以通过自定义 RequestBody 来实现。

  1. 创建一个 ProgressRequestBody,继承 RequestBody
  2. 在其 writeTo() 方法中,每写入一部分数据,就计算一次进度,并通过回调接口通知 UI 层。
  3. 在上传时,使用这个自定义的 ProgressRequestBody

这是一个常见的模式,网上有大量成熟的实现方案可供参考。

在后台执行上传 (使用 WorkManager)

如果用户离开你的应用或关闭了手机屏幕,上传任务可能会被系统杀死,为了保证上传任务的可靠性,应该使用 WorkManager

  • 优点
    • 可靠性:系统会确保任务最终被执行,即使应用被关闭或重启。
    • 约束:可以设置约束,仅在 Wi-Fi 下执行”或“仅在充电时执行”。
    • 周期性任务:也支持周期性任务。

你需要将你的 OkHttp/Retrofit 代码封装到一个 WorkerCoroutineWorker 中,然后由 WorkManager 来调度执行。

错误处理与重试机制

网络是不可靠的,可能会出现各种错误(无网络、超时、服务器 500 等)。

  • OkHttp/Retrofit:它们会抛出 IOException 或特定的 HTTP 错误,你需要用 try-catchonFailure 回调来处理。
  • WorkManager:内置了重试机制,Worker 返回 Result.retry(),WorkManager 会在一段时间后自动重试,并且重试间隔会越来越长(指数退避)。

安全性 (HTTPS)

在现代应用中,必须使用 HTTPS

  • 为什么? 它能加密数据传输,防止中间人攻击,保护用户隐私。
  • Android 9 (API 28+) 的限制:默认情况下,禁止应用使用不安全的 HTTP 协议,如果你的服务器是 HTTP,你需要:
    1. 强烈建议:将服务器升级为 HTTPS。
    2. 临时方案:在 AndroidManifest.xml<application> 标签中添加 android:usesCleartextTraffic="true",但这会降低安全性,仅用于开发测试。

调试网络请求

调试网络请求非常重要,尤其是在与后端联调时。

  • OkHttp 自带日志:OkHttp 有一个非常棒的内置日志系统,可以打印出请求和响应的详细信息。
// 在 Application 类或初始化 OkHttp 的地方添加
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY) // 打印请求体和响应体
val client = OkHttpClient.Builder()
    .addInterceptor(logging)
    .build()
  • 其他工具
    • Charles ProxyFiddler:可以在你的电脑上设置代理,捕获所有通过该设备的 HTTP/HTTPS 流量(需要配置 SSL Pinning)。
    • Chrome 开发者工具:在手机上开启 USB 调试后,可以在 Chrome 的 chrome://inspect 页面查看网络请求。

希望这份详尽的指南能帮助你顺利地在 Android 上实现数据上传!

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