1.简介:

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;
        }
    }