Android端使用Kloudless API上传文件至云存储的问题求助
问题:Android Volley使用Kloudless API上传文件时仅创建空文件
我想把Android内部存储的文件上传到任意云存储(比如Google Drive、OneDrive),Kloudless提供了通过access token上传文件的API,我在Postman里成功实现了上传,但用Android Volley尝试时,云端只能创建文件但没有内容。下面是我的代码:
public class MainActivity extends AppCompatActivity { Button b; TextView TV; File myFile; String responseString; String path; public String BASE_URL = "https://api.kloudless.com"; private static final int FILE_SELECT_CODE = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TV = findViewById(R.id.textView); b = findViewById(R.id.button); b.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { showFileChooser(); } }); } private void showFileChooser() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); try { startActivityForResult( Intent.createChooser(intent, "Select a File to Upload"), FILE_SELECT_CODE); } catch (android.content.ActivityNotFoundException ex) { Toast.makeText(this, "Please install a File Manager.", Toast.LENGTH_SHORT).show(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == FILE_SELECT_CODE) { if (resultCode == RESULT_OK) { Uri uri = data.getData(); Log.d("TAG", "File Uri: " + uri.toString()); String path = null; try { path = FileUtils.getPath(this, uri); } catch (URISyntaxException e) { e.printStackTrace(); } Log.d("TAG", "File Path: " + path); try { data(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } super.onActivityResult(requestCode, resultCode, data); } public void data() throws FileNotFoundException, UnsupportedEncodingException { final String url = BASE_URL + "/v1/accounts/" + "accountid" + "/storage/files/"; final RequestQueue queue = Volley.newRequestQueue(this); HashMap<String, Object> params = new HashMap<String, Object>(); params.put( "file","somedata"); JSONObject Body=new JSONObject(params); final JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url,Body, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { Log.d("Response", response.toString()); } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d("Error.Response", error.toString()); } }) { @Override public Map<String, String> getHeaders() throws AuthFailureError { HashMap<String, String> params = new HashMap<String, String>(); params.put("Authorization", "Bearer Bearerkey"); params.put("Content-Type", "form-data"); params.put("X-Kloudless-Metadata", "{\"parent_id\":\"root\",\"name\":\"testdone.txt\"}"); return params; } }; queue.add(request); }
解决方案
你的问题核心在于Volley的JsonObjectRequest并不适合处理文件上传——因为文件上传需要使用multipart/form-data格式的请求体,而不是JSON格式。你当前的代码只是把字符串"somedata"作为file参数传入,并没有真正读取并发送文件的二进制数据。
以下是修复步骤:
步骤1:自定义Volley的MultipartRequest类
Volley默认没有提供Multipart请求的实现,我们需要自己写一个支持文件上传的请求类:
import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.toolbox.HttpHeaderParser; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Map; public class MultipartRequest extends Request<NetworkResponse> { private final String boundary = "apiclient-" + System.currentTimeMillis(); private final Response.Listener<NetworkResponse> mListener; private final File mFile; private final Map<String, String> mHeaders; private final String mParentId; private final String mFileName; public MultipartRequest(String url, File file, String parentId, String fileName, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener, Map<String, String> headers) { super(Method.POST, url, errorListener); this.mListener = listener; this.mFile = file; this.mHeaders = headers; this.mParentId = parentId; this.mFileName = fileName; } @Override public Map<String, String> getHeaders() throws AuthFailureError { return mHeaders != null ? mHeaders : super.getHeaders(); } @Override public String getBodyContentType() { return "multipart/form-data; boundary=" + boundary; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); try { // 添加X-Kloudless-Metadata作为form字段 dos.writeBytes("--" + boundary + "\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"X-Kloudless-Metadata\"\r\n\r\n"); String metadata = String.format("{\"parent_id\":\"%s\",\"name\":\"%s\"}", mParentId, mFileName); dos.writeBytes(metadata + "\r\n"); // 添加文件二进制数据 dos.writeBytes("--" + boundary + "\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + mFile.getName() + "\"\r\n"); dos.writeBytes("Content-Type: application/octet-stream\r\n\r\n"); FileInputStream fis = new FileInputStream(mFile); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { dos.write(buffer, 0, bytesRead); } fis.close(); dos.writeBytes("\r\n"); dos.writeBytes("--" + boundary + "--\r\n"); return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) { try { return Response.success(response, HttpHeaderParser.parseCacheHeaders(response)); } catch (Exception e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(NetworkResponse response) { mListener.onResponse(response); } }
步骤2:修改data()方法,使用MultipartRequest上传文件
修改原方法,传入文件路径并创建真实的文件对象,用自定义请求发送:
public void data(String filePath) throws FileNotFoundException { final String url = BASE_URL + "/v1/accounts/" + "accountid" + "/storage/files/"; final RequestQueue queue = Volley.newRequestQueue(this); // 验证文件存在性 File uploadFile = new File(filePath); if (!uploadFile.exists()) { Log.e("TAG", "目标文件不存在:" + filePath); TV.setText("上传失败:文件不存在"); return; } // 设置请求头(仅保留Authorization即可) HashMap<String, String> headers = new HashMap<>(); headers.put("Authorization", "Bearer Bearerkey"); // 替换为你的真实Bearer Token // 发送Multipart请求 MultipartRequest request = new MultipartRequest( url, uploadFile, "root", // 上传到云存储的根目录,可根据需求修改 uploadFile.getName(), // 云端保存的文件名,也可自定义 new Response.Listener<NetworkResponse>() { @Override public void onResponse(NetworkResponse response) { try { String responseStr = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); Log.d("上传成功", responseStr); TV.setText("文件上传完成!"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); TV.setText("上传成功但解析响应失败"); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("上传失败", error.toString()); TV.setText("上传失败:" + error.getMessage()); } }, headers ); queue.add(request); }
步骤3:调整onActivityResult的调用逻辑
在获取到文件路径后,将路径传入data()方法:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == FILE_SELECT_CODE) { if (resultCode == RESULT_OK) { Uri uri = data.getData(); Log.d("TAG", "文件Uri: " + uri.toString()); String path = null; try { path = FileUtils.getPath(this, uri); } catch (URISyntaxException e) { e.printStackTrace(); } Log.d("TAG", "文件路径: " + path); if (path != null) { try { data(path); // 传入真实文件路径 } catch (FileNotFoundException e) { e.printStackTrace(); } } } } }
关键注意点
- Multipart格式要求:文件上传必须使用
multipart/form-data,每个字段(包括文件和元数据)都需要用边界符(boundary)分隔,这是Postman能成功但Volley默认请求失败的核心原因。 - X-Kloudless-Metadata的传递:该参数作为form字段发送比放在请求头中更稳妥,避免格式冲突。
- 文件二进制读取:确保读取文件的原始二进制数据,而不是用字符串占位。
这样修改后,应该就能成功将文件内容上传到云端了。
内容的提问来源于stack exchange,提问作者dinesh reddy
相关产品推荐
相关产品推荐

