天天看點

大檔案分片上傳和斷點續傳

 轉自:http://blog.csdn.net/nndasdfg/article/details/51436731

總結一下大檔案分片上傳和斷點續傳的問題。因為檔案過大(比如1G以上),必須要考慮上傳過程網絡中斷的情況。http的網絡請求中本身就已經具備了分片上傳功能,當傳輸的檔案比較大時,http協定自動會将檔案切片(分塊),但這不是我們現在說的重點,我們要做的事是保證在網絡中斷後1G的檔案已上傳的那部分在下次網絡連接配接時不必再重傳。是以我們本地在上傳的時候,要将大檔案進行分片,比如分成1024*1024B,即将大檔案分成1M的片進行上傳,伺服器在接收後,再将這些片合并成原始檔案,這就是分片的基本原理。斷點續傳要求本地要記錄每一片的上傳的狀态,我通過三個狀态進行了标記(wait  loading  finish),當網絡中斷,再次連接配接後,從斷點處進行上傳。伺服器通過檔案名、總片數判斷該檔案是否已全部上傳完成。

        下面來說細節:

  1、首先擷取檔案(音視訊、圖檔)

分兩種情況,一種是在相冊庫裡直接擷取,一種是調用相機。如果是通過UIImagePickerView來擷取(細節不詳述,網上一大堆),我們會發現當你標明一個視訊的時候,會出現圖1的壓縮頁面,最後我們的app擷取的視訊就是這個經過壓縮後的視訊(不是視訊庫裡的原始視訊,這裡有個注意點,操作完該壓縮視訊後記得釋放,系統不會幫你釋放的,需要你手動來操作,下面會說到),然後通過UIImagePickerView的協定方法中的- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info擷取視訊的Info

fileInfo = {

    UIImagePickerControllerMediaType = "public.movie";

    UIImagePickerControllerMediaURL = "file:///private/var/mobile/Containers/Data/Application/2AAE9E44-0E6D-4499-9AC3-93D44D8342EA/tmp/trim.F36EC46C-4219-43C8-96A7-FA7141AB64D2.MOV";

    UIImagePickerControllerReferenceURL = "assets-library://asset/asset.MOV?id=DEDA9406-3223-4F87-ABB2-98FB5F5EB9C4&ext=MOV";

}

UIImagePickerControllerMediaType是選取檔案的類型,如KUTTypeImage,KUTTypeMovie。這裡注意一下movie和video的差別,一個是有聲音的視訊檔案,一個是沒有聲音的視訊檔案,當然還有Audio是隻有聲音沒有視訊。UIImagePickerControllerMediaURL是視訊的URL(如果是相機拍攝的,那麼這個就是原始拍攝得到的視訊;如果是在相冊庫裡選擇的,那就是壓縮之後生成的視訊),注意這個URL不指向相冊庫,通過這個URL你可以操作這個視訊如删除,拷貝等,可以擷取壓縮後的視訊的大小。UIImagePickerControllerReferenceURL是一個指向相冊的URL,官方的解釋是an NSURL that references an asset in the AssetsLibrary framework,通過這個URL,你可以擷取視訊的所有資訊,包括檔案名,縮略圖,時長等(通過ALAssetsLibrary裡的assetsLibraryassetForURL:referenceURLresultBlock:)。

如果是相機拍攝的,注意兩個儲存方法:圖檔儲存到相冊assetsLibrarywriteImageDataToSavedPhotosAlbum:UIImageJPEGRepresentation([infovalueForKey:UIImagePickerControllerOriginalImage],(CGFloat)1.0)metadata:nilcompletionBlock: failureBlock:

高保真壓縮圖檔的方法NSData * UIImageJPEGRepresentation ( UIImage *image, CGFloat compressionQuality)

視訊儲存到相冊:assetsLibrary writeVideoAtPathToSavedPhotosAlbum:MediaURL completionBlock:failureBlock:

到這裡,我們就擷取了所有需要的檔案以及檔案資訊。下面要做的就是将檔案分片。

2、将擷取到的檔案分片

首先,我将擷取到的檔案儲存在這這樣一個類中

@interface CNFile : NSObject

@property (nonatomic,copy)NSString* fileType;//image  or  movie

@property (nonatomic,copy)NSString* filePath;//檔案在app中路徑

@property (nonatomic,copy)NSString* fileName;//檔案名

@property (nonatomic,assign)NSInteger fileSize;//檔案大小

@property (nonatomic,assign) NSInteger trunks;//總片數

@property (nonatomic,copy)NSString* fileInfo;

@property (nonatomic,strong)UIImage* fileImage;//檔案縮略圖

@property (nonatomic,strong) NSMutableArray* fileArr;//标記每片的上傳狀态

@end

這樣我們就可以對每一個CNFile對象進行操作了。

-(void)readDataWithChunk:(NSInteger)chunk file:(CNFile*)file{

  總片數的擷取方法:

    int offset =1024*1024;(每一片的大小是1M)

    NSInteger chunks = (file.fileSize%1024==0)?((int)(file.fileSize/1024*1024)):((int)(file.fileSize/(1024*1024) + 1));

    NSLog(@"chunks = %ld",(long)chunks);

    将檔案分片,讀取每一片的資料:

    NSData* data;

    NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingAtPath:file.filePath];

    [readHandle seekToFileOffset:offset * chunk];

    data = [readHandle readDataOfLength:offset];

}

這樣我們就擷取了每一片要上傳的資料,然後詢問伺服器,該片是否已經存在

(方法-(void)ifHaveData:(NSData*)data WithChunk:(NSInteger)chunk file:(CNFile*)file)

,如果存在,令chunk+1,重複上面的方法讀取下一片,直到伺服器不存在該片,那麼上傳該片資料。在這個方法中注意設定該chunk的上傳狀态(wait  loading finish),這将關系到本地判斷該檔案是否已全部上傳完成。

下一步就是上傳的過程:

-(void)uploadData:(NSData*) data WithChunk:(NSInteger) chunk file:(CNFile*)file;

在伺服器傳回該片上傳成功後,我們要做的事有很多:

1)先将已經成功上傳的本片的flag置finish

[file.fileArr replaceObjectAtIndex:chunk withObject:@“finish"];

2)檢視是否所有片的flag都已經置finish,如果都已經finishi,說明該檔案上傳完成,那麼删除該檔案,上傳下一個檔案或者結束。

for (NSInteger j =0; j<chunks; j++){

if (j == chunks || ((j == chunks -1)&&([file.fileArr[j]isEqualToString:@"finish"])))

     [me deleteFile:file.filePath];

     [me readNextFile];

}

3)如果沒有都finish,那麼看本地下一chunk對用的flag是否是wait

 NSLog(@"檢視第%ld片的狀态",chunk+1);

 for(NSInteger i = chunk+1;i < chunks;i++)

  {

     NSString* flag = [file.fileArrobjectAtIndex:i];

      if ([flagisEqualToString:@"wait"]) {

             [me readDataWithChunk:ifileName:fileNamefile:file];

               break;

          }

   }

在第2、3步之間可以有一個 2.5)判斷是否暫停上傳

if(me.isPause ==YES)

  {

  //将目前讀到了第幾個檔案的第幾片儲存到本地

     [self saveProgressWithChunk:chunk file:file];

      return ;

   }

這個操作實際上和上傳過程中斷網是一樣的,為了斷點續傳,在斷網或者暫停的時候,我們要将目前的進度儲存起來,以便下次上傳時略過前面已置finish的片。

然後還有一個問題,如果我們就這樣線性的一片一片上傳,實際上失去了分片上傳的意義,應該結合多線程,使分片上傳過程并發執行,同時上傳多片,這樣就提高了上傳效率,并充分利用了網絡帶寬。

    dispatch_async(dispatch_queue_t queue, ^{

        [me readDataWithChunk: chunk];

    })

最後注意一下,每上傳完一個視訊,去設定裡看看你的app占用的存儲空間有沒有增大哦,如果你沒有處理那個生成的壓縮視訊,你會發現你的app的空間占用量是很大的。

繼續閱讀