“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

1, the preface

Often there will be students to ask: how to achieve a breakpoint file upload?

Breakpoint upload/download, this is often encountered in the client scenario, when we need to upload or download a large file, will consider the use of breakpoint continuation method.

The biggest difference between breakpoint upload and breakpoint download lies in the record of the breakpoint location. The upload is recorded on the server, and the download is recorded on the client. Therefore, the client needs to get the breakpoint location through the interface before uploading, and then jump the file input stream to the breakpoint location during uploading

2. Preparation

To upload a file, you open the input stream of the file, read the data into a byte array, and then write it to the server. What the client needs to do is skip the part that has been uploaded, that is, jump directly to the breakpoint position, so that the data can be read from the breakpoint position, and thus the purpose of the breakpoint upload is achieved.

The pseudocode is as follows:

String filePath = "...";
long skipSize = 100;  // Assume the breakpoint position is 100 bytes
InputStream input = input = new FileInputStream(filePath);
input.skip(skipSize)  // Jump to the breakpoint position
Copy the code

However, OkHttp does not directly provide a method for setting the breakpoint, so the client needs to customize the RequestBody named FileRequestBody, as follows:

// Part of the code has been omitted to simplify reading
public class FileRequestBody extends RequestBody {

    private final File file;
    private final long skipSize;  // The breakpoint position
    private final MediaType mediaType;

    public FileRequestBody(File file, long skipSize, @Nullable MediaType mediaType) {
        this.file = file;
        this.skipSize = skipSize;
        this.mediaType = mediaType;
    }

    @Override
    public long contentLength(a) throws IOException {
        return file.length() - skipSize;
    }

    @Override
    public void writeTo(@NotNull BufferedSink sink) throws IOException {
        InputStream input = null;
        Source source = null;
        try {
            input = new FileInputStream(file);
            if (skipSize > 0) {
                input.skip(skipSize); // Jump to the breakpoint
            }
            source = Okio.source(input);
            sink.writeAll(source);
        } finally{ OkHttpCompat.closeQuietly(source, input); }}}Copy the code

For convenience reading, the above omitted part of the source code, FileRequestBody class complete source code

With the FileRequestBody class, all we need to do is pass in a breakpoint location and do the same thing as a normal file upload. Next, go straight to the code implementation.

3. Code implementation

3.1 Obtaining the breakpoint location

First, the server needs to provide an interface to find the unfinished task list of the user through the userId. The code is as follows:

RxHttp.get("/... /getToUploadTask")
    .add("userId"."88888888")
    .asList<ToUploadTask>()
    .subscribe({
        List
      
    }, {
        // Exception callback
    });
Copy the code

The ToUploadTask class is as follows:

// The task to be uploaded
data class ToUploadTask(
    val md5: String,          // Md5 of the file, used to verify the uniqueness of the file
    val filePath: String,     // The absolute path of the file on the client
    val skipSize: Long = 0    // The breakpoint position
)
Copy the code

Note: The md5 and filePath parameters need to be passed by the client to the server when the file is uploaded. They are used for file verification to prevent file confusion

3.2 Uploading at breakpoint

With the task to be uploaded, the client can perform a breakpoint upload with the following OkHttp code:

fun uploadFile(uploadTask: ToUploadTask) {
    //1. Check whether the file exists
    val file = File(uploadTask.filePath)
    if(! file.exists() && ! file.isFile)return
    //2. Verify the MD5 value
    val fileMd5 = FileUtils.getFileMD5ToString(file)
    if(! fileMd5.equals(uploadTask.md5))return
    //3. Build the request body
    val fileRequestBody = FileRequestBody(file, uploadTask.skipSize, BuildUtil.getMediaType(file.name))
    val multipartBody = MultipartBody.Builder()
        .addFormDataPart("userId"."88888888")
        .addFormDataPart("md5", fileMd5)
        .addFormDataPart("filePath", file.absolutePath)
        .addFormDataPart("file", file.name, fileRequestBody) // Add the body file
        .build()
    //4. Build the request
    val request = Request.Builder()
        .url("/... /uploadFile")
        .post(multipartBody)
        .build()
    //5. Execute the request
    val okClient = OkHttpClient.Builder().build()
    okClient.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            // Exception callback
        }
        override fun onResponse(call: Call, response: Response) {
            // Successful callback}})}Copy the code

FIleUtils source, BuildUtil source

Of course, considering that few people will use OkHttp directly, the implementation code for RxHttp is also posted here. It is very simple, just build an UpFile object, and it is very convenient to monitor the upload progress. The code is as follows:

fun uploadFile(uploadTask: ToUploadTask) {                            
    //1. Check whether the file exists
    val file = File(uploadTask.filePath)                               
    if(! file.exists() && ! file.isFile)return                         
    //2. Verify the MD5 value
    val fileMd5 = FileUtils.getFileMD5ToString(file)                   
    if(! fileMd5.equals(uploadTask.md5))return                        
    val upFile = UpFile("file", file, file.name, uploadTask.skipSize)  
    //3. Upload directly
    RxHttp.postForm("/... /uploadFile")    
        .add("userId"."88888888")                                     
        .add("md5", fileMd5)                                           
        .add("filePath", file.absolutePath)                            
        .addFile(upFile)                                               
        .upload(AndroidSchedulers.mainThread()) {                       
            // Upload progress callback
        }                                                              
        .asString()                                                    
        .subscribe({                                                    
            // Successful callback
        }, {                                                            
            // Exception callback})}Copy the code

4, summary

Breakpoint upload Compared with ordinary file upload, the client has an extra breakpoint. Most of the work is done on the server. The server not only needs to process the file splicing logic, but also needs to record the unfinished uploading tasks, and expose them to the client through the interface.

5. Recommended reading

RxHttp is an impressive Http request framework

RxHttp, a more elegant coroutine experience than Retrofit

RxHttp is perfect for Android 10/11 upload/download/progress monitoring

RxHttp 3K STAR, any request 3 steps to do, master the request trilogy, master the essence of RxHttp.