編輯:關於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 ArrayListmPieceList; 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; } } }
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();
記錄於此,以便以後查閱。
在Android系統中,發一個狀態欄通知還是很方便的。下面我們就來看一下,怎麼發送狀態欄通知,狀態欄通知又有哪些參數可以設置?首先,發送一個狀態欄通知必須用到兩個類: N
BaseActivity是項目中所有activity的基類,含有一些公共的屬性和方法,同時控制toolbar的顯示,以及其他一些功能。。。來看源碼:/** * BaseA
動態加載是什麼應用在運行的時候通過加載一些本地不存在的可執行文件實現一些特定的功能,Android中動態加載的核心思想是動態調用外部的Dex文件,極端的情況下,Andro
1、支持幾乎所有安卓版本,從安卓2.x~7.0安卓4.4、4.4.1和4.4.2是無法支持的,因為當時Google說WebView上傳文件不安全,就去掉了。所以這3個版本