结合服务端实现断点续传

本人第一次写博客,才疏学浅,希望大牛能指出不足,如有错,请勿喷


什么是断点续传?
    断点续传是指,支持从文件上次中断的地方开始传送数据,而并非是从文件开头传送。这样做的优点是,如果在传输一个比较大的文件,发生连接超时错误时,只要客户端记录下已经接受的文件的大小,下次连接时,告诉服务器上次接受了多少,服务器就能跳过你已经接受部分继续传输,而你客户端只要将其追加在上次保存文件位置之后,就能实现断点续传,而不用从头传输,从而减少提高下载效率。并且当我们需要开发下载暂停,暂停后继续下载的功能时,也可以通过断点续传功能实现。
首先我们先了解一下文件在网络中怎么样传输的
    比如我们要传输一个大文件,先在服务端将其转化成字节流写入,然后一字节一字节经过网络进行传输,到客户端取出该字节流,将其接受,保存成相应的文件类型,记得文件类型即MIME类型,也就是文件后缀要一致,因为电脑会根据文件的MIME类型选择正确方式打开,如果不一致可能会无法打开。当然实际中的传输不是这么简单,因为在传输中还要根据相应的网络协议进行包装,这个我们就不深入研究。
怎么实现断点续传?
    我打个将简单的比方:
比如现在有ABCDEFGH八个字母,我要对着再抄一份(这里比喻 传输过程),我就看一眼A,在新纸上写下A,当写到D的时候笔没水了,这时我只要记下我已经写4个字母,下次继续写的时候,我就跳过4个字母,从E继续抄写,并且是写在上次的D后面,当我抄完剩下EFGH四个字母时,我们就完成了抄写工作。
代码实现:
    当我们要下载一个文件时,我们首先要请求网络获取该文件的相关信息,比如文件名,文件大小,文件下载地址,然后将其保存在一个实体类中,在这个实体类中还可以加入该文件已经下载的大小,下载状态之类的信息,根据开发的需要添加;
服务端代码如下:
<%@page import="java.io.File"%>
<%@ page contentType="text/html; charset=utf-8" language="java"
	errorPage=""%>
<%
	String size;
	String name;
	File file = new File("E:/ecilpse_preject/JSp/WebContent/app/Kugou.apk");
	if (file.isFile()) {
		long size1 = file.length();
		size = size1 + "";
		name = file.getName();
	} else {
		size = "-1";
		name = "";
	}

	String s = "{\"name\":\"" + name + "\",\"size\":\"" + size + "\",\"url\":\"JSp/Download\"}";

	out.print(s);
%>
安卓客户端代码如下:
保存信息的实体类:
/**
 * name : 软件名称
 * url: 软件地址
 * size :软件已下载大小
 * maxsize:软件大小
 */
data class app(var name: String, var url: String, var size: Long, var maxsize: Long) : Serializable
安卓端的请求,由于我做了一些封装,故我贴出部分代码供参考:
 Call.getRetrofit().create(DLretrofit::class.java)
                .getapp().enqueue(object : Callback<appbean> {
            override fun onResponse(call: retrofit2.Call<appbean>?, response: Response<appbean>?) {
                Log.v(TAG, response?.toString())
                if (response != null) {
             //用实体类保存要下载文件信息
                    appx = app(name = response.body()?.name!!, url = response.body()?.url!!, size = 0,
                            maxsize = DLUitl.StringToLong(response.body()?.size!!))
             //以下两步作用为初始化界面的控件显示
                    EventBus.getDefault().post(setView(response.body()?.name ?: "", response.body()?.size ?: ""))
                    EventBus.getDefault().post(showButton())
                }

            }

            override fun onFailure(call: retrofit2.Call<appbean>?, t: Throwable?) {
                EventBus.getDefault().post(ToastShow())//显示错误提示!!
                Log.d("123", t.toString())
            }
        })
当我们获得文件信息后,就可以开始我们的文件下载,这里我们会讲到实现断点续传的关键点 RandomAccessFile类

这类在官方文档说明中是这样的:

    这个类的实例支持读取和写入随机访问文件。随机访问文件的行为与存储在文件系统中的大量字节相同。有一种游标,或索引到隐含的数组中,称为文件指针;输入操作从文件指针开始读取字节,并使文件指针超过读取的字节。如果随机访问文件是以读/写模式创建的,那么输出操作也是可用的;输出操作从文件指针开始写入字节,并使文件指针超过写入的字节。写入隐含数组的当前末尾的输出操作会导致数组被扩展。文件指针可以通过getFilePointer方法读取,并通过seek方法设置。

    通过这类我们就可以随意的从任意位置开始读取大文件,这样当客户端需要下载文件,并需要从多少字节开始传输,这时我们只要操作服务端跳过文件多少字节开始传输即可实现断点续传。

服务端代码如下:
public class Download extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public Download() {
		super();
	}

	@SuppressWarnings("resource")
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String size = request.getParameter("size");
		int size_int = Integer.parseInt(size);
		System.out.println(size);
		File file = new File("E:/ecilpse_preject/JSp/WebContent/app/Kugou.apk");
		RandomAccessFile raf = new RandomAccessFile(file, "rw");
		ServletOutputStream outputStream = response.getOutputStream();
		byte[] bs = new byte[2048];
		raf.seek(size_int);
		int len;
		while ((len = raf.read(bs)) > 0) {
			outputStream.write(bs, 0, len);
		}

	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}
安卓客户端部分代码:
//下载地址+已经下载文件的大小
            val url: String = Constant.url + app.url + "?size=" + app.size.toString()
            Log.v(TAG, url)
//保存地址
            val fileUrl = Constant.fileUrl + app.name
            Log.v(TAG, fileUrl)

 val mConnectionUrl = URL(url)
            val openConnection = mConnectionUrl.openConnection()//打开连接
            openConnection.connectTimeout = 10000//设置连接超时时间
            openConnection.readTimeout = 10000//设置读取超时时间
            inputStream = openConnection.getInputStream()//打开字节流
            fileOutputStream = FileOutputStream(fileUrl, true)//打开本地保存的文件字节流,并从允许追加保存(关键代码)
            bufferedOutputStream = BufferedOutputStream(fileOutputStream)//包装成缓存字节流

            mCurrentTime = System.currentTimeMillis()//记录开始的时间
            val byte: ByteArray = kotlin.ByteArray(2048)
            if (inputStream != null || fileOutputStream != null || bufferedOutputStream != null) {

                len = inputStream!!.read(byte)
                while (idex > 0) {
                    if (!boolean) {//用于停止和暂停功能的实现(如不想实现可以注释掉)
                        break
                    }
                    bufferedOutputStream!!.write(byte, 0, len)
                    app.size = app.size + idex
                    mDownloadSize += len
//                    以下代码实现功能,通过回调,告诉主线程多少时间下载了多少,实现进度条,和下载速度的显示更新(如不想实现可以注释掉)
                    times++
                    if (times > 10) {
                        mNewTime = System.currentTimeMillis()

                        EventBus.getDefault().post(DLCallback(app.size, mNewTime - mCurrentTime, mDownloadSize))
                        mCurrentTime = mNewTime
                        times = 0
                        mDownloadSize = 0
                    }
                    len = inputStream!!.read(byte)
                }
            }
这样就完整的实现断点续传下载文件的整个流程了。

注:安卓端的代码我用的是Kotlin进行编写,有java基础应该大概可以看懂,如想了解相关语法的建议到https://www.kotlincn.net/docs/reference/进行学习,工作中一般用java开发,但我个人比较喜欢用Kotlin进行开发,个人感觉Kotlin代码相当精简,而且在检查是否非空方面很突出

最后谢谢各位的指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值