对于Android OTA刷机程序的理解1

这件事情发生之后告诉我一个非常重要的道理,没有搞清楚理论的东西千万不要去做开发!顿时就能理解为什么项目经理要有丰富的开发经验了。可以少走弯路......

前段时间,我负责写一个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;
	}
}


最后,主角是FileDownloadTash.java

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();


到此为止,多线程断点续传的功能都实现了,下载过程也不像之前那样容易被网络环境干扰得太厉害,即使是断了网,用户关了机,等下次开机的时候,依然能从断点开始下载,直到下载完成为止。现在项目有一个新需求,鉴于OTA服务器带宽的问题,现在是p2p下载方式和断点续传这两种方式去下载升级文件,首先是p2p下载,p2p实在没法现在的,到ota服务器上去查漏补缺!至于p2p的代码,其实也是非常清晰的,服务器端帮助客户端进行UDP打洞,一旦打洞成功了,那么两个peer就可以通信了,主要解决的还是NAT问题。至于两个peer之间是怎么通信的细节问题,这可以对UDP包进行数据封装,封装成一个peer之间的一种协议,就行了。

记录于此,以便以后查阅。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值