android 文件上传可以分为两类:一个是小文件,直接上传文件;一个是大文件,这个需要分块上传。Okhttp+Retrofit实现文件上传。
2. 需要的依赖和权限:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.2.5'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3.示例:
3.1.小文件上传:直接上传文件(图片上传为例)
public class UpLoadImageUtils { private static final String TAG = "UpLoadImageUtils"; //需要上传的图片数量 private static int imgSum; //上传成功的图片数量 private static int uploadSuccessNum; private static String enRttId; //失败数量 private static int errorNum; private static TestService apiService; public static void getService(){ Retrofit retrofit = new Retrofit.Builder() .baseUrl(BuildConfig.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); apiService = retrofit.create(TestService.class); } @SuppressLint("CheckResult") public static void uploadImage(String url, File file) { MultipartBody.Builder builder = new MultipartBody.Builder() .setType(MultipartBody.FORM);//表单类型 Map<String, RequestBody> map = new HashMap<>(); //"image/png" 是内容类型,后台设置的类型 RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); builder.addFormDataPart("name", file.getName()); builder.addFormDataPart("size", "" + file.length()); /* * 这里重点注意: * com_img[]里面的值为服务器端需要key 只有这个key 才可以得到对应的文件 * filename是文件的名字,包含后缀名的 比如:abc.png */ builder.addFormDataPart("file", file.getName(), requestBody); MultipartBody body = builder.build(); Observable<BaseResponse> meSetIconObservable = apiService.imgUpload(url, body); meSetIconObservable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(baseResponse -> { Log.i(TAG, JsonUtil.jsonToString(baseResponse)); if ("000000".equalsIgnoreCase(baseResponse.getCode())) { Log.i(TAG, "onComplete: ---" + uploadSuccessNum); errorNum = 0; uploadSuccessNum++; if (imgSum == uploadSuccessNum) { finishUpload(true); uploadSuccessNum = 0; } } else { if (errorNum < 4) { uploadImage(url, file); errorNum++; }else { finishUpload(false); } } }, throwable -> { Log.i(TAG, "onComplete: ---" + throwable.getMessage()); }); } /** * 上传 * * @param compressFile 需要上传的文件 * @param urls 需要上传的文件地址 */ public static void uploadList(List<String> urls, List<File> compressFile) { getService(); //多张图片 imgSum = urls.size(); for (int i = 0; i < compressFile.size(); i++) { uploadImage(urls.get(i), compressFile.get(i)); } } public interface TestService { @POST() Observable<BaseResponse> imgUpload(@Url String url, @Body MultipartBody multipartBody); } }
public class UpLoadImageUtils { private static final String TAG = "UpLoadImageUtils";
//需要上传的图片数量 private static int imgSum;
//上传成功的图片数量 private static int uploadSuccessNum; private static String enRttId;
//失败数量 private static int errorNum;
private static TestService apiService; public static void getService(){ Retrofit retrofit = new Retrofit.Builder() .baseUrl(BuildConfig.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); apiService = retrofit.create(TestService.class); } @SuppressLint("CheckResult") public static void uploadImage(String url, File file) { MultipartBody.Builder builder = new MultipartBody.Builder() .setType(MultipartBody.FORM);//表单类型 Map<String, RequestBody> map = new HashMap<>(); //"image/png" 是内容类型,后台设置的类型 RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); builder.addFormDataPart("name", file.getName()); builder.addFormDataPart("size", "" + file.length()); /* * 这里重点注意: * com_img[]里面的值为服务器端需要key 只有这个key 才可以得到对应的文件 * filename是文件的名字,包含后缀名的 比如:abc.png */ builder.addFormDataPart("file", file.getName(), requestBody); MultipartBody body = builder.build(); Observable<BaseResponse> meSetIconObservable = apiService.imgUpload(url, body); meSetIconObservable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(baseResponse -> { Log.i(TAG, JsonUtil.jsonToString(baseResponse)); if ("000000".equalsIgnoreCase(baseResponse.getCode())) { Log.i(TAG, "onComplete: ---" + uploadSuccessNum); errorNum = 0; uploadSuccessNum++; if (imgSum == uploadSuccessNum) { finishUpload(true); uploadSuccessNum = 0; } } else { if (errorNum < 4) { uploadImage(url, file); errorNum++; }else { finishUpload(false); } } }, throwable -> { Log.i(TAG, "onComplete: ---" + throwable.getMessage()); }); } /** * 上传 * * @param compressFile 需要上传的文件 * @param urls 需要上传的文件地址 */ public static void uploadList(List<String> urls, List<File> compressFile) { getService(); //多张图片 imgSum = urls.size(); for (int i = 0; i < compressFile.size(); i++) { uploadImage(urls.get(i), compressFile.get(i)); } } public interface TestService { @POST() Observable<BaseResponse> imgUpload(@Url String url, @Body MultipartBody multipartBody); } }
3.2.大文件分块上传(视频上传为例)同步
public class UploadMediaFileUtils {
private static final String TAG = "UploadMediaFileUtils";
private static UploadService uploadService;
//基础的裁剪大小20m
private static final long baseCuttingSize = 20 * 1024 * 1024;
//总的块数
private static int sumBlock;
//取消上传
private static boolean isCancel = false;
//是否在上传中
private static boolean isUploadCenter=false;
public static void uploadMediaFile(String url, String uploadName, File file, String appInfo, IOUploadAudioListener ioResultListener) {
if (file.exists()) {
getService();
//总的分块数
sumBlock = (int) (file.length() / baseCuttingSize);
if (file.length() % baseCuttingSize != 0) {
sumBlock = sumBlock + 1;
}
isCancel = false;
isUploadCenter = true;
uploadMedia(url, uploadName, file, appInfo, 1, ioResultListener);
} else {
Log.i(TAG, "文件不存在");
ioResultListener.errorResult("-1", "文件不存在");
}
}
@SuppressLint("CheckResult")
public static void uploadMedia(String url, String uploadName, File file, String appInfo, int currentBlock, IOUploadAudioListener ioResultListener) {
if (isCancel){
Log.i(TAG, "取消上传");
return;
}
byte[] fileStream = cutFile(file, currentBlock - 1, ioResultListener);
if (fileStream == null) {
Log.i(TAG, "uploadMedia: getBlock error");
ioResultListener.errorResult("-1", "fileStream为空");
return;
}
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);//表单类型
RequestBody requestBody = RequestBody.create(MultipartBody.FORM, fileStream);
builder.addFormDataPart("name", uploadName);
builder.addFormDataPart("size", "" + fileStream.length);
Log.i(TAG, "size" + fileStream.length);
builder.addFormDataPart("num", "" + currentBlock);
/*
* 这里重点注意:
* com_img[]里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
* filename是文件的名字,包含后缀名的 比如:abc.png
*/
builder.addFormDataPart("file", file.getName(), requestBody);
MultipartBody body = builder.build();
Observable<BaseResponse> meSetIconObservable = uploadService.mediaUpload("huizhan", appInfo, url, body);
meSetIconObservable.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(baseResponse -> {
if ("000000".equalsIgnoreCase(baseResponse.getCode())) {
double progress = 100 * div(currentBlock, sumBlock, 2);
ioResultListener.progress("" + progress);
if (currentBlock < sumBlock) {
uploadMedia(url, uploadName, file, appInfo, currentBlock + 1, ioResultListener);
return;
}
ioResultListener.successResult(baseResponse);
} else {
ioResultListener.errorResult(baseResponse.getCode(), baseResponse.getDesc());
}
}, throwable -> {
ioResultListener.errorResult("-1", "上传失败");
});
}
public static void getService() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
uploadService = retrofit.create(UploadService.class);
}
public interface UploadService {
@POST()
Observable<BaseResponse> mediaUpload(@Header("X-Biz-Id") String bizId,
@Header("X-App-Info") String AppInfo,
@Url String url,
@Body MultipartBody multipartBody);
}
/**
* 写入本地(测试用)
*
* @param list
*/
public static void writeFile(List<byte[]> list) {
FileWriter file1 = new FileWriter();
String path = Environment.getExternalStorageDirectory() + File.separator + "12345.wav";
try {
file1.open(path);
for (int i = 0; i < list.size(); i++) {
Log.i(TAG, "writeFile: " + list.get(i).length);
file1.writeBytes(list.get(i), 0, list.get(i).length);
}
file1.close();
LogUtils.i(TAG, "writeFile: ");
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] cutFile(File file, int currentBlock, IOUploadAudioListener ioResultListener) {
Log.i(TAG, "getBlockThree:000000---" + currentBlock);
int size = 20 * 1024 * 1024;
byte[] endResult = null;
byte[] result = new byte[size];
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
accessFile.seek(currentBlock * size);
//判断是否整除
if (file.length() % baseCuttingSize != 0) {
//当前的位数和总数是否相等(是不是最后一段)
if ((currentBlock + 1) != sumBlock) {
int len = accessFile.read(result);
out.write(result, 0, len);
endResult = out.toByteArray();
} else {
//当有余数时
//当前位置2147483647-20971520
byte[] bytes = new byte[(int) (file.length() % baseCuttingSize)];
int len = accessFile.read(bytes);
out.write(bytes, 0, len);
endResult = out.toByteArray();
}
} else {
int len = accessFile.read(result);
out.write(result, 0, len);
endResult = out.toByteArray();
}
accessFile.close();
out.close();
} catch (IOException e) {
// e.printStackTrace();
ioResultListener.errorResult("-1", "cutFile失败");
}
return endResult;
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 取消上传
*/
public static void cancelUpload() {
if (isUploadCenter) {
isCancel = true;
}
}
作者:zhang106209
链接:https://juejin.cn/post/7234700513223557180
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。