在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();
}
}