这件事情发生之后告诉我一个非常重要的道理,没有搞清楚理论的东西千万不要去做开发!顿时就能理解为什么项目经理要有丰富的开发经验了。可以少走弯路......
前段时间,我负责写一个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;
}
}
}
RegetInfoUtil.java
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();
记录于此,以便以后查阅。