Android中如何用Volley/AsyncTask的Multipart上传多张图片?
Hey Bharath! I see you're trying to adapt a single-image multipart upload to handle multiple images using Volley or AsyncTask, since Base64 wasn't working for you. Let's walk through working implementations for both approaches that should solve your problem.
Option 1: Using Volley with Custom Multipart Request
Volley is great for efficient network operations, but it doesn't natively support multipart requests—so we'll create a custom request class to handle multiple files.
Step 1: Add Volley Dependency
First, include Volley in your module-level build.gradle:
dependencies { implementation 'com.android.volley:volley:1.2.1' }
Step 2: Create Custom MultipartRequest Class
This class extends Volley's Request to build multipart form data with text parameters and multiple files:
import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.HttpHeaderParser; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class MultipartRequest extends Request<String> { private final Response.Listener<String> mListener; private final Map<String, String> mParams; private final Map<String, File> mFileParams; private final String mBoundary; private static final String LINE_FEED = "\r\n"; private static final String TWO_HYPHENS = "--"; public MultipartRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener, Map<String, String> params, Map<String, File> fileParams) { super(method, url, errorListener); this.mListener = listener; this.mParams = params; this.mFileParams = fileParams; mBoundary = "apiclient-" + System.currentTimeMillis(); } @Override public String getBodyContentType() { return "multipart/form-data; boundary=" + mBoundary; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { // Add text parameters (if any) if (mParams != null && !mParams.isEmpty()) { for (Map.Entry<String, String> entry : mParams.entrySet()) { bos.write((TWO_HYPHENS + mBoundary + LINE_FEED).getBytes()); bos.write(("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINE_FEED).getBytes()); bos.write(LINE_FEED.getBytes()); bos.write(entry.getValue().getBytes()); bos.write(LINE_FEED.getBytes()); } } // Add image files if (mFileParams != null && !mFileParams.isEmpty()) { for (Map.Entry<String, File> entry : mFileParams.entrySet()) { String key = entry.getKey(); File file = entry.getValue(); String fileName = file.getName(); bos.write((TWO_HYPHENS + mBoundary + LINE_FEED).getBytes()); bos.write(("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"" + LINE_FEED).getBytes()); bos.write(("Content-Type: image/jpeg" + LINE_FEED).getBytes()); // Adjust for PNG if needed bos.write(LINE_FEED.getBytes()); // Read and write file bytes FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } fis.close(); bos.write(LINE_FEED.getBytes()); } } // End multipart data bos.write((TWO_HYPHENS + mBoundary + TWO_HYPHENS + LINE_FEED).getBytes()); return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); throw new AuthFailureError("Error building multipart request"); } } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String responseString; try { responseString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (Exception e) { responseString = new String(response.data); } return Response.success(responseString, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } }
Step 3: Use MultipartRequest in MainActivity
Here's how to integrate this into your MainActivity to upload multiple images:
package com.getspot.getspot.imagerestapi; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.VolleyError; import com.android.volley.toolbox.Volley; import java.io.File; import java.util.HashMap; import java.util.Map; public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 100; private RequestQueue mRequestQueue; private final String UPLOAD_URL = "YOUR_SERVER_UPLOAD_ENDPOINT"; // Replace with your server URL @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRequestQueue = Volley.newRequestQueue(this); Button uploadBtn = findViewById(R.id.upload_btn); uploadBtn.setOnClickListener(v -> { if (checkPermissions()) { uploadMultipleImages(); } else { requestPermissions(); } }); } private boolean checkPermissions() { return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED; } private void requestPermissions() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.INTERNET}, PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { uploadMultipleImages(); } else { Toast.makeText(this, "Permissions are required to upload images", Toast.LENGTH_SHORT).show(); } } } private void uploadMultipleImages() { // Replace these with your actual image file paths File image1 = new File("/storage/emulated/0/Pictures/image1.jpg"); File image2 = new File("/storage/emulated/0/Pictures/image2.jpg"); File image3 = new File("/storage/emulated/0/Pictures/image3.jpg"); // Optional text parameters Map<String, String> textParams = new HashMap<>(); textParams.put("user_id", "123"); textParams.put("description", "Multiple images upload via Volley"); // File parameters (match key to your server's expected field name) Map<String, File> fileParams = new HashMap<>(); fileParams.put("images[]", image1); // Use array key for multiple files fileParams.put("images[]", image2); fileParams.put("images[]", image3); MultipartRequest multipartRequest = new MultipartRequest( Request.Method.POST, UPLOAD_URL, response -> { Log.d("UPLOAD_SUCCESS", response); Toast.makeText(MainActivity.this, "Upload successful!", Toast.LENGTH_SHORT).show(); }, error -> { Log.e("UPLOAD_ERROR", error.toString()); Toast.makeText(MainActivity.this, "Upload failed: " + error.getMessage(), Toast.LENGTH_SHORT).show(); }, textParams, fileParams ); mRequestQueue.add(multipartRequest); } }
Option 2: Using AsyncTask (Legacy but Reliable)
If you prefer AsyncTask over Volley, here's a working implementation using HttpURLConnection:
package com.getspot.getspot.imagerestapi; import android.Manifest; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 100; private final String UPLOAD_URL = "YOUR_SERVER_UPLOAD_ENDPOINT"; // Replace with your server URL private final ArrayList<File> imageFiles = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Add your image files to the list imageFiles.add(new File("/storage/emulated/0/Pictures/image1.jpg")); imageFiles.add(new File("/storage/emulated/0/Pictures/image2.jpg")); Button uploadBtn = findViewById(R.id.upload_btn); uploadBtn.setOnClickListener(v -> { if (checkPermissions()) { new UploadMultipleImagesTask().execute(); } else { requestPermissions(); } }); } private boolean checkPermissions() { return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED; } private void requestPermissions() { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.INTERNET}, PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new UploadMultipleImagesTask().execute(); } else { Toast.makeText(this, "Permissions required", Toast.LENGTH_SHORT).show(); } } } private class UploadMultipleImagesTask extends AsyncTask<Void, Void, String> { private static final String LINE_FEED = "\r\n"; private static final String TWO_HYPHENS = "--"; private final String boundary = "apiclient-" + System.currentTimeMillis(); @Override protected String doInBackground(Void... voids) { HttpURLConnection conn = null; DataOutputStream dos = null; try { URL url = new URL(UPLOAD_URL); conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); dos = new DataOutputStream(conn.getOutputStream()); // Add text parameters addFormField(dos, "user_id", "123"); addFormField(dos, "description", "Multiple images via AsyncTask"); // Add images for (File file : imageFiles) { addFilePart(dos, "images[]", file); } // End multipart data dos.writeBytes(TWO_HYPHENS + boundary + TWO_HYPHENS + LINE_FEED); dos.flush(); dos.close(); // Get server response int responseCode = conn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) sb.append(line); br.close(); return sb.toString(); } else { return "Server response code: " + responseCode; } } catch (Exception e) { e.printStackTrace(); return "Error: " + e.getMessage(); } finally { if (conn != null) conn.disconnect(); } } private void addFormField(DataOutputStream dos, String name, String value) throws Exception { dos.writeBytes(TWO_HYPHENS + boundary + LINE_FEED); dos.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" + LINE_FEED); dos.writeBytes(LINE_FEED); dos.writeBytes(value + LINE_FEED); } private void addFilePart(DataOutputStream dos, String fieldName, File file) throws Exception { String fileName = file.getName(); dos.writeBytes(TWO_HYPHENS + boundary + LINE_FEED); dos.writeBytes("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + fileName + "\"" + LINE_FEED); dos.writeBytes("Content-Type: image/jpeg" + LINE_FEED); dos.writeBytes(LINE_FEED); FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) dos.write(buffer, 0, bytesRead); fis.close(); dos.writeBytes(LINE_FEED); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); Log.d("UPLOAD_RESULT", result); if (result.startsWith("Error")) { Toast.makeText(MainActivity.this, "Upload failed: " + result, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "Upload successful!", Toast.LENGTH_SHORT).show(); } } } }
Key Notes:
- Replace
YOUR_SERVER_UPLOAD_ENDPOINTwith your actual server URL that handles multipart form data. - Adjust the
Content-Type(e.g.,image/png) if uploading PNG images. - Ensure your server accepts multiple files using the key you specify (like
images[]for array-based uploads). - Add these permissions to your
AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:minSdkVersion="33"/>
内容的提问来源于stack exchange,提问作者Bharath S

