編輯:關於Android編程
最近在給我的開源下載框架Aria增加FTP斷點續傳下載和上傳功能,在此過程中,爬了FTP的不少坑,終於將功能實現了,在此把一些核心功能點記錄下載。
FTP下載原理
FTP單線程斷點續傳
FTP和傳統的HTTP協議有所不同,由於FTP沒有所謂的頭文件,因此我們不能像HTTP那樣通過設置header向服務器指定下載區間。
但是FTP協議提供了一個更好用的命令REST用於從指定位置恢復任務,同時FTP協議也提供了一個命令SIZE用於獲取下載的文件大小,有了這兩個命令,FTP斷點續傳也就沒有什麼問題。
FTP斷點續傳的原理和HTTP的斷點續傳原理差不多,在暫停時記錄文件的停止位置,再次下載時,先讀取記錄的位置,如果位置存在,則通過REST命令告訴服務器從指定區間進行下載。
FTP多線程斷點續傳
多線程下載的原理和HTTP多線程下載的原理差不多。先獲取文件大小,然後根據線程數,對整個文件進行分段下載,在任務停止時,記錄每一條線程的暫停位置,重新開始下載,每一條線程讀取對應的下載記錄,然後每一線程從指定位置開始下載。
分段下載
和HTTP所不同的是,FTP並沒有提供文件區間的API,因此,FTP在分段下載中,只有起始位置而沒有結束位置。
因此,你需要在指定位置手動停止線程。
功能實現
本文使用將采用apache commons-net實現FTP斷點續傳下載\上傳功能。<br>
通過下文的幾步操作,你就能很簡單的實現FTP斷點續傳。
登錄
FTP協議和HTTP協議有所不同,使用FTP進行下載時,你需要進行登錄操作。
當然,如果你服務器沒有登錄功能,你可以忽略登錄操作。
FTPClient client = new FTPClient(); client.connect(serverIp, port); //連接到FTP服務器 client.login(userName, passsword);
通過上面三行代碼,就可以很簡單的登錄到FTP服務器上。
在進行登錄後,還需要驗證是否登錄成功
int reply = client.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { client.disconnect(); Log.d(TAG, "無法連接到ftp服務器,錯誤碼為:" + reply); return; }
由於FTP協議中,連接成功的狀態有多個,因此需要通過FTPReply.isPositiveCompletion(reply)
用於驗證是否成功連接到FTP服務器。
文件信息獲取
在連接到FTP服務器後,就需要開始獲取下載最重要的幾個參數(文件長度、文件名)。
客戶端可以通過client.listFiles(remotePath)
獲取FTP服務器上該路徑的文件列表。
String remotePath = "/upload/qjnn.apk"; //FTP服務器上文件路徑 FTPFile[] files = client.listFiles(remotePath); FTPFile file = files[0]; //文件信息 long size = file.getSize(); String fileaName = file.getName();
如果你的文件為英文名,並且路徑中沒有中文,那麼通過上述代碼,便可以獲取到正確的文件信息。
但如果FTP上的服務器上的文件名有中文或路徑有中文,那麼上述代碼,你將獲取不到正確的文件信息。
正確的寫法
由於FTP服務器默認的編碼是ISO-8859-1,因此,客戶端在獲取文件信息時
String remotePath = "/upload/qjnn.apk"; //FTP服務器上文件路徑 String charSet = "UTF-8"; if (!FTPReply.isPositiveCompletion(client.sendCommand("OPTS UTF8", "ON"))) { //向服務器請求使用"UTF-8"編碼 charSet = "GBK"; } FTPFile[] files = client.listFiles(new String(remotePath.getBytes(charSet), "ISO-8859-1")); //對remotePath進行編碼轉換 FTPFile file = files[0]; //文件信息 long size = file.getSize(); String fileaName = new String(fileName.getBytes(), Charset.forName(charSet));
通過以上代碼,便可以獲取到正確的文件信息。
文件下載
配置每條線程的下載區間
long fileLength = mEntity.getFileSize(); Properties pro = CommonUtil.loadConfig(mConfigFile); int blockSize = (int) (fileLength / mThreadNum); int[] recordL = new int[mThreadNum]; for (int i = 0; i < mThreadNum; i++) { recordL[i] = -1; } int rl = 0; for (int i = 0; i < mThreadNum; i++) { long startL = i * blockSize, endL = (i + 1) * blockSize; Object state = pro.getProperty(mTempFile.getName() + "_state_" + i); if (state != null && Integer.parseInt(state + "") == 1) { //該線程已經完成 if (resumeRecordLocation(i, startL, endL)) return; continue; } //分配下載位置 Object record = pro.getProperty(fileName + "_record_" + i); //如果有記錄,則恢復下載 if (record != null && Long.parseLong(record + "") >= 0) { Long r = Long.parseLong(record + ""); mConstance.CURRENT_LOCATION += r - startL; Log.d(TAG, "任務【" + mEntity.getFileName() + "】線程__" + i + "__恢復下載"); startL = r; recordL[rl] = i; rl++; } else { recordL[rl] = i; rl++; } //最後一個線程的結束位置即為文件的總長度 if (i == (mThreadNum - 1)) endL = fileLength; //創建分段線程 AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength); if (task == null) return; mTask.put(i, task); } startSingleTask(recordL);
在上面的代碼中,主要做了兩步操作:
FTP 分段線程區間自動停止
由於FTP協議沒有區間下載的原因,為了讓線程只下載特定區間的內容,需要客戶端在單條線程累計讀的數據長度已經超過了所分配的區間長度的時候,停止該條線程。
client.enterLocalPassiveMode(); //設置被動模式 client.setFileType(FTP.BINARY_FILE_TYPE); //設置文件傳輸模式 client.setRestartOffset(mConfig.START_LOCATION); //設置恢復下載的位置 client.allocate(mBufSize); is = client.retrieveFileStream(new String(remotePath.getBytes(charSet), SERVER_CHARSET)); //發送第二次指令時,還需要再做一次判斷 reply = client.getReplyCode(); if (!FTPReply.isPositivePreliminary(reply)) { client.disconnect(); fail(mChildCurrentLocation, "獲取文件信息錯誤,錯誤碼為:" + reply, null); return; } file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", mBufSize); file.seek(mConfig.START_LOCATION); byte[] buffer = new byte[mBufSize]; int len; while ((len = is.read(buffer)) != -1) { //如果該條線程讀取的數據長度大於所分配的區間長度,則只能讀到區間的最大長度 if (mChildCurrentLocation + len >= mConfig.END_LOCATION) { len = (int) (mConfig.END_LOCATION - mChildCurrentLocation); file.write(buffer, 0, len); progress(len); break; } else { file.write(buffer, 0, len); progress(len); } }
這裡還有幾個坑需要處理一下:
關於FTP文件上傳
FTP 文件斷點續傳的方式原理和下載的都差不多:
而和下載有區別的是:
最終效果
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
出於性能優化考慮,android的UI操作並不是線程安全的,這意味著意味著如果有多個線程並發操作UI組件,可能導致線程安全問題,未解決此問題, 從開發的角度來說, Han
仿微信相冊選擇圖片,查看大圖,寫的不太好,希望評論指出不足,諒解,先介紹一下我的基本思路第一步獲取手機上的所有圖片路徑: Uri
android的gradle插件用了不少了,比如說官方的應用構建插件(com.android.application),lib構建插件(com.android.libra
概述:此部分內容涉及到android的自定義View、自定義屬性和Android圖形圖像處理的綜合應用:Bitmap、Path、Matrix、Canvas。圖片打碼以及如