这件事情发生之后告诉我一个非常重要的道理,没有搞清楚理论的东西千万不要去做开发!顿时就能理解为什么项目经理要有丰富的开发经验了。可以少走弯路......
前段时间,我负责写一个OTA刷机程序,兼顾U盘刷机,这两个功能想起来也是非常简单的,U盘刷机就是捕捉U盘挂载的广播,然后获取挂载的路径传给升级程序的主程序,它去搜索该路径下是否有update.zip升级文件并且检查是否适合本机型,如果符合就提示是否复制到机顶盒进行系统升级,如果不符合就不用提示了。OTA升级的话,想想流程也是挺明朗的,通过HTTP协议,将本机的型号和参数上传给OTA服务器,OTA服务器配合升级策略检查是否可以升级,如果可以升级的话,那么就给机顶盒发送升级包的基本信息,包括升级包的大小、版本号、下载地址,升级主程序收到这些信息之后就提示用户是否下载升级系统。当然,刚才说的这些都不是重点,其实这些按着流程走也不会出错,而OTA刷机程序的关键之处在于怎么下载升级包!
网络环境的复杂和不稳定性远远超出了我的想象,在编码调试的时候,服务器和机顶盒都位于同于个局域网,那个传输数据的速度简直是非常快,当时的时候,部门负责人也说了,可以先不用做断点续传,为了以后做P2P下载,当时也就那么做了。之后公司某个产品招标的时候,居然用到了我写的这个不完整的程序,当时招标人员在北京等着我修改代码,忙得跟疯狗似的,一下午脑袋高速运转,简直不是一般的刺激,我想赶过工的程序猿应该知道这是什么滋味!结果还是因为对HTTP协议理解不透彻而搁浅!
我之前以为的断点续传,是服务器端的后台程序给客户端发送文件片下载请求,结果不是这样的! 通过HTTP的GET请求方式,在header属性中添加Range属性,就可以让服务器给自己发送某个文件片,如下代码:
httpGet.addHeader("Range", "bytes=0-100“));
如果服务器本身支持断点续传的话,服务器就会给请求方发送某个绝对路径所表示的文件的第0个位置开始到第100个位置结束的数据,总共101个单位的数据,通常单位是字节(bytes)
怎样测试服务器本身到底支不支持断点续传呢?通过HTTP的GET方式,在header属性中添加属性并且发起请求,如果收到服务器的回应的响应码StatusCode等于206的话,那么就支持断点下载,这里还有一个方式可以查看,就是打印服务器返回给客户端的Headers。下面的代码可以检测:
/** * 获取文件大小 * 是否支持断点续传 */ private void getDownloadFileInfo(HttpClient httpClient) throws IOException, ClientProtocolException, Exception { Log.e("","getDownloadFileInfo entered "); HttpGet httpGet = new HttpGet(mUri); HttpResponse response = httpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 200) { err = ERR_NOT_EXISTS; Log.e(TAG, "HttpGet Response.statusCode = " + statusCode); throw new Exception("resource is not exist!"); } if (mDebug) { Log.e("","============= HttpGet Reponse Header info ================="); for (Header header : response.getAllHeaders()) { Log.e(TAG, header.getName() + " : " + header.getValue()); } } mContentLength = response.getEntity().getContentLength(); Log.e(TAG, "ContentLength = " + mContentLength); httpGet.abort(); httpGet = new HttpGet(mUri); httpGet.addHeader("Range", "bytes=0-" + (mContentLength - 1)); response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 206) { mAcceptRanges = true; Log.e(TAG, "AcceptRanges = true"); } else { Log.e(TAG, "AcceptRanges = false"); } httpGet.abort(); Log.e("","getDownloadFileInfo out "); }在这个方法中,参数httpClient是单例模式生成的类,全局只有它一个httpClient,至于单例模式有啥好处,这里就不用多说了。这里来补充一下Http Reponse头结点中的属性,一般来说有如下的属性
Server、Accept-Ranges、ETag、Last-Modified、Content-Type、Content-Length、Date,我当前的服务器给我返回的HttpGet请求的response的头属性如下:
Server:Apache-Coyote/1.1
Accept-Ranges:bytes
ETag:W/"31767885-1386658749234"
Last-Modified:Tue,10 Dec 2013 06:59:09 GMT
Content-Type:application/zip;charset=UTF-8
Content-Length:31767885
Date:Tue ,17 Dec 2013 01:06:14 GMT
通过这个返回的信息,我们可以看到,服务器时支持Range,即分片下载,单位是bytes,本次response可以取的大小是31767885个字节,这里需要说明一下,如果在httpGet中没有指定Range属性的话,返回的Content-Length是该绝对路径下资源文件的大小。如果指定了Range的话,返回的Content-Length就是本次求情的文件片的大小。
这里就已经解决了取断点的问题了,可以想象,如果服务器本身不支持Range的话,还需要服务器后台程序去帮助取Range来发送给客户端,此时,下载地址就跟支持断点续传的地址不一样了,需要定位到一个请求分片下载的功能模块,该功能模块负责取数据和发送数据给客户端。剩下的事情就是客户端怎样去取服务器上的数据,以及下载策略的问题了。一般来说,下载都放在一个线程里面,比较android的主线程是不安全的。断点续传是这样的,下载线程在下载过程中随时保存当前下载了的文件长度,如果重新开始下载的话,从这个断点开始下载,比如某个线程服务下载从0到1000个自己,在下载到第500个字节时,突然客户端断电了,那么下载开始下载的时候就从第501个字节开始下载,只需要在Range里面封装请求文件片即可!下载方式的话,这里主要分为两种,这两种方式都是建立在服务器支持分片下载的基础上,
单线程断点续传下载
顾名思义,就只有一个线程在下载。可以想象程序的结构,一个while循环直到接收的文件长度大于需要下载文件的总长度为止!while循环中如果发生网络异常,如ConnectionTimeOutException、SocketException、SocketTimeOutException等等,要不就抛出给主调程序处理,要不就自己处理,但是,抛出给主调程序处理是非常好的选择!主调程序判断错误类型,再决定如何处理,是自动继续下载,还是与用户交互等等。
多线程断点续传下载
同理,有多条线程负责下载这个文件。先对整个文件进行分片处理,每一个线程负责下载其中的某一段,计算公式如下 pieceSize = (FileTotalSize / ThreadNum) + 1 ; 可以看出,如果只有一条线程的话,该线程就负责下载整个文件!比如当前有3条线程,需要下载的文件有3000个字节,那么每条线程就负责下载1000个字节。每一条线程的处理机制,就类似于单线程断点下载了。
多线程断点续传为啥比单线程断点续传快?
多线程快,是因为它抢占服务器的资源多! 打个极端的比喻,服务器当前连接有100个,其中客户端A就只有1个连接,客户端B有99个连接,可想而知,服务器对客户端B的响应更快速!
下面是实现多线程断点续传的代码:
FileInfo.java 用于定于文件片
RegetInfoUtil.java 用于记录下载断点
FileDownloadTash.java 是负责下载的类
MyHttpClient.java 一个单例模式的HttpClient
首先看看FileInfo.java
import java.net.URI; import java.util.ArrayList; import android.util.Log; public class FileInfo { private static final String TAG = "FileInfo"; private URI mURI; private long mFileLength; private long mReceivedLength; private String mFileName; private ArrayList<Piece> mPieceList; public URI getURI() { return mURI; } synchronized public void setmURI(URI mURI) { this.mURI = mURI; } public long getReceivedLength() { return mReceivedLength; } synchronized public void setReceivedLength(long length) { mReceivedLength = length; } public long getFileLength() { return mFileLength; } synchronized public void setFileLength(long mFileLength) { this.mFileLength = mFileLength; } public int getPieceNum() { if(mPieceList == null) { mPieceList = new ArrayList<Piece>(); } return mPieceList.size(); } public String getFileName() { return mFileName; } synchronized public void setFileName(String mFileName) { this.mFileName = mFileName; } synchronized public void addPiece(long start, long end, long posNow){ if(mPieceList == null) { mPieceList = new ArrayList<Piece>(); } Piece p = new Piece(start, end, posNow); mPieceList.add(p); } synchronized public void modifyPieceState(int pieceID, long posNew) { Piece p = mPieceList.get(pieceID); if(p != null) { p.setPosNow(posNew); } } public Piece getPieceById(int pieceID) { return mPieceList.get(pieceID); } public void printDebug() { Log.d(TAG, "filename = " + mFileName); Log.d(TAG, "uri = " + mURI); Log.d(TAG, "PieceNum = " + mPieceList.size()); Log.d(TAG, "FileLength = " + mFileLength); int id = 0; for(Piece p : mPieceList) { Log.d(TAG, "piece " + id + " :start = " + p.getStart() + " end = " + p.getEnd() + " posNow = " + p.getPosNow()); id++; } } public class Piece { private long start; private long end; private long posNow; public Piece(long s, long e, long n) { start = s; end = e; posNow = n; } public long getStart() { return start; } public void setStart(long start) { this.start = start; } public long getEnd() { return end; } public void setEnd(long end) { this.end = end; } public long getPosNow() { return posNow; } public void setPosNow(long posNow) { this.posNow = posNow; } } }
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.util.Log; import android.util.Xml; public class RegetInfoUtil { private static final String TAG = "RegetInfoUtil"; public static void writeFileInfoXml(File target, FileInfo fileInfo) throws IllegalArgumentException, IllegalStateException, IOException { FileOutputStream fout; if(!target.exists()) { target.createNewFile(); } fout = new FileOutputStream(target); XmlSerializer xmlSerializer = Xml.newSerializer(); xmlSerializer.setOutput(fout, "UTF-8"); xmlSerializer.startDocument("UTF-8", true); xmlSerializer.startTag(null, "FileInfo"); xmlSerializer.attribute(null, "Name", fileInfo.getFileName()); xmlSerializer.attribute(null, "Length", String.valueOf(fileInfo.getFileLength())); xmlSerializer.attribute(null, "ReceiveLength", String.valueOf(fileInfo.getReceivedLength())); xmlSerializer.attribute(null, "URI", fileInfo.getURI().toString()); xmlSerializer.startTag(null, "Pieces"); for(int id = 0; id < fileInfo.getPieceNum(); id++) { Piece p = fileInfo.getPieceById(id); xmlSerializer.startTag(null, "Piece"); xmlSerializer.attribute(null, "Start", String.valueOf(p.getStart())); xmlSerializer.attribute(null, "End", String.valueOf(p.getEnd())); xmlSerializer.attribute(null, "PosNow", String.valueOf(p.getPosNow())); xmlSerializer.endTag(null, "Piece"); } xmlSerializer.endTag(null, "Pieces"); xmlSerializer.endTag(null, "FileInfo"); xmlSerializer.endDocument(); fout.flush(); fout.close(); Log.i(TAG, fout.toString()); } public static FileInfo parseFileInfoXml(File target) throws XmlPullParserException, URISyntaxException, IOException { FileInfo fileInfo = new FileInfo(); FileInputStream fin = new FileInputStream(target); XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(fin, "UTF-8"); int eventType = xmlPullParser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { String tag = xmlPullParser.getName(); switch (eventType) { case XmlPullParser.START_DOCUMENT: break; case XmlPullParser.START_TAG: if ("FileInfo".equals(tag)) { fileInfo.setFileName(xmlPullParser.getAttributeValue(0)); fileInfo.setFileLength(Integer.valueOf(xmlPullParser.getAttributeValue(1))); fileInfo.setReceivedLength(Integer.valueOf(xmlPullParser.getAttributeValue(2))); fileInfo.setmURI(new URI(xmlPullParser.getAttributeValue(3))); }else if("Piece".equals(tag)) { fileInfo.addPiece((long)Integer.valueOf(xmlPullParser.getAttributeValue(0)), (long)Integer.valueOf(xmlPullParser.getAttributeValue(1)), (long)Integer.valueOf(xmlPullParser.getAttributeValue(2))); Log.d(TAG, "add a Piece"); } break; case XmlPullParser.END_TAG: break; } eventType = xmlPullParser.next(); } fileInfo.printDebug(); return fileInfo; } }
public class FileDownloadTask extends Thread { private String TAG = "FileDownloadTask"; private HttpClient mHttpClient; private String mPath; private String mFileName; private String mTempFileName; private URI mUri; private FileInfo mFileInfo; private boolean mDebug = true; private long mContentLength; private volatile long mReceivedCount; private volatile long mLastReceivedCount; private boolean mAcceptRanges = false; private int mPoolThreadNum; private Handler mProgressHandler; private ExecutorService mDownloadThreadPool; private volatile int err = ERR_NOERR; private boolean requestStop = false; private static final int BUFF_SIZE = 4096; public static final int ERR_CONNECT_TIMEOUT = 1; public static final int ERR_NOERR = 0; public static final int ERR_FILELENGTH_NOMATCH = 2; public static final int ERR_REQUEST_STOP = 3; public static final int ERR_NOT_EXISTS = 4; public static final int ERR_SOCKET_TIMEOUT = 5; public static final int ERR_CLIENT_PROTOCAL = 6 ; public static final int ERR_IOEXCEPTION = 7 ; // message public static final int PROGRESS_UPDATE = 1; public static final int PROGRESS_STOP_COMPLETE = 2; public static final int PROGRESS_START_COMPLETE = 3; public static final int PROGRESS_DOWNLOAD_COMPLETE = 4; public FileDownloadTask(HttpClient httpClient, URI uri, String path,String fileName, int poolThreadNum) { mHttpClient = httpClient; mPath = path; mUri = uri; mPoolThreadNum = poolThreadNum; mReceivedCount = (long) 0; mLastReceivedCount = (long) 0; if (fileName == null) { String uriStr = uri.toString(); mFileName = uriStr.substring(uriStr.lastIndexOf("/") + 1, uriStr.lastIndexOf("?") > 0 ? uriStr.lastIndexOf("?"): uriStr.length()); } else { mFileName = fileName; } if (mFileName.lastIndexOf(".") > 0) { mTempFileName = "." + mFileName.substring(0, mFileName.lastIndexOf(".")) + "__tp.xml"; } else { mTempFileName = "." + mFileName + "__tp.xml"; } Log.d(TAG, "DestFileName = " + mFileName); Log.d(TAG, "TempFileName = " + mTempFileName); Log.d(TAG, "ThreadNum = " + poolThreadNum); } public void setProgressHandler(Handler progressHandler) { mProgressHandler = progressHandler; } @Override public void run() { startTask(); } private void startTask() { err = ERR_NOERR; requestStop = false; try { getDownloadFileInfo(mHttpClient); } catch (ClientProtocolException e1) { Log.e(TAG, "ClientProtocolException"); err = ERR_CLIENT_PROTOCAL; e1.printStackTrace(); onProgressStopComplete(err); return ; } catch (ConnectTimeoutException e1) { err = ERR_CONNECT_TIMEOUT; Log.e(TAG, "ConnectTimeoutException"); onProgressStopComplete(err); return ; } catch (SocketTimeoutException e1) { err = ERR_SOCKET_TIMEOUT; Log.e(TAG, "SocketTimeOutException"); onProgressStopComplete(err); return ; }catch (Exception e1) { err = ERR_IOEXCEPTION; onProgressStopComplete(err); Log.e(TAG, e1.toString()); return ; } try { startWorkThread(); monitor(); finish(); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "startWorkThread IOException "); err = ERR_CONNECT_TIMEOUT; onProgressStopComplete(err); } catch (Exception e) { e.printStackTrace(); err = ERR_CONNECT_TIMEOUT; onProgressStopComplete(err); } } public void stopDownload() { err = ERR_REQUEST_STOP; requestStop = true; } private void onProgressUpdate() { long receivedCount = mReceivedCount; long contentLength = mContentLength; long receivedPerSecond = (mReceivedCount - mLastReceivedCount); if (mProgressHandler != null) { Message m = new Message(); m.what = PROGRESS_UPDATE; Bundle b = new Bundle(); b.putLong("ContentLength", contentLength); b.putLong("ReceivedCount", receivedCount); b.putLong("ReceivedPerSecond", receivedPerSecond); m.setData(b); mProgressHandler.sendMessage(m); } mLastReceivedCount = mReceivedCount; } private void onProgressStopComplete(int errCode) { if (mProgressHandler != null) { Message m = new Message(); m.what = PROGRESS_STOP_COMPLETE; Bundle b = new Bundle(); b.putInt("err", errCode); m.setData(b); mProgressHandler.sendMessage(m); } } private void onProgressStartComplete() { if (mProgressHandler != null) { Message m = new Message(); m.what = PROGRESS_START_COMPLETE; mProgressHandler.sendMessage(m); } } private void onProgressDownloadComplete() { if (mProgressHandler != null) { Message m = new Message(); m.what = PROGRESS_DOWNLOAD_COMPLETE; mProgressHandler.sendMessage(m); } } private void finish() throws InterruptedException, IllegalArgumentException, IllegalStateException, IOException { if (err == ERR_NOERR) { String fullTempfilePath = mPath.endsWith("/") ? (mPath + mTempFileName) : (mPath + "/" + mTempFileName); File f = new File(fullTempfilePath); if (f.exists()) { f.delete(); Log.e(TAG, "finish(): delete the temp file!"); } onProgressDownloadComplete(); Log.e(TAG, "download successfull"); return; } else if (err == ERR_REQUEST_STOP) { mDownloadThreadPool.shutdownNow(); while (!mDownloadThreadPool.awaitTermination(1, TimeUnit.SECONDS)) { Log.e(TAG, "monitor: progress ===== " + mReceivedCount + "/" + mContentLength); onProgressUpdate(); } } else if (err == ERR_CONNECT_TIMEOUT) { mDownloadThreadPool.shutdown(); while (!mDownloadThreadPool.awaitTermination(1, TimeUnit.SECONDS) && requestStop == false) { Log.e(TAG, "monitor: progress ===== " + mReceivedCount + "/"+ mContentLength); onProgressUpdate(); } mDownloadThreadPool.shutdownNow(); while (!mDownloadThreadPool.awaitTermination(1, TimeUnit.SECONDS)); } String fullTempfilePath = mPath.endsWith("/") ? (mPath + mTempFileName) : (mPath + "/" + mTempFileName); Log.e(TAG, "tempfilepath = " + fullTempfilePath); File f = new File(fullTempfilePath); RegetInfoUtil.writeFileInfoXml(f, mFileInfo); Log.e(TAG, "download task not complete, save the progress !!!"); onProgressStopComplete(err); } private void monitor() { onProgressStartComplete(); while (mReceivedCount < mContentLength && err == ERR_NOERR) { Log.e(TAG, "Download Progress == " + mReceivedCount + "/" + mContentLength); try { Thread.sleep(1000);// 500 秒刷新一次界面 onProgressUpdate(); } catch (InterruptedException e) { e.printStackTrace(); } } if (err == ERR_CONNECT_TIMEOUT) { Log.e(TAG, "monitor : ERR_CONNECT_TIMEOUT"); } if (err == ERR_REQUEST_STOP) { Log.e(TAG, "monitor: ERR_REQUEST_STOP"); } } private int startWorkThread() throws Exception { Log.e("","startWorkThread entered "); // 找到下载文件和临时文件的完整路径。 String fullPath = mPath.endsWith("/") ? (mPath + mFileName) : (mPath + "/" + mFileName); String fullTempfilePath = mPath.endsWith("/") ? (mPath + mTempFileName) : (mPath + "/" + mTempFileName); Log.d(TAG, "FilePath = " + fullPath); Log.d(TAG, "TempFilePath = " + fullTempfilePath); File targetFile = new File(fullPath); if (!targetFile.exists()) { targetFile.createNewFile(); } else { File tmpFile = new File(fullTempfilePath); if (tmpFile.exists()) { mFileInfo = RegetInfoUtil.parseFileInfoXml(tmpFile); Log.e(TAG, "Try to continue download!"); } else { targetFile.delete(); targetFile.createNewFile(); Log.e(TAG, "Delete and rewrite it!!!"); } } if (mFileInfo == null) { mFileInfo = new FileInfo(); mFileInfo.setFileLength(mContentLength); mFileInfo.setmURI(mUri); mFileInfo.setFileName(mFileName); mFileInfo.setReceivedLength(0); } if (mFileInfo.getFileLength() != mContentLength && mFileInfo.getURI().equals(mUri)) { err = ERR_FILELENGTH_NOMATCH; Log.e(TAG,"FileLength or uri not the same, you can't continue download!"); throw new Exception("ERR_FILELENGTH_NOMATCH!"); } DownloadListener listener = new DownloadListener() { public void onPerBlockDown(int count, int pieceId, long posNew) { synchronized (this) { mReceivedCount += count; } mFileInfo.modifyPieceState(pieceId, posNew); mFileInfo.setReceivedLength(mReceivedCount); } public void onPieceComplete() { Log.e(TAG, "one piece complete"); } public void onErrorOccurre(int pieceId, long posNow) { Log.e(TAG, "ErrorOccurre pieceId = " + pieceId + " posNow = " + posNow); mFileInfo.modifyPieceState(pieceId, posNow); } }; if (mAcceptRanges) { Log.e(TAG, "Support Ranges"); if (mDownloadThreadPool == null) { mDownloadThreadPool = Executors.newFixedThreadPool(mPoolThreadNum); } if (mFileInfo.getPieceNum() == 0) { long pieceSize = (mContentLength / mPoolThreadNum) + 1; long start = 0, end = pieceSize - 1; int pieceId = 0; do {// 分片操作 if (end > mContentLength - 1) { end = mContentLength - 1; } Log.e(TAG, "Piece" + pieceId + " == " + start + "-----" + end); DownloadFilePieceRunnable task = new DownloadFilePieceRunnable(targetFile, pieceId, start, end, start, true); mFileInfo.addPiece(start, end, start); task.setDownloadListener(listener); mDownloadThreadPool.execute(task); start += pieceSize; end = start + pieceSize - 1; pieceId++; } while (start < mContentLength); } else { Log.e(TAG, "try to continue download ====>"); mReceivedCount = mFileInfo.getReceivedLength(); for (int index = 0; index < mFileInfo.getPieceNum(); index++) { Piece p = mFileInfo.getPieceById(index); DownloadFilePieceRunnable task = new DownloadFilePieceRunnable( targetFile, index, p.getStart(), p.getEnd(), p.getPosNow(), true); task.setDownloadListener(listener); mDownloadThreadPool.execute(task); } } } else { Log.e(TAG, "Can't Ranges!"); if (mDownloadThreadPool == null) { mDownloadThreadPool = Executors.newFixedThreadPool(1); } if (mFileInfo.getPieceNum() == 0) { DownloadFilePieceRunnable task = new DownloadFilePieceRunnable( targetFile, 0, 0, mContentLength - 1, 0, false); mFileInfo.addPiece(0, mContentLength - 1, 0); task.setDownloadListener(listener); mDownloadThreadPool.execute(task); } else { Log.e(TAG, "try to continue download ====>"); mReceivedCount = (long) 0; Piece p = mFileInfo.getPieceById(0); p.setPosNow(0); DownloadFilePieceRunnable task = new DownloadFilePieceRunnable( targetFile, 0, 0, mContentLength - 1, p.getPosNow(), false); task.setDownloadListener(listener); mDownloadThreadPool.execute(task); } } Log.e("","startWorkThread out "); return 0; } /** * 获取文件大小 * 是否支持断点续传 */ private void getDownloadFileInfo(HttpClient httpClient) throws IOException, ClientProtocolException, Exception { Log.e("","getDownloadFileInfo entered "); HttpGet httpGet = new HttpGet(mUri); HttpResponse response = httpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 200) { err = ERR_NOT_EXISTS; Log.e(TAG, "HttpGet Response.statusCode = " + statusCode); throw new Exception("resource is not exist!"); } if (mDebug) { Log.e("","============= HttpGet Reponse Header info ================="); for (Header header : response.getAllHeaders()) { Log.e(TAG, header.getName() + " : " + header.getValue()); } } mContentLength = response.getEntity().getContentLength(); Log.e(TAG, "ContentLength = " + mContentLength); httpGet.abort(); httpGet = new HttpGet(mUri); httpGet.addHeader("Range", "bytes=0-" + (mContentLength - 1)); response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 206) { mAcceptRanges = true; Log.e(TAG, "AcceptRanges = true"); } else { Log.e(TAG, "AcceptRanges = false"); } httpGet.abort(); Log.e("","getDownloadFileInfo out "); } private interface DownloadListener { public void onPerBlockDown(int count, int pieceId, long posNew); public void onPieceComplete(); public void onErrorOccurre(int pieceId, long posNew); } private class DownloadFilePieceRunnable implements Runnable { private File mFile; private long mStartPosition; private long mEndPosition; private long mPosNow; private boolean mIsRange; private DownloadListener mListener; private int mPieceId; public DownloadFilePieceRunnable(File file, int pieceId, long startPosition, long endPosition, long posNow, boolean isRange) { mFile = file; mStartPosition = startPosition; mEndPosition = endPosition; mIsRange = isRange; mPieceId = pieceId; mPosNow = posNow; } public void setDownloadListener(DownloadListener listener) { mListener = listener; } public void run() { if (mDebug) { Log.e(TAG, "Range: " + mStartPosition + "-" + mEndPosition + " 现在的位置:" + mPosNow); } try { HttpGet httpGet = new HttpGet(mUri); if (mIsRange) { httpGet.addHeader("Range", "bytes=" + mPosNow + "-" + mEndPosition); } HttpResponse response = mHttpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); if (mDebug) { for (Header header : response.getAllHeaders()) { Log.e(TAG, header.getName() + ":" + header.getValue()); } Log.e(TAG, "statusCode:" + statusCode); } if (statusCode == 206 || (statusCode == 200 && !mIsRange)) { InputStream inputStream = response.getEntity().getContent(); @SuppressWarnings("resource") RandomAccessFile outputStream = new RandomAccessFile(mFile, "rw"); outputStream.seek(mPosNow); int count = 0; byte[] buffer = new byte[BUFF_SIZE]; // 4K 一片 while ((count = inputStream.read(buffer, 0, buffer.length)) > 0) { if (Thread.interrupted()) { Log.e("WorkThread", "interrupted ====>>"); httpGet.abort(); return; } outputStream.write(buffer, 0, count); mPosNow += count; afterPerBlockDown(count, mPieceId, mPosNow); } outputStream.close(); httpGet.abort(); } else { httpGet.abort(); throw new Exception(); } } catch (IOException e) { ErrorOccurre(mPieceId, mPosNow); err = ERR_CONNECT_TIMEOUT; return; } catch (Exception e) { e.printStackTrace(); ErrorOccurre(mPieceId, mPosNow); err = ERR_CONNECT_TIMEOUT; return; } onePieceComplete(); if (mDebug) { Log.e(TAG, "End:" + mStartPosition + "-" + mEndPosition); } } private void afterPerBlockDown(int count, int pieceId, long posNew) { if (mListener != null) { mListener.onPerBlockDown(count, pieceId, posNew); } } private void onePieceComplete() { if (mListener != null) { mListener.onPieceComplete(); } } private void ErrorOccurre(int pieceId, long posNew) { if (mListener != null) { mListener.onErrorOccurre(pieceId, posNew); } } } }
一个单例模式的HttpClient
import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; public class MyHttpClient { private static final String CHARSET = HTTP.UTF_8; private static HttpClient customerHttpClient; private MyHttpClient() { } public static synchronized HttpClient getHttpClient() { if (null == customerHttpClient) { HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, CHARSET) ; HttpProtocolParams.setUseExpectContinue(params, true); HttpProtocolParams.setUserAgent(params, "Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) "+"AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1"); ConnManagerParams.setTimeout(params, 1000); HttpConnectionParams.setConnectionTimeout(params, 10 * 1000); HttpConnectionParams.setSoTimeout(params, 4000); customerHttpClient = new DefaultHttpClient(params); } return customerHttpClient; } public static synchronized void closeHttpClient() { if(customerHttpClient != null) { customerHttpClient.getConnectionManager().shutdown(); customerHttpClient = null; } } }
这4个文件就撑起了断点续传的主要框架,至于是多线程还是单线程,只需要在初始化FileDownloadTask这个对象时给ThreadNum赋的值,在主调程序中需要给FileDownloadTask对象一个绝对的URI,比如htttp://192.168.10.114:8080/package/ota_update_3.2.9.zip,使用的方法如下:
FileDownloadTask mTask = null ;
String uriStr = "htttp://192.168.10.114:8080/package/ota_update_3.2.9.zip";
URI mUri = null;
try {
mUri = new URI(uriStr);
} catch (URISyntaxException e) {
e.printStackTrace();
}
String mDownloadDir = "/mnt/sdcard";
String mDownloadFileName = "update.zip";
mTask = new FileDownloadTask(MyHttpClient.getHttpClient(), mUri,mDownloadDir, mDownloadFileName, 1);// 当前表示只有1个线程负责下载
mTask.setProgressHandler(mProgressHandler);// 设置Handler,用于结束下载过程中发送给主调程序的消息
mTask.start();
记录于此,以便以后查阅。