单线程下载HTTP文件是一件非常简单的事。那么,多线程断点需要什么功能?
1.多线程下载;
2.支持断点;
思路:第一次下载某个文件的时候,创建一个下载线程开始从网络下载文件,把已经下载的部分保存到磁盘文件,当用户通过UI主动暂停下载的时候,把线程停止销毁。
下次下载同一个url的文件的时候,重新创建一个下载线程开始从网络下载文件,如果发现磁盘上保存过该文件,则通过 HttpURLConnection.setRequestProperty("Range", "bytes=本地缓存的文件的字节数-完整文件的大小");
从上次中断的地方请求继续下载。
一、多线程
使用多线程的好处:使用多线程下载会提升文件下载的速度。那么多线程下载文件的过程是:
(1)首先获得下载文件的长度,然后设置本地文件的长度。
HttpURLConnection.getContentLength();//获取下载文件的长度
RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
file.setLength(filesize);//设置本地文件的长度
(2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。
(3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,
比如要下载的文件是1000个字节(编号0-999),分两次下载
HttpURLConnection.setRequestProperty("Range", "bytes=0-499"); 下载文件的前500个字节,0代表第一个字节。
HttpURLConnection.setRequestProperty("Range", "bytes=500-999");
(4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置开始写入数据
二、断点续传
写了一个断点续传的组件
public class SuspendableDownloader { private static final String SDPATH = "//sdcard//" ; private static final String TAG = "SuspendableDownloader" ; public boolean isStopDownload = false ; public void startDownload() { isStopDownload = false ; } public void stopDownload(){ isStopDownload = true ; } public interface CallBack{ public boolean notfiyProgress( int percent); public void onDownLoadCancel(); } private CallBack mCallBack = null ; public void setCallBack(CallBack callback){ this .mCallBack = callback; } public String downLoadFile(String httpUrl) throws IOException { File tmpFile = new File(SDPATH); if (!tmpFile.exists()) { tmpFile.mkdir(); } String fileName = httpUrl.split( "/" )[httpUrl.split( "/" ).length- 1 ]; final RandomAccessFile file = new RandomAccessFile(SDPATH + fileName, "rwd" ); if (file.length() == 0 ) { URL url = new URL(httpUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); int length = conn.getContentLength(); InputStream is = conn.getInputStream(); byte [] buf = new byte [ 256 * 2 ]; conn.connect(); double count = 0 ; if (conn.getResponseCode() >= 400 ) { Log.i(TAG, "time exceed" ); } else { while (count <= 100 && !isStopDownload) { if (is != null ) { int numRead = is.read(buf); if (numRead <= 0 ) { break ; } else { file.write(buf, 0 , numRead); mCallBack.notfiyProgress(( int )(file.length()* 100 /length)); Log.d(TAG, "notifyprogress=" +( int )(file.length()* 100 /length)); } } else { break ; } } if (isStopDownload) { mCallBack.onDownLoadCancel(); } } conn.disconnect(); file.close(); is.close(); } else { Log.d(TAG, "continue file.length() =" +file.length() ); URL url = new URL(httpUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); int length = conn.getContentLength(); HttpURLConnection conn2 = (HttpURLConnection) url.openConnection(); conn2.setRequestProperty( "Range" , "bytes=" +file.length()+ "-" +(length- 1 )); if (file.length() == length) { return SDPATH + fileName; } InputStream is = conn.getInputStream(); byte [] buf = new byte [ 256 * 2 ]; conn.connect(); double count = 0 ; if (conn.getResponseCode() >= 400 ) { Log.i(TAG, "time exceed" ); } else { while (count <= 100 && !isStopDownload) { if (is != null ) { int numRead = is.read(buf); if (numRead <= 0 ) { break ; } else { file.write(buf, 0 , numRead); mCallBack.notfiyProgress(( int )(file.length()* 100 /length)); Log.d(TAG, "notifyprogress=" +( int )(file.length()* 100 /length)); } } else { break ; } } if (isStopDownload) { mCallBack.onDownLoadCancel(); } } conn.disconnect(); file.close(); is.close(); } return SDPATH + fileName; } } |
注意一些地方:
1.错误 java.lang.IllegalStateException: Cannot set request property after connection is made
由于要通过HttpURLConnection getContentLength获取下载文件的长度,之后才能设置Range请求头,但是getContentLength已经与server建立了connection,导致了这个错误。解决方法就是通过两个HttpURLConnection实例,第一次获取下载文件长度,第二次获取文件
2. Thread.stop 已经废弃,建议使用调用interrupt , 但是有些操作是不可打断的。包括进入synchronized段以及Lock.lock(),inputSteam.read()等。解决方法是同一个变量来控制isStopDownload来控制下载线程的退出。