安卓文件下载之断点续传(一)
2017-09-25 18:28
330 查看
最近学了一段时间node js,结果公司暂时没有业务代码用的到,又先转回了安卓这边。因为之前手机应用更新这一块逻辑一直有些问题,在讨论了一番后决定花段时间把这块重做一遍,更新怎么少的了文件下载,文件下载中又有许多需要注意或者说可以优化的地方,断点续传就是其中的一种优化方式,看了许多网上的例子,自己在经过一周的时间后,总算是做了一个过的去的 demo ,目前 demo 比较完善的实现了开启一个线程断点下载的功能,DownloadManager的方式正在实现中,两者的思路是完全相同的,下面就让我们对这个文件下载 demo 制作的过程进行一下分析:
首先想要实现断点续传当然需要记录终端的 点 在哪,这里的点指的就是文件已经下载的大小,你需要让服务器知道你所已经下载的大小是多少,然后服务器才能返回具体那一段的数据,当然我们还需要记录一些别的内容,如文件名,文件大小,临时文件等等。这里我选用数据库记录这些字段,选择使用了 greedao 这个第三方库,目前项目添加的依赖为:
库的版本比较老了,有想使用新版本的可以去百度搜一下,介绍有很多,这里就不再赘述,该 demo 以1.3版本为例。
首先我们创建一个 CreateDBImpl 类用来生成数据库相关文件:
我在数据库中记录了这么几个字段,右键run一下生成对应的文件:
写一个接口,看看我们可能会有那些用到的方法:
因为做的是个demo 功能与实际需求还有一定的区别,这里大家可以根据需求实现自己的方法。
然后就是下载的流程了,这里我制作了一张流程图,方便大家能看的更清楚一些,之后我们代码的书写过程也会参考这张流程图来写,让人有更清晰的思路:
我们先来实现最开始的几个功能看这个 url 是否符合规矩并在我们的数据库中有所记录:
然后在FileDownload类中开启线程:
最终归并于一个方法,并开启了线程。
在线程中我们初始化一个HttpURLConnection ,并进行一些设置,在这里遇到了关于不少问题,尤其是对于header的一些参数的设置,如获取文件大小为-1,无法下载文件等等,在尝试多遍后目前的设置为:
可以看到我在最开始并没有向header中设置文件从哪开始下载,因为httpURLConnection不能在get某个参数在对某个参数set,如果get代表链接已经发出,就不能在设置了,接下来我们看开断点续传的关键代码:
我们从数据库中获取已下载文件大小,如果为-1的话我们认为这个文件从未下载过,并尝试从请求中获取文件名,与文件大小,并对数据库中的字段一一进行设置。如果下载文件大于0,那么我们认为数据库中已经有了对应的完整的文件信息,并对header中的Range字段进行设置。
接下来自然是获取请求的状态码了:
这里有个地方需要注意,正常的状态码为200,断点续传的状态码为206,所以如果已下载部分文件,状态码却依然返回200,那么我们认为服务器是不支持断点续传的,需要重新下载:
最终效果图:
上面只是逻辑部分的代码,可以下载我github上面的demo作为参考,欢迎提问,欢迎star。
github项目地址:文件下载demo
首先想要实现断点续传当然需要记录终端的 点 在哪,这里的点指的就是文件已经下载的大小,你需要让服务器知道你所已经下载的大小是多少,然后服务器才能返回具体那一段的数据,当然我们还需要记录一些别的内容,如文件名,文件大小,临时文件等等。这里我选用数据库记录这些字段,选择使用了 greedao 这个第三方库,目前项目添加的依赖为:
compile 'de.greenrobot:DaoGenerator:1.3.0' compile 'de.greenrobot:greendao:1.3.7'
库的版本比较老了,有想使用新版本的可以去百度搜一下,介绍有很多,这里就不再赘述,该 demo 以1.3版本为例。
首先我们创建一个 CreateDBImpl 类用来生成数据库相关文件:
/** * Created by 冒险者ztn on 2017/9/11. */ public class CreateDBImpl { public static void main(String args[]) throws Exception { Schema schema = new Schema(1, "com.example.zhangtianning.download.dao"); addDownloadFileSize(schema); DaoGenerator daoGenerator = new DaoGenerator(); String PATH = "app/src/main/java/"; daoGenerator.generateAll(schema, PATH); } private static void addDownloadFileSize(Schema schema) { Entity downloadInfo = schema.addEntity("DownloadFileInfo"); downloadInfo.addIdProperty().primaryKey(); downloadInfo.addLongProperty("fileSize"); downloadInfo.addStringProperty("filePath"); downloadInfo.addStringProperty("fileName"); downloadInfo.addStringProperty("tempFilePath"); downloadInfo.addStringProperty("downloadUrl"); downloadInfo.addLongProperty("hadDownloadSize"); downloadInfo.addIntProperty("downloadProgress"); downloadInfo.addIntProperty("version"); } }
我在数据库中记录了这么几个字段,右键run一下生成对应的文件:
写一个接口,看看我们可能会有那些用到的方法:
/** * 接口 * Created by 冒险者ztn on 2017/9/11. */ public interface DBInstances { /** * 获取所有下载内容信息 */ List<DownloadFileInfo> getDownloadFileInfo(); /** * 根据url获取对应下载内容信息 */ DownloadFileInfo getDownloadFileInfoWithUrl(String url); /** * 保存下载内容信息 */ void setDownloadFileInfo(DownloadFileInfo downloadFileInfo); /** * 清除数据 */ void clearDownloadFileInfo(); /** * 是否有完整文件 * * @param downloadFileInfo * @return */ Boolean hasCompleteFile(DownloadFileInfo downloadFileInfo); /** * 初始化一条数据 * * @param url * @return */ DownloadFileInfo initBaseDownloadFileInfo(String url); /** * 更新一条数据 * * @param downloadFileInfo */ void Updata(DownloadFileInfo downloadFileInfo); }
因为做的是个demo 功能与实际需求还有一定的区别,这里大家可以根据需求实现自己的方法。
然后就是下载的流程了,这里我制作了一张流程图,方便大家能看的更清楚一些,之后我们代码的书写过程也会参考这张流程图来写,让人有更清晰的思路:
我们先来实现最开始的几个功能看这个 url 是否符合规矩并在我们的数据库中有所记录:
private void urlIsInDB(Context context, String url) { if (url.indexOf("http") == 0 || url.indexOf("https") == 0) { downloadFileInfo = DBDaoImpl.getInstance(context).getDownloadFileInfoWithUrl(url); if (downloadFileInfo != null) { if (TextUtils.equals(downloadFileInfo.getHadDownloadSize().toString(), downloadFileInfo.getFileSize().toString())) { File file = new File(downloadFileInfo.getFilePath()); if (file.exists()) { downloadState = STATE_FINISH_DOWNLOAD; OpenApk.getInstance().openFile(mainActivity, file); } else { downloadFileInfo.setHadDownloadSize((long) 0); DBDaoImpl.getInstance(context).Updata(downloadFileInfo); } } else { fileDownload.start(mainActivity, downloadFileInfo); Message message = myWeakHandler.obtainMessage(); downloadState = message.what = STATE_START_DOWNLOAD; myWeakHandler.sendMessage(message); } } else { LogUtils.D(TAG, "没有文件信息!"); fileDownload.start(mainActivity, etUrl.getText().toString()); } } else { Toast.makeText(mainActivity, "请输入一个正确的url", Toast.LENGTH_SHORT).show(); } }
然后在FileDownload类中开启线程:
/** * 开始下载 * * @param downloadurl 文件url地址 */ public void start(Activity activity, String downloadurl) { start(activity, DBDaoImpl.getInstance(activity).initBaseDownloadFileInfo(downloadurl)); } /** * 已经有记录开始下载 * * @param downloadFileInfo 下载信息 */ public void start(Activity activity, DownloadFileInfo downloadFileInfo) { downloadThread = new DownloadThread(downloadFileInfo, mFileDownloadListener, activity); downloadThread.start(); }
最终归并于一个方法,并开启了线程。
在线程中我们初始化一个HttpURLConnection ,并进行一些设置,在这里遇到了关于不少问题,尤其是对于header的一些参数的设置,如获取文件大小为-1,无法下载文件等等,在尝试多遍后目前的设置为:
url = new URL(this.mDownloadUrl); httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setUseCaches(false); // 请求时不使用缓存 httpURLConnection.setConnectTimeout(5 * 1000); // 设置连接超时时间 httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestProperty("Accept-Language", "zh-cn"); httpURLConnection.setRequestProperty("UA-CPU", "x86"); httpURLConnection.setRequestProperty("Accept-Encoding", "gzip");//为什么没有deflate呢 // httpURLConnection.setRequestProperty("Content-type", "text/html"); httpURLConnection.setRequestProperty("Connection", "close"); // httpURLConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)"); httpURLConnection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)"); httpURLConnection.setRequestProperty("Accept", "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*"); // httpURLConnection.setRequestProperty("User-Agent", " Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36");
可以看到我在最开始并没有向header中设置文件从哪开始下载,因为httpURLConnection不能在get某个参数在对某个参数set,如果get代表链接已经发出,就不能在设置了,接下来我们看开断点续传的关键代码:
long downloadSize = mDownloadFileInfo.getHadDownloadSize(); String fileName = mDownloadFileInfo.getFileName(); long fileLength; if (downloadSize == mDownloadFileInfo.getFileSize()) { OpenApk.getInstance().openFile(ctx, new File(mDownloadFileInfo.getFilePath())); interrupt(); } if (downloadSize > 0) { String tmpFilePath = Environment.getExternalStorageDirectory().getPath() + FILE_DOWNLOAD_TEMP_DIR + fileName; LogUtils.D("Tag", "临时文件的路径:" + tmpFilePath); fileLength = mDownloadFileInfo.getFileSize(); httpURLConnection.setRequestProperty("Range", "bytes=" + mDownloadFileInfo.getHadDownloadSize() + "-" + mDownloadFileInfo.getFileSize()); tmpFile = new File(tmpFilePath); if (!tmpFile.exists() || !tmpFile.isFile()) { tmpFile = FileUtils.createTempFile(tmpfileBasePath, fileName); mDownloadFileInfo.setTempFilePath(tmpFile.getAbsolutePath()); } } else { fileLength = httpURLConnection.getContentLength(); // 获取文件的大小 mDownloadFileInfo.setFileSize(fileLength); if (fileName == null) { fileName = httpURLConnection.getHeaderField("Content-Disposition"); // 获取文件名 } if (fileName == null) { fileName = mDownloadUrl.substring(mDownloadUrl.lastIndexOf("/") + 1); } mDownloadFileInfo.setFileName(fileName); mDownloadFileInfo.setFilePath(basePath + File.separator + fileName); String tmpFilePath = Environment.getExternalStorageDirectory().getPath() + FILE_DOWNLOAD_TEMP_DIR + fileName; LogUtils.D("Tag", "临时文件的路径:" + tmpFilePath); tmpFile = FileUtils.createTempFile(tmpfileBasePath, fileName); mDownloadFileInfo.setTempFilePath(tmpFile.getAbsolutePath()); }
我们从数据库中获取已下载文件大小,如果为-1的话我们认为这个文件从未下载过,并尝试从请求中获取文件名,与文件大小,并对数据库中的字段一一进行设置。如果下载文件大于0,那么我们认为数据库中已经有了对应的完整的文件信息,并对header中的Range字段进行设置。
接下来自然是获取请求的状态码了:
int code = httpURLConnection.getResponseCode();
这里有个地方需要注意,正常的状态码为200,断点续传的状态码为206,所以如果已下载部分文件,状态码却依然返回200,那么我们认为服务器是不支持断点续传的,需要重新下载:
if (code == HttpURLConnection.HTTP_OK) { if (mDownloadFileInfo.getHadDownloadSize() > 0) { // 子线程 ToastUtils.showToast(ctx, "文件不支持断点续传,已重新开始下载!"); mDownloadFileInfo.setHadDownloadSize((long) -1); } long currentTime = System.currentTimeMillis(); int bufferSize = 1024; bufferedInputStream = new BufferedInputStream(httpURLConnection.getInputStream(), bufferSize); int len; //读取到的数据长度 byte[] buffer = new byte[bufferSize]; //写入中间文件 mOutputStream = new FileOutputStream(tmpFile, true);//true表示向打开的文件末尾追加数据 mByteOutput = new ByteArrayOutputStream(); // 开始读取 while ((len = bufferedInputStream.read(buffer)) != -1) { mByteOutput.write(buffer, 0, len); mDownloadFileInfo = writeCache(mDownloadFileInfo); progress = DownloadUtils.getProgress(mDownloadFileInfo.getHadDownloadSize(), fileLength); long nowTime = System.currentTimeMillis(); if (currentTime < nowTime - 500) { currentTime = nowTime; mDownloadFileInfo.setDownloadProgress(progress); if (mFileDownloadListener != null) { if (downloadSize == fileLength) { mFileDownloadListener.onFileDownloadCompleted(mDownloadFileInfo); break; } else { mFileDownloadListener.onFileDownloading(mDownloadFileInfo); } } } if (isStopDownload) { if (mFileDownloadListener != null) { mFileDownloadListener.onFileDownloadPaused(mDownloadFileInfo); } break; } } } else if (code == HttpURLConnection.HTTP_PARTIAL) { long currentTime = System.currentTimeMillis(); int bufferSize = 1024; bufferedInputStream = new BufferedInputStream(httpURLConnection.getInputStream(), bufferSize); int len; //读取到的数据长度 byte[] buffer = new byte[bufferSize]; //写入中间文件 mOutputStream = new FileOutputStream(tmpFile, true);//true表示向打开的文件末尾追加数据 mByteOutput = new ByteArrayOutputStream(); // 开始读取 while ((len = bufferedInputStream.read(buffer)) != -1) { mByteOutput.write(buffer, 0, len); mDownloadFileInfo = writeCache(mDownloadFileInfo); progress = DownloadUtils.getProgress(mDownloadFileInfo.getHadDownloadSize(), fileLength); long nowTime = System.currentTimeMillis(); if (currentTime < nowTime - 500) { currentTime = nowTime; mDownloadFileInfo.setDownloadProgress(progress); if (mFileDownloadListener != null) { if (downloadSize == fileLength) { mFileDownloadListener.onFileDownloadCompleted(mDownloadFileInfo); break; } else { mFileDownloadListener.onFileDownloading(mDownloadFileInfo); } } } if (isStopDownload) { if (mFileDownloadListener != null) { mFileDownloadListener.onFileDownloadPaused(mDownloadFileInfo); } break; } } }
最终效果图:
上面只是逻辑部分的代码,可以下载我github上面的demo作为参考,欢迎提问,欢迎star。
github项目地址:文件下载demo
相关文章推荐
- IOS 下载文件断点续传原理与实现(附源码)
- PHP实现文件下载断点续传详解
- 嵌入式 FTP断点续传原理以及实现指定下载文件起始地址
- PHP 大文件下载,文件传输,支持断点续传。 2g以上超大文件也有效
- 以多线程、断点续传方式下载文件的实现
- Asp.net 2.0 文件下载[支持多线程, 断点续传功能](示例代码下载)
- 【SFTP】使用Jsch实现Sftp文件下载-支持断点续传和进程监控
- iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>
- 关于php支持分块与断点续传文件下载功能代码
- Winform文件下载之断点续传
- Android开发文件下载中的断点续传源码
- RxJava+Retrofit+OkHttp实现多文件下载之断点续传
- 文件下载之断点续传(客户端与服务端的实现)
- C# 断点续传 上传、下载文件处理
- php实现的支持断点续传的文件下载类
- C# 文件下载之断点续传实现代码
- php实现的支持断点续传的文件下载类
- PHP实现文件下载断点续传详解
- iOS开发之网络编程--3、NSURLSessionDataTask实现文件下载(离线断点续传下载)