1. Foreword
Students often ask: how to upload files at breakpoints?
Breakpoint upload / download is a common scenario on the client. When we need to upload or download a large file, we will consider using breakpoint continuation.
The biggest difference between breakpoint upload and breakpoint download lies in the record of breakpoint location. The upload record is on the server and the download record is on the client. Therefore, the client needs to get the breakpoint location of the file through the interface before uploading, and then jump the file input stream to the breakpoint location during uploading
2. Preparatory work
For file upload, it is actually to open the input stream of the file, constantly read the data into the byte array, and then write it to the server; What the client needs to do is skip the uploaded part, that is, directly jump to the breakpoint position, so that the data can be read from the breakpoint position, which achieves the purpose of breakpoint upload.
The pseudo code is as follows:
String filePath = "..."; long skipSize = 100; //Suppose the breakpoint location is 100 byte s InputStream input = input = new FileInputStream(filePath); input.skip(skipSize) //Jump to breakpoint location
However, OkHttp does not directly provide a method to set breakpoints, so the client needs to customize the RequestBody, named FileRequestBody, as follows:
//Some code has been omitted to simplify reading public class FileRequestBody extends RequestBody { private final File file; private final long skipSize; //Breakpoint location 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() 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 breakpoint } source = Okio.source(input); sink.writeAll(source); } finally { OkHttpCompat.closeQuietly(source, input); } } }
For ease of reading, part of the source code is omitted above, FileRequestBody class complete source code
With the FileRequestBody class, we only need to pass in a breakpoint location, and the rest is the same as ordinary file upload. Next, go directly to the code implementation.
3. Code implementation
3.1 get breakpoint location
First, the server needs to provide an interface to find the unfinished task list uploaded by the user through userId. The code is as follows:
RxHttp.get("/.../getToUploadTask") .add("userId", "88888888") .asList<ToUploadTask>() .subscribe({ //The callback is successful. Here you can get the list < touploadtask > through it }, { //Exception callback });
The ToUploadTask class is as follows:
//Tasks 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 //Breakpoint location )
Note: md5 and filePath parameters need to be passed by the client to the server during file upload to verify the file and prevent file disorder
3.2 breakpoint upload
With the task to be uploaded, the client can perform breakpoint upload. The OkHttp code is as follows:
fun uploadFile(uploadTask: ToUploadTask) { //1. Verify whether the file exists val file = File(uploadTask.filePath) if (!file.exists() && !file.isFile) return //2. Verify the md5 value of the file val fileMd5 = FileUtils.getFileMD5ToString(file) if (!fileMd5.equals(uploadTask.md5)) return //3. Build 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 file body .build() //4. Build request val request = Request.Builder() .url("/.../uploadFile") .post(multipartBody) .build() //5. Execution 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 } }) }
Of course, considering that few people will directly use OkHttp, the implementation code of {RxHttp} is also posted here. It is very simple. Just build an UpFile object to easily monitor the upload progress. The code is as follows:
fun uploadFile(uploadTask: ToUploadTask) { //1. Verify whether the file exists val file = File(uploadTask.filePath) if (!file.exists() && !file.isFile) return //2. Verify the md5 value of the file val fileMd5 = FileUtils.getFileMD5ToString(file) if (!fileMd5.equals(uploadTask.md5)) return val upFile = UpFile("file", file, file.name, uploadTask.skipSize) //3. Direct upload 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 }) }
4. Summary
Compared with ordinary file upload, the client has one more breakpoint setting. Most of the workload is on the server. The server not only needs to process the file splicing logic, but also needs to record the unfinished tasks and expose them to the client through the interface.