zoukankan      html  css  js  c++  java
  • iOS开发网络请求——大文件的多线程断点下载

      iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了。网络文件传输对移动客户端而言主要分为文件的上传和下载。作为开发者从技术角度会将文件分为小文件和大文件。小文件因为文件大小比较小导致传输所需时间少传输就快,因此不太容易影响用户体验,可用的技术就多。而大文件因为文件大小比较大导致传输时间长,因此就需要考虑到各种用户体验,比如避免在上传下载文件过程中阻塞主线程影响用户体验,就需要使用到多线程技术;为了给用户友好的进度提示,因此又需要开发中跟踪数据上传和下载数据的变化;为了提高用户体验,也需要考虑到断点续传的功能实现;而且大文件传输容易导致数据保持在内存中,又需要开发者处理内存中的数据;而为了处理多个文件或者压缩传输文件的数据大小,我们开发者还需要用到压缩和解压缩技术。根据不同的需求对大文件传输会有需要用到不同的解决方案,不过多线程断点续传是一个理想的在网络中传输大文件的方案。

    小文件下载

      小文件的下载方式比较多,下面列出常用的下载方式:

        1.直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url方法;
        2.利用NSURLConnection发送一个HTTP请求去下载
        3.利用NSURLSession发送一个HTTP请求去下载
        4.利用AFNetworking发送一个HTTP请求去下载
        4.如果是下载图片,还可以利用SDWebImage框架
    大文件下载最佳解决方案———多线程断点下载
      实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。
      
      项目中用到的主要类如下:
    完成的实现代码如下:
      
    YYViewController.m
    #import "YYViewController.h"
    #import "YYFileMultiDownloader.h"
    
    @interface YYViewController ()
    @property (nonatomic, strong) YYFileMultiDownloader *fileMultiDownloader;
    @end
    
    @implementation YYViewController
    -  (YYFileMultiDownloader *)fileMultiDownloader
    {
        if (!_fileMultiDownloader) {
            _fileMultiDownloader = [[YYFileMultiDownloader alloc] init];
            // 需要下载的文件远程URL
            _fileMultiDownloader.url = @"http://192.168.1.200:8080/MJServer/resources/jre.zip";
            // 文件保存到什么地方
            NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
            NSString *filepath = [caches stringByAppendingPathComponent:@"jre.zip"];
            _fileMultiDownloader.destPath = filepath;
        }
        return _fileMultiDownloader;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        [self.fileMultiDownloader start];
    }
    
    @end

    自定义一个基类

    YYFileDownloader.h文件

    #import <Foundation/Foundation.h>
    
    @interface YYFileDownloader : NSObject
    {
        BOOL _downloading;
    }
    /**
     * 所需要下载文件的远程URL(连接服务器的路径)
     */
    @property (nonatomic, copy) NSString *url;
    /**
     * 文件的存储路径(文件下载到什么地方)
     */
    @property (nonatomic, copy) NSString *destPath;
    
    /**
     * 是否正在下载(有没有在下载, 只有下载器内部才知道)
     */
    @property (nonatomic, readonly, getter = isDownloading) BOOL downloading;
    
    /**
     * 用来监听下载进度
     */
    @property (nonatomic, copy) void (^progressHandler)(double progress);
    
    /**
     * 开始(恢复)下载
     */
    - (void)start;
    
    /**
     * 暂停下载
     */
    - (void)pause;
    @end

      YYFileDownloader.m文件

    #import "YYFileDownloader.h"
     
    @implementation YYFileDownloader
    @end

    下载器类继承自YYFileDownloader这个类

    YYFileSingDownloader.h文件

    #import "YYFileDownloader.h"
    
    @interface YYFileSingleDownloader : YYFileDownloader
    /**
     *  开始的位置
     */
    @property (nonatomic, assign) long long begin;
    /**
     *  结束的位置
     */
    @property (nonatomic, assign) long long end; 
    @end

    YYFileSingDownloader.m文件

    #import "YYFileSingleDownloader.h"
    @interface YYFileSingleDownloader() <NSURLConnectionDataDelegate>
    /**
     * 连接对象
     */
    @property (nonatomic, strong) NSURLConnection *conn;
    
    /**
     *  写数据的文件句柄
     */
    @property (nonatomic, strong) NSFileHandle *writeHandle;
    /**
     *  当前已下载数据的长度
     */
    @property (nonatomic, assign) long long currentLength;
    @end
    
    @implementation YYFileSingleDownloader
    
    - (NSFileHandle *)writeHandle
    {
        if (!_writeHandle) {
            _writeHandle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
        }
        return _writeHandle;
    }
    
    /**
     * 开始(恢复)下载
     */
    - (void)start
    {
        NSURL *url = [NSURL URLWithString:self.url];
        // 默认就是GET请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        // 设置请求头信息
        NSString *value = [NSString stringWithFormat:@"bytes=%lld-%lld", self.begin + self.currentLength, self.end];
        [request setValue:value forHTTPHeaderField:@"Range"];
        self.conn = [NSURLConnection connectionWithRequest:request delegate:self];
        
        _downloading = YES;
    }
    
    /**
     * 暂停下载
     */
    - (void)pause
    {
        [self.conn cancel];
        self.conn = nil;
        
        _downloading = NO;
    }
    
    
    #pragma mark - NSURLConnectionDataDelegate 代理方法
    /**
     *  1. 当接受到服务器的响应(连通了服务器)就会调用
     */
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        
    }
    
    /**
     *  2. 当接受到服务器的数据就会调用(可能会被调用多次, 每次调用只会传递部分数据)
     */
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        // 移动到文件的尾部
        [self.writeHandle seekToFileOffset:self.begin + self.currentLength];
        // 从当前移动的位置(文件尾部)开始写入数据
        [self.writeHandle writeData:data];
        
        // 累加长度
        self.currentLength += data.length;
        
        // 打印下载进度
        double progress = (double)self.currentLength / (self.end - self.begin);
        if (self.progressHandler) {
            self.progressHandler(progress);
        }
    }
    
    /**
     *  3. 当服务器的数据接受完毕后就会调用
     */
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        // 清空属性值
        self.currentLength = 0;
        
        // 关闭连接(不再输入数据到文件中)
        [self.writeHandle closeFile];
        self.writeHandle = nil;
    }
    
    /**
     *  请求错误(失败)的时候调用(请求超时断网没有网, 一般指客户端错误)
     */
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
        
    }
    
    @end

    设计多线程下载器(利用HMFileMultiDownloader能开启多个线程同时下载一个文件)

    一个多线程下载器只下载一个文件

    YYFileMultiDownloader.h文件

    #import "YYFileDownloader.h"
     
    @interface YYFileMultiDownloader : YYFileDownloader
     
    @end

    YYFileMultiDownloader.m文件

    #import "YYFileMultiDownloader.h"
    #import "YYFileSingleDownloader.h"
    
    #define YYMaxDownloadCount 4
    
    @interface YYFileMultiDownloader()
    @property (nonatomic, strong) NSMutableArray *singleDownloaders;
    @property (nonatomic, assign) long long totalLength;
    @end
    
    @implementation YYFileMultiDownloader
    
    - (void)getFilesize
    {
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
        request.HTTPMethod = @"HEAD";
        
        NSURLResponse *response = nil;
    #warning 这里要用异步请求
        [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
        self.totalLength = response.expectedContentLength;
    }
    
    - (NSMutableArray *)singleDownloaders
    {
        if (!_singleDownloaders) {
            _singleDownloaders = [NSMutableArray array];
            
            // 获得文件大小
            [self getFilesize];
            
            // 每条路径的下载量
            long long size = 0;
            if (self.totalLength % YYMaxDownloadCount == 0) {
                size = self.totalLength / YYMaxDownloadCount;
            } else {
                size = self.totalLength / YYMaxDownloadCount + 1;
            }
            
            // 创建N个下载器
            for (int i = 0; i<YYMaxDownloadCount; i++) {
                YYFileSingleDownloader *singleDownloader = [[YYFileSingleDownloader alloc] init];
                singleDownloader.url = self.url;
                singleDownloader.destPath = self.destPath;
                singleDownloader.begin = i * size;
                singleDownloader.end = singleDownloader.begin + size - 1;
                singleDownloader.progressHandler = ^(double progress){
                    NSLog(@"%d --- %f", i, progress);
                };
                [_singleDownloaders addObject:singleDownloader];
            }
            
            // 创建一个跟服务器文件等大小的临时文件
            [[NSFileManager defaultManager] createFileAtPath:self.destPath contents:nil attributes:nil];
            
            // 让self.destPath文件的长度是self.totalLengt
            NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
            [handle truncateFileAtOffset:self.totalLength];
        }
        return _singleDownloaders;
    }
    
    /**
     * 开始(恢复)下载
     */
    - (void)start
    {
        [self.singleDownloaders makeObjectsPerformSelector:@selector(start)];
        
        _downloading = YES;
    }
    
    /**
     * 暂停下载
     */
    - (void)pause
    {
        [self.singleDownloaders makeObjectsPerformSelector:@selector(pause)];
        _downloading = NO;
    }
    
    @end

    补充说明:如何获得将要下载的文件的大小?

    参考文章:http://www.cnblogs.com/wendingding/p/3947550.html

  • 相关阅读:
    var type = $('#<%=DropDownListRateType.ClientID %>').val();DropDownListRateType.ClientID是什么意思
    通过代码理解Asp.net4中的几种ClientIDMode设置.
    left join right join inner join 详解
    T-SQL查询处理执行顺序(一)
    Tsql查询执行顺序(二)
    【转】"超时时间已到。在操作完成之前超时时间已过或服务器未响应"的解决方法
    【转】ibatis 中使用select top #pagesize# * from tablename
    最近找到的一些分页优化的方法
    看懂SqlServer查询计划
    java中的数据结构
  • 原文地址:https://www.cnblogs.com/JackieHoo/p/5062360.html
Copyright © 2011-2022 走看看