OkHttp文件上传(1):实现文件上传进度监听

96
08_carmelo
2018.01.19 22:00 字数 278

前言

OkHttp已经支持标准表单形式上传文件,具体方法参考OkHttp相关教程,本文实现文件上传的进度监听。

原理

关键点是自定义 支持进度反馈的RequestBody:
重写write方法按照自定义的SEGMENT_SIZE 来写文件,从而监听进度。

public class FileProgressRequestBody extends RequestBody {

    public interface ProgressListener {
        void transferred( long size );
    }

    public static final int SEGMENT_SIZE = 2*1024; // okio.Segment.SIZE

    protected File file;
    protected ProgressListener listener;
    protected String contentType;

    public FileProgressRequestBody(File file, String contentType, ProgressListener listener) {
        this.file = file;
        this.contentType = contentType;
        this.listener = listener;
    }

    protected FileProgressRequestBody() {}

    @Override
    public long contentLength() {
        return file.length();
    }

    @Override
    public MediaType contentType() {
        return MediaType.parse(contentType);
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
            source = Okio.source(file);
            long total = 0;
            long read;

            while ((read = source.read(sink.buffer(), SEGMENT_SIZE)) != -1) {
                total += read;
                sink.flush();
                this.listener.transferred(total);

            }
        } finally {
            Util.closeQuietly(source);
        }
    }

}

FileProgressRequestBody 以2KB为单位上传,对外暴露回调ProgressListener来发布进度。接着写一个上传管理类:HttpUploader,先构造Request对象:

    protected Request generateRequest(String url){

        // 构造上传请求,模拟表单提交文件
        String formData = String.format("form-data;name=file; filename=%s", FileUtil.pickFileNameFromPath(fileInfo.filePath) );
        FileProgressRequestBody filePart = new FileProgressRequestBody( new File(fileInfo.filePath) , "application/octet-stream" , this );
        MultipartBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addPart( Headers.of("Content-Disposition",formData), filePart )
                .build();

        // 创建Request对象
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();

        return request;
    }

传入文件路径,formData是与服务端的header约定,此处约定:name是文件名称
定义文件上传的执行方法doUpload:

    protected int doUpload(String url){
        try {
            OkHttpClient httpClient = OkHttpClientMgr.Instance().getOkHttpClient();
            call = httpClient.newCall( generateRequest(url) );
            Response response = call.execute();
            if (response.isSuccessful()) {
                sbFileUUID = new StringBuilder();
                return readResponse(response,sbFileUUID);
            } else( ... ) { // 重试
                return STATUS_RETRY;
            }
        } catch (IOException ioe) {
            LogUtil.e(LOG_TAG, "exception occurs while uploading file!",ioe);
        }
        return isCancelled() ? STATUS_CANCEL : STATUS_FAILED_EXIT;
    }

上半部分是OkHttp请求的标准写法,然后对上传结果进行分析,和服务端约定返回结果。
里面的readResponse()方法就是每次上传后读取服务端的结果:
上传成功,可以让服务端返回文件的uuid,从response.body() 读取uuid
上传失败,和服务端约定一个状态码,比如500执行重试。

Android经验分享
Web note ad 1