Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 對於Android OTA刷機程序的理解1

對於Android OTA刷機程序的理解1

編輯:關於Android編程

這件事情發生之後告訴我一個非常重要的道理,沒有搞清楚理論的東西千萬不要去做開發!頓時就能理解為什麼項目經理要有豐富的開發經驗了。可以少走彎路......

前段時間,我負責寫一個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之間的一種協議,就行了。

記錄於此,以便以後查閱。



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved