Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例
最近在給我的開源下載框架Aria增加FTP斷點(diǎn)續(xù)傳下載和上傳功能,在此過(guò)程中,爬了FTP的不少坑,終于將功能實(shí)現(xiàn)了,在此把一些核心功能點(diǎn)記錄下載。
FTP下載原理
FTP單線程斷點(diǎn)續(xù)傳
FTP和傳統(tǒng)的HTTP協(xié)議有所不同,由于FTP沒(méi)有所謂的頭文件,因此我們不能像HTTP那樣通過(guò)設(shè)置header向服務(wù)器指定下載區(qū)間。
但是FTP協(xié)議提供了一個(gè)更好用的命令REST用于從指定位置恢復(fù)任務(wù),同時(shí)FTP協(xié)議也提供了一個(gè)命令SIZE用于獲取下載的文件大小,有了這兩個(gè)命令,F(xiàn)TP斷點(diǎn)續(xù)傳也就沒(méi)有什么問(wèn)題。
FTP斷點(diǎn)續(xù)傳的原理和HTTP的斷點(diǎn)續(xù)傳原理差不多,在暫停時(shí)記錄文件的停止位置,再次下載時(shí),先讀取記錄的位置,如果位置存在,則通過(guò)REST命令告訴服務(wù)器從指定區(qū)間進(jìn)行下載。
FTP多線程斷點(diǎn)續(xù)傳
多線程下載的原理和HTTP多線程下載的原理差不多。先獲取文件大小,然后根據(jù)線程數(shù),對(duì)整個(gè)文件進(jìn)行分段下載,在任務(wù)停止時(shí),記錄每一條線程的暫停位置,重新開始下載,每一條線程讀取對(duì)應(yīng)的下載記錄,然后每一線程從指定位置開始下載。
分段下載
和HTTP所不同的是,F(xiàn)TP并沒(méi)有提供文件區(qū)間的API,因此,F(xiàn)TP在分段下載中,只有起始位置而沒(méi)有結(jié)束位置。
因此,你需要在指定位置手動(dòng)停止線程。
功能實(shí)現(xiàn)
本文使用將采用apache commons-net實(shí)現(xiàn)FTP斷點(diǎn)續(xù)傳下載\上傳功能。<br>
通過(guò)下文的幾步操作,你就能很簡(jiǎn)單的實(shí)現(xiàn)FTP斷點(diǎn)續(xù)傳。
登錄
FTP協(xié)議和HTTP協(xié)議有所不同,使用FTP進(jìn)行下載時(shí),你需要進(jìn)行登錄操作。
當(dāng)然,如果你服務(wù)器沒(méi)有登錄功能,你可以忽略登錄操作。
FTPClient client = new FTPClient(); client.connect(serverIp, port); //連接到FTP服務(wù)器 client.login(userName, passsword);
通過(guò)上面三行代碼,就可以很簡(jiǎn)單的登錄到FTP服務(wù)器上。
在進(jìn)行登錄后,還需要驗(yàn)證是否登錄成功
int reply = client.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { client.disconnect(); Log.d(TAG, "無(wú)法連接到ftp服務(wù)器,錯(cuò)誤碼為:" + reply); return; }
由于FTP協(xié)議中,連接成功的狀態(tài)有多個(gè),因此需要通過(guò)FTPReply.isPositiveCompletion(reply)
用于驗(yàn)證是否成功連接到FTP服務(wù)器。
文件信息獲取
在連接到FTP服務(wù)器后,就需要開始獲取下載最重要的幾個(gè)參數(shù)(文件長(zhǎng)度、文件名)。
客戶端可以通過(guò)client.listFiles(remotePath)
獲取FTP服務(wù)器上該路徑的文件列表。
- 如果路徑是文件,只會(huì)返回一個(gè)長(zhǎng)度為1的數(shù)組。
- 如果該路徑為文件夾,則會(huì)返回該文件夾下對(duì)應(yīng)的所有文件。
String remotePath = "/upload/qjnn.apk"; //FTP服務(wù)器上文件路徑 FTPFile[] files = client.listFiles(remotePath); FTPFile file = files[0]; //文件信息 long size = file.getSize(); String fileaName = file.getName();
如果你的文件為英文名,并且路徑中沒(méi)有中文,那么通過(guò)上述代碼,便可以獲取到正確的文件信息。
但如果FTP上的服務(wù)器上的文件名有中文或路徑有中文,那么上述代碼,你將獲取不到正確的文件信息。
正確的寫法
由于FTP服務(wù)器默認(rèn)的編碼是ISO-8859-1,因此,客戶端在獲取文件信息時(shí)
- 需要請(qǐng)求服務(wù)器使用UTF-8編碼(如果服務(wù)器支持的話),如果服務(wù)器不支持開啟UTF-8編碼,那么客戶端需要指定字符串編碼格式
- 客戶端在請(qǐng)求remotePath路徑、獲取文件名時(shí),都需要對(duì)路徑進(jìn)行編碼轉(zhuǎn)換處理。
String remotePath = "/upload/qjnn.apk"; //FTP服務(wù)器上文件路徑 String charSet = "UTF-8"; if (!FTPReply.isPositiveCompletion(client.sendCommand("OPTS UTF8", "ON"))) { //向服務(wù)器請(qǐng)求使用"UTF-8"編碼 charSet = "GBK"; } FTPFile[] files = client.listFiles(new String(remotePath.getBytes(charSet), "ISO-8859-1")); //對(duì)remotePath進(jìn)行編碼轉(zhuǎn)換 FTPFile file = files[0]; //文件信息 long size = file.getSize(); String fileaName = new String(fileName.getBytes(), Charset.forName(charSet));
通過(guò)以上代碼,便可以獲取到正確的文件信息。
文件下載
配置每條線程的下載區(qū)間
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) { //該線程已經(jīng)完成 if (resumeRecordLocation(i, startL, endL)) return; continue; } //分配下載位置 Object record = pro.getProperty(fileName + "_record_" + i); //如果有記錄,則恢復(fù)下載 if (record != null && Long.parseLong(record + "") >= 0) { Long r = Long.parseLong(record + ""); mConstance.CURRENT_LOCATION += r - startL; Log.d(TAG, "任務(wù)【" + mEntity.getFileName() + "】線程__" + i + "__恢復(fù)下載"); startL = r; recordL[rl] = i; rl++; } else { recordL[rl] = i; rl++; } //最后一個(gè)線程的結(jié)束位置即為文件的總長(zhǎng)度 if (i == (mThreadNum - 1)) endL = fileLength; //創(chuàng)建分段線程 AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength); if (task == null) return; mTask.put(i, task); } startSingleTask(recordL);
在上面的代碼中,主要做了兩步操作:
- 在文件下載前,先從本地文件中讀取當(dāng)前下載的每一條線程的下載情況
- 如果下載記錄存在,從記錄位置開始下載,如果記錄不存在,則重新開始下載
FTP 分段線程區(qū)間自動(dòng)停止
由于FTP協(xié)議沒(méi)有區(qū)間下載的原因,為了讓線程只下載特定區(qū)間的內(nèi)容,需要客戶端在單條線程累計(jì)讀的數(shù)據(jù)長(zhǎng)度已經(jīng)超過(guò)了所分配的區(qū)間長(zhǎng)度的時(shí)候,停止該條線程。
client.enterLocalPassiveMode(); //設(shè)置被動(dòng)模式 client.setFileType(FTP.BINARY_FILE_TYPE); //設(shè)置文件傳輸模式 client.setRestartOffset(mConfig.START_LOCATION); //設(shè)置恢復(fù)下載的位置 client.allocate(mBufSize); is = client.retrieveFileStream(new String(remotePath.getBytes(charSet), SERVER_CHARSET)); //發(fā)送第二次指令時(shí),還需要再做一次判斷 reply = client.getReplyCode(); if (!FTPReply.isPositivePreliminary(reply)) { client.disconnect(); fail(mChildCurrentLocation, "獲取文件信息錯(cuò)誤,錯(cuò)誤碼為:" + 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) { //如果該條線程讀取的數(shù)據(jù)長(zhǎng)度大于所分配的區(qū)間長(zhǎng)度,則只能讀到區(qū)間的最大長(zhǎng)度 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); } }
這里還有幾個(gè)坑需要處理一下:
- 對(duì)于FTP客戶端來(lái)說(shuō),一般需要設(shè)置被動(dòng)模式,被動(dòng)模式和主動(dòng)模式的區(qū)別
- 在獲取文件流后,還需要使用FTPReply.isPositivePreliminary(reply)進(jìn)行第二次命令判斷
關(guān)于FTP文件上傳
FTP 文件斷點(diǎn)續(xù)傳的方式原理和下載的都差不多:
- 都是在停止的時(shí)候記錄停止位置,重新開始下載的時(shí)候從指定位置通過(guò)REST命令恢復(fù)斷點(diǎn)。
- 都需要在任務(wù)執(zhí)行前獲取文件信息,比對(duì)服務(wù)器上的文件。
而和下載有區(qū)別的是:
- FTP上傳時(shí)需要指定工作目錄、在遠(yuǎn)程服務(wù)器上創(chuàng)建文件夾
- 需要服務(wù)器給用戶打開刪除和讀入IO的權(quán)限,否則會(huì)出現(xiàn)550權(quán)限錯(cuò)誤問(wèn)題
- 上傳文件需要storeFileStream獲取outputStream流
最終效果
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android實(shí)現(xiàn)多線程下載文件(支持暫停、取消、斷點(diǎn)續(xù)傳)
- Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能
- Android多線程斷點(diǎn)續(xù)傳下載功能實(shí)現(xiàn)代碼
- Android多線程斷點(diǎn)續(xù)傳下載示例詳解
- Android 使用AsyncTask實(shí)現(xiàn)多任務(wù)多線程斷點(diǎn)續(xù)傳下載
- Android實(shí)現(xiàn)網(wǎng)絡(luò)多線程斷點(diǎn)續(xù)傳下載實(shí)例
- Android編程開發(fā)實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳下載器實(shí)例
- PC版與Android手機(jī)版帶斷點(diǎn)續(xù)傳的多線程下載
- Android 使用AsyncTask實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳
- android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能
相關(guān)文章
Android使用Handler實(shí)現(xiàn)打地鼠游戲
這篇文章主要為大家詳細(xì)介紹了Android使用Handler實(shí)現(xiàn)打地鼠游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06Android layoutAnimation詳解及應(yīng)用
這篇文章主要介紹了Android layoutAnimation詳解及應(yīng)用的相關(guān)資料,需要的朋友可以參考下2017-05-05android studio 一直卡在Gradle:Build Running的幾種解決辦法
這篇文章主要介紹了android studio 一直卡在Gradle:Build Running的解決辦法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10Android App多個(gè)入口的實(shí)現(xiàn)方法
這篇文章主要介紹了Android App多個(gè)入口的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Android仿QQ未讀消息--紅點(diǎn)拖拽刪除【源代碼】
本文Demo是一款仿qq未讀消息拖拽刪除的例子,繼承RelativeLayout的WaterDrop實(shí)現(xiàn)了圓形圖標(biāo)功能;繼承ImageView的CircleImageView圓形圖片功能。效果非常不錯(cuò),很適合有圓形設(shè)計(jì)的朋友參考2017-04-04android輕量級(jí)無(wú)侵入式管理數(shù)據(jù)庫(kù)自動(dòng)升級(jí)組件
這篇文章主要為大家介紹了android輕量級(jí)無(wú)侵入式管理數(shù)據(jù)庫(kù)自動(dòng)升級(jí)組件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Android App中自定義View視圖的實(shí)例教程
這篇文章主要介紹了Android App中自定義View視圖的實(shí)例教程,詳細(xì)講解了如何在創(chuàng)建View中定義各種鎖需要的樣式屬性,需要的朋友可以參考下2016-04-04Android手機(jī)內(nèi)存中文件的讀寫方法小結(jié)
這篇文章主要介紹了Android手機(jī)內(nèi)存中文件的讀寫方法,實(shí)例總結(jié)了Android針對(duì)文件讀寫操作的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04