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

浏览:
字体:
发布时间:2013-12-22 23:48:38
来源:

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

前段时间,我负责写一个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 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();		}		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 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之间的一种协议,就行了。

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



>更多相关文章
24小时热门资讯
24小时回复排行
资讯 | QQ | 安全 | 编程 | 数据库 | 系统 | 网络 | 考试 | 站长 | 关于东联 | 安全雇佣 | 搞笑视频大全 | 微信学院 | 视频课程 |
关于我们 | 联系我们 | 广告服务 | 免责申明 | 作品发布 | 网站地图 | 官方微博 | 技术培训
Copyright © 2007 - 2024 Vm888.Com. All Rights Reserved
粤公网安备 44060402001498号 粤ICP备19097316号 请遵循相关法律法规
');})();