上一篇
安卓上传图片到服务器端
- 行业动态
- 2025-04-24
- 3365
Android上传图片至服务器需配置网络权限,通过FileProvider获取图片路径,压缩后使用Retrofit+MultipartBody构建POST请求,异步上传至后端接口,服务器解析并存储文件
准备工作
添加必要权限
在AndroidManifest.xml
中声明存储和网络权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/>
依赖库配置
在build.gradle
添加网络请求库(如Retrofit)和图片处理库:
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
图片选择与获取
通过文件选择器获取图片
fun selectImage() { val intent = Intent(Intent.ACTION_GET_CONTENT).apply { type = "image/" putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) } startActivityForResult(intent, IMAGE_PICK_CODE) }
处理返回结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == IMAGE_PICK_CODE && resultCode == RESULT_OK) { val uri = data?.data // 处理URI(需考虑Android 10+沙盒机制) } }
图片预处理
图片压缩(可选)
使用Luban库进行压缩:
implementation 'top.zibin:luban:1.1.0'
Luban.with(this) .load(uri) .setCompressListener { // 获取压缩后的文件路径 }.launch()
文件路径转换(Android 10+适配)
fun getRealPathFromUri(context: Context, uri: Uri): String? { return when (uri.scheme) { "content" -> { val projection = arrayOf(MediaStore.Images.Media.DATA) context.contentResolver.query(uri, projection, null, null, null)?.use { cursor -> val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) cursor.moveToFirst() cursor.getString(columnIndex) } } "file" -> uri.path else -> null } }
上传实现
定义Retrofit接口
interface ApiService { @Multipart @POST("upload/image") suspend fun uploadImage( @Part file: MultipartBody.Part, @Part("description") description: RequestBody ): Response<UploadResponse> }
创建Retrofit实例
val retrofit = Retrofit.Builder() .baseUrl("https://yourserver.com/api/") .addConverterFactory(GsonConverterFactory.create()) .build() val apiService = retrofit.create(ApiService::class.java)
构建请求体
fun prepareFilePart(filePath: String): MultipartBody.Part { val file = File(filePath) val requestBody = RequestBody.create(MediaType.parse("image/jpeg"), file) return MultipartBody.Part.createFormData("image", file.name, requestBody) }
执行上传(协程方式)
GlobalScope.launch(Dispatchers.IO) { try { val response = apiService.uploadImage( filePart = prepareFilePart(imagePath), description = RequestBody.create(null, "Mobile Upload") ) if (response.isSuccessful) { // 处理成功逻辑 } else { // 处理错误响应 } } catch (e: Exception) { // 网络错误处理 } }
进度监听实现
自定义OkHttp拦截器
class ProgressInterceptor(private val listener: (Long, Long, Boolean) -> Unit) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) val contentLength = response.body?.contentLength() ?: -1 val buffer = ByteArray(2048) var totalRead = 0L response.body?.source()?.let { source -> val inputStream = source.inputStream() while (true) { val read = inputStream.read(buffer).takeIf { it >= 0 } ?: break totalRead += read listener(totalRead, contentLength, totalRead == contentLength) } } return response } }
集成进度监听
val okHttpClient = OkHttpClient.Builder() .addInterceptor(ProgressInterceptor { bytesRead, totalBytes, done -> runOnUiThread { progressBar.max = totalBytes.toInt() // 需处理-1情况 progressBar.progress = bytesRead.toInt() if (done) showToast("上传完成") } }) .build() val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build()
服务器端接收示例(Node.js)
const express = require('express'); const multer = require('multer'); const app = express(); const upload = multer({ dest: 'uploads/' }); app.post('/upload/image', upload.single('image'), (req, res) => { const file = req.file; // 保存文件信息到数据库或进行其他处理 res.json({ code: 200, path: file.path }); });
完整流程示意图
步骤 | 客户端操作 | 服务端操作 | 数据格式 |
---|---|---|---|
1 | 选择本地图片 | content:// URI | |
2 | 图片压缩处理 | 二进制文件流 | |
3 | 构建Multipart请求 | multipart/form-data | |
4 | 上传文件流 | 接收文件流 | 分块传输编码 |
5 | 进度监听 | 暂存文件 | 临时缓存 |
6 | 完成回调 | 持久化存储 | JSON响应 |
常见问题与解决方案
Q1:Android 10+系统无法读取外部存储?
解决方案:
- 使用
MediaStore.Images.Media.insertImage()
替代直接文件访问 - 申请
MANAGE_EXTERNAL_STORAGE
权限(需谨慎) - 建议使用Content URI方式处理文件
Q2:大文件上传失败如何处理?
解决方案:
- 启用HTTP分块上传(chunked encoding)
- 服务端配置最大接收内存限制
- 客户端实现断点续传机制
- 示例代码(Retrofit):
val requestBody = RequestBody.create(MediaType.parse("image/jpeg"), file) val part = MultipartBody.Part.createFormData("image", file.name, requestBody)