如何在Android中使用HTTP PUT结合Retrofit上传图片、视频、PDF等任意文件?
Hey there! I feel your pain—spending a week stuck on something that feels like it should be straightforward is the worst. Let's get you sorted with Retrofit PUT file uploads, even for big files like videos or PDFs.
Step 1: Confirm Your Dependencies
First, make sure you have Retrofit and OkHttp set up in your module-level build.gradle:
dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.okhttp3:okhttp:4.10.0' }
(You can swap in the latest stable versions if you prefer)
Step 2: Define Your Retrofit PUT Interface
The only major difference from POST uploads is swapping @POST for @PUT. For raw file uploads (where your backend expects the file content directly in the request body), use this interface:
import retrofit2.Response import retrofit2.http.Body import retrofit2.http.PUT import retrofit2.http.Path import okhttp3.RequestBody import okhttp3.ResponseBody interface FileUploadApi { // Replace with your actual backend PUT endpoint path @PUT("upload/{file-id}") suspend fun uploadFile( @Path("file-id") fileId: String, // Optional: if your endpoint needs a unique file identifier @Body fileRequestBody: RequestBody ): Response<ResponseBody> }
Step 3: Handle Large Files & Track Upload Progress
Large files can cause OutOfMemory errors if loaded entirely into memory. Use a custom RequestBody to stream the file in chunks and track progress (a huge win for user experience):
import okhttp3.MediaType import okhttp3.RequestBody import okhttp3.internal.Util import okio.BufferedSink import okio.source import java.io.File class ProgressRequestBody( private val file: File, private val mediaType: MediaType?, private val onProgressUpdate: (Int) -> Unit ) : RequestBody() { override fun contentType(): MediaType? = mediaType override fun contentLength(): Long = file.length() override fun writeTo(sink: BufferedSink) { val source = file.source() val buffer = ByteArray(Util.DEFAULT_BUFFER_SIZE) var bytesWritten = 0L source.use { input -> while (true) { val read = input.read(buffer) if (read == -1) break bytesWritten += read sink.write(buffer, 0, read) // Calculate and send progress update to the UI val progress = ((bytesWritten * 100) / file.length()).toInt() onProgressUpdate(progress) } } } }
Step 4: Implement the Upload Call
Now tie everything together. Use Kotlin Coroutines to run the network call on a background thread (standard practice for Android to avoid ANRs):
import android.os.Bundle import android.widget.ProgressBar import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.MediaType import retrofit2.Retrofit class UploadActivity : AppCompatActivity() { private lateinit var progressBar: ProgressBar override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_upload) progressBar = findViewById(R.id.progress_bar) // Replace with your actual file path (e.g., from a file picker result) val targetFile = File(getExternalFilesDir(null), "large_presentation.pdf") // Adjust the media type based on your file: image/jpeg, video/mp4, application/pdf, etc. val mediaType = MediaType.parse("application/pdf") // Create Retrofit instance with a longer timeout for large files val retrofit = Retrofit.Builder() .baseUrl("https://your-backend-base-url.com/") .client(okhttp3.OkHttpClient.Builder() .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS) .writeTimeout(120, java.util.concurrent.TimeUnit.SECONDS) .build()) .build() val uploadApi = retrofit.create(FileUploadApi::class.java) // Launch the upload process CoroutineScope(Dispatchers.IO).launch { try { val progressBody = ProgressRequestBody(targetFile, mediaType) { progress -> // Update progress bar on the main thread withContext(Dispatchers.Main) { progressBar.progress = progress } } val response = uploadApi.uploadFile("my-unique-file-id", progressBody) withContext(Dispatchers.Main) { if (response.isSuccessful) { Toast.makeText(this@UploadActivity, "Upload successful!", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this@UploadActivity, "Upload failed: ${response.message()}", Toast.LENGTH_SHORT).show() } } } catch (e: Exception) { withContext(Dispatchers.Main) { Toast.makeText(this@UploadActivity, "Error: ${e.localizedMessage}", Toast.LENGTH_SHORT).show() } } } } }
Bonus: Multipart PUT Uploads (If Your Backend Expects Form Data)
If your backend needs form data (like a file field plus additional parameters), @Multipart works with PUT too:
interface FileUploadApi { @Multipart @PUT("upload") suspend fun uploadMultipartFile( @Part file: okhttp3.MultipartBody.Part, @Part("file_name") fileName: RequestBody ): Response<ResponseBody> }
Create the multipart parts like this:
val filePart = okhttp3.MultipartBody.Part.createFormData( "file", targetFile.name, targetFile.asRequestBody(mediaType) ) val fileNameBody = RequestBody.create(MediaType.parse("text/plain"), targetFile.name)
Key Notes to Avoid Headaches
- Permissions: Add
<uses-permission android:name="android.permission.INTERNET"/>to yourAndroidManifest.xml. For local file access, you'll need storage permissions (adjust for Android 13+ scoped storage rules). - Timeout Settings: Large files require longer write timeouts—tweak the OkHttpClient settings as shown.
- Threading: Never run network calls on the main thread to avoid app crashes or ANRs.
This setup streams files instead of loading them fully into memory, so it works seamlessly with any file size. Let me know if you hit any snags!
内容的提问来源于stack exchange,提问作者Ramanjeet

