文件断点续传之TUS

牧云@^-^@ 提交于 2020-01-07 03:44:01

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

文件断点续传之TUS

TUS是github上一个开源的断点续传的框架,服务端是用go写的,客户端也支持很多种,支持js的,andorid,java的,IOS也支持。本文将对android端做一些分析。

先来介绍框架最核心的部分,即如何做到续传。说到续传就是接着上一次上传的位置继续上传,而不用从头开始传。TUS的做法是,客户端在创建链接的时候上传必要的参数,服务端根据客户端传的参数寻找对应的文件,并返回上次上传的位置给客户端。客户端根据服务的返回的偏移量再进行上传。

那客户端要传递什么参数呢?客户端传什么参数才能保证服务器找到的文件就是对应的文件呢?这里使用了文件名和大小作为文件的唯一性。如果文件名相同,文件大小也相同,那么服务端就认为是同一个文件。

        try {
            SharedPreferences pref = getSharedPreferences("tus", 0);
            client = new TusClient();
            client.setUploadCreationURL(new URL("http://192.168.160.1:1080/files/"));
            client.enableResuming(new TusPreferencesURLStore(pref));
        } catch(Exception e) {
            showError(e);
        }

首先创建一个client对象,传入要上传的地址,设置上传信息的preference。

    public void resumeUpload() {
        try {
            TusUpload upload = new TusAndroidUpload(fileUri, this);
            uploadTask = new UploadTask(this, client, upload);
            uploadTask.execute(new Void[0]);
        } catch (Exception e) {
            showError(e);
        }
    }

这段代码创建了上传的对象,并将上传的任务放到线程里面执行。这个里面传入了文件的信息。

 protected URL doInBackground(Void... params) {
            try {
                TusUploader uploader = client.resumeOrCreateUpload(upload);
                long totalBytes = upload.getSize();
                long uploadedBytes = uploader.getOffset();

                // Upload file in 1MiB chunks
                uploader.setChunkSize(1024 * 1024);
                Log.i("upload","upload_tus start");
                while(!isCancelled() && uploader.uploadChunk() > 0) {
                    uploadedBytes = uploader.getOffset();
                    Log.i("upload","upload_tus byte = "+uploadedBytes);
                    publishProgress(uploadedBytes, totalBytes);
                }
                Log.i("upload","upload_tus finish");
                uploader.finish();
                return uploader.getUploadURL();

            } catch(Exception e) {
                exception = e;
                cancel(true);
            }
            return null;
        }

这个是上传的过程,先获取一个uploader对象,在这个uploader对象里面已经完成了和服务器的交互,并获取了偏移量。uploader.setChunkSize这个方法设置了一次读取文件的大小。设置越大一次读取的字节越多。uploader.uploadChunk()这个才是真正上传的地方。这里使用一个while循环来不断的读取文件并上传。publishProgress是为了向用户展示进度条。 下面看看uploader.uploadChunk()的实现。

public int uploadChunk() throws IOException, ProtocolException {
        openConnection();

        int bytesToRead = Math.min(getChunkSize(), bytesRemainingForRequest);

        int bytesRead = input.read(buffer, bytesToRead);
        if(bytesRead == -1) {
            // No bytes were read since the input stream is empty
            return -1;
        }

        // Do not write the entire buffer to the stream since the array will
        // be filled up with 0x00s if the number of read bytes is lower then
        // the chunk's size.
        output.write(buffer, 0, bytesRead);
        output.flush();

        offset += bytesRead;
        bytesRemainingForRequest -= bytesRead;

        if(bytesRemainingForRequest <= 0) {
            finishConnection();
        }

        return bytesRead;
    }

这里首先去打开链接,如果链接已经打开那么就返回。然后再去从文件里面读数据,然后再写入outputstream里面并刷新缓冲区。再将偏移量累加。后面这几段代码就不是很理解了。bytesRemainingForRequest这个是一次请求发送的大小,如果一次链接发送的请求大于这个数则将请求断开,再次循环的时候再进行重新链接。大家一定会这个很纳闷,为什么要重新链接呢。这个其实和客户端使用的inputstream类型是有关系的。如果使用bufferedinputstream,这种类型的inputstream为了减少对文件的操作,会将读取的文件字节进行缓存,读取的字节越多那么缓存越大,这样就会势必造成申请大内存。申请的内存大到一定的程度就可能出现oom。所以这里为了解决这个问题使用了bytesRemainingForRequest来控制bufferedinputstream缓存的大小。这种方式固然缩小了对文件的访问,但是经过我实际的测试,这种方式并不可取,首先每次断开链接再进行重新链接的时候,在传输过程中可能造成tcp reset的异常或者IO错误。特别是在上传大文件的时候,要经过几次的断开链接和链接的过程。为此我在这里舍弃了bufferedinputstream的使用。修改如下的代码:

 public TusInputStream(InputStream stream) {
       /* if(!stream.markSupported()) {
            stream = new BufferedInputStream(stream);
        }*/

        this.stream = stream;
    }

上面屏蔽的代码既是让其不使用bufferedinputstream。并将断开链接的条件删除:

 public int uploadChunk() throws IOException, ProtocolException {
        openConnection();

        int bytesToRead = Math.min(getChunkSize(), bytesRemainingForRequest);

        int bytesRead = input.read(buffer, bytesToRead);
        if(bytesRead == -1) {
            // No bytes were read since the input stream is empty
            return -1;
        }

        // Do not write the entire buffer to the stream since the array will
        // be filled up with 0x00s if the number of read bytes is lower then
        // the chunk's size.
        output.write(buffer, 0, bytesRead);
        output.flush();

        offset += bytesRead;
        /*
        bytesRemainingForRequest -= bytesRead;

        if(bytesRemainingForRequest <= 0) {
            finishConnection();
        }
        */
        return bytesRead;
    }

bytesRemainingForRequest将不再作为关闭链接的条件。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!