You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 14:17:43