Android 通过okHttp上传Uri地址文件

在Android平台上,用户选择文件一般只会返回Uri,此时上传文件不能采用传统的File API来实现,特此记录。

选择文件

触发选择文件Intent如下,一般需要指定mimeType

    final String JPG = "image/jpeg";
    final String PNG = "image/png";
    final String DOC = "application/msword";
    final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    final String XLS = "application/vnd.ms-excel";
    final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    final String PDF = "application/pdf";

    // 选择文件
    uploadFileView.setOnClickListener(o -> {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        String[] mimeTypes = {JPG, PNG, DOC, DOCX, PDF, XLS, XLSX};
        intent.setType("application/*");
        intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, 0);
        }
    });

接收结果只能获取到Uri

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode != RESULT_OK) {
            return;
        }

        if (requestCode == 0) {
            Uri uri = data.getData();
            if (uri == null) {
                return;
            }

            Cursor returnCursor =
                    getContentResolver().query(uri, null, null, null, null);
            if (returnCursor == null) {
                return;
            }
            int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
            returnCursor.moveToFirst();
            String name = returnCursor.getString(nameIndex);
            long size = returnCursor.getLong(sizeIndex);
            returnCursor.close();
            
            // update ui
        }
    }

上传文件

上传文件需要使用MultipartBody,其中文件部分不能使用传统File api,只能使用基于Uri的接口,参考https://developer.android.com/training/secure-file-sharing/request-file#OpenFile

参照该Api,我们只能从Uri拿到InputStream,构造RequestBody方式如下,可以参考https://github.com/square/okhttp/issues/3585#issuecomment-327319196

具体做法如下,传入Uri来构造RequestBody

public class InputStreamRequestBody extends RequestBody {

    private final MediaType contentType;
    private final ContentResolver contentResolver;
    private final Uri uri;

    public InputStreamRequestBody(MediaType contentType, ContentResolver contentResolver, Uri uri) {
        if (uri == null) throw new NullPointerException("uri == null");
        this.contentType = contentType;
        this.contentResolver = contentResolver;
        this.uri = uri;
    }

    @Nullable
    @Override
    public MediaType contentType() {
        return contentType;
    }

    @Override
    public long contentLength() throws IOException {
        return -1;
    }

    @Override
    public void writeTo(@NonNull BufferedSink sink) throws IOException {
        sink.writeAll(Okio.source(contentResolver.openInputStream(uri)));
    }
}

然后我们上传文件时按照常规的Multipart格式即可,具体代码如下(仅演示功能)

    public void uploadFile(String bizId, AppointFile file) {

        OkHttpClient client = new OkHttpClient();
        RequestBody fileBody = new InputStreamRequestBody(MediaType.parse("image/*"), getContentResolver(), file.getUri());
        RequestBody requestBody = new MultipartBody.Builder()
                .addFormDataPart("bizId", bizId)
                .addFormDataPart("bizCode", "attachment")
                .addFormDataPart("file", file.getName(), fileBody)
                .build();
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            String result = response.body().string();
            Log.e("wangxue", "uploadResult:" + result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
举报
评论 0