美文网首页
Alamofire源码分析(10)——后台下载、断点续传

Alamofire源码分析(10)——后台下载、断点续传

作者: 无悔zero | 来源:发表于2020-12-27 07:47 被阅读0次

    提到下载就会想到软件下载、游戏下载,这些都是大文件下载,下载时间长,如果这时候进入后台或app推出了怎么办?今天来说说下载那些事。

    (一)后台下载

    既然做下载功能,那就直接做成后台下载吧:

    DowloadManager.shared.sessionManager
        .download(url) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
            let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
            let fileUrl = documentUrl?.appendingPathComponent(response.suggestedFilename!)
            return (fileUrl!, [.removePreviousFile, .createIntermediateDirectories])//存储路径, [删除以前的文件, 创建中间目录]
        }
        .response { (response) in
            print("完成")
        }
        .downloadProgress { (progress) in
            print("下载进度")
        }
    
    class DowloadManager: NSObject {
    
        static let shared = DowloadManager()
        
        let sessionManager: SessionManager = {
            let configuration = URLSessionConfiguration.background(withIdentifier: "fireDowload")
            configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
            configuration.sharedContainerIdentifier = "group.fireDowload"//需要用到后台Session,则必需给此属性设置一个有效的ID值
            return SessionManager(configuration: configuration)
        }()
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
            //后台下载完成回调通知系统
            DowloadManager.shared.sessionManager.backgroundCompletionHandler = completionHandler
        }
    
    • 后台下载注意点:
    1. AlamofireSessionManager需要做成单例,否则报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled";系统的 API 就不用单例
    2. 记得AppDelegatecompletionHandler的调用
    • 源码分析
    completionHandler闭包在下载完成时会被调用
    (二)断点续传

    下载文件往往比较大,时间长了,会遇见很多种情况,比如进入后台、暂停后重启app再下载、强杀app、app闪退:

    DowloadManager.shared.download(url) 
    
    class DowloadManager: NSObject {
    
        static let shared = DowloadManager()
        private var request: DownloadRequest?
        private var resumeData: Data?
        private var documentUrl: URL {
            return FileManager.default.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first!
        }
        //初始化
        let sessionManager: SessionManager = {
            /* 1. 为了避免跟其他App冲突,建议这个identifier 跟应用程序的 Bundle ID相关,保证唯一;
                2. URLSession必须传入delegate;
                3. 最好在App启动的时候创建 */
            let configuration = URLSessionConfiguration.background(withIdentifier: "com.fireDowload")
            configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
            configuration.sharedContainerIdentifier = "group.fireDowload"//需要用到后台Session,则必需给此属性设置一个有效的ID值
            let manager = SessionManager(configuration: configuration)
            
            //因为session使用同一个Identifier,所以:
            /* 一. 下载时强杀app,重启会来到taskDidComplete */
            manager.delegate.taskDidComplete = { (seesion,task, error) in
                if let error = error, let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
                    /* tmp是临时文件,包含resumeData和其他数据;
                        断点续传就是拿到tmp的resumeData便可继续下载;
                        所以需要保存resumeData,不然下次正常重启app没有resumeData */
                    DowloadManager.shared.resumeData = resumeData
                    DowloadManager.shared.saveResumeData(resumeData)
                }
            }
            /* 二. 下载时app闪退,系统会在后台继续下载到完成,
                    完成后再重启app会来到downloadTaskDidFinishDownloadingToURL */
            manager.delegate.downloadTaskDidFinishDownloadingToURL = { (session, downloadTask, url) in
                //移动下载好的文件
                DowloadManager.shared.moveFile(downloadTask: downloadTask, url: url)
            }
            /* 三. app闪退后,在系统后台进程还没下载完成时,
                    重启app会来到downloadTaskDidWriteData,
                    此时可以拿到task和进度 */
            manager.delegate.downloadTaskDidWriteData = { (session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in
                print(totalBytesWritten/totalBytesExpectedToWrite)//进度
            }
            return manager
        }()
        //下载
        func download(url: String) ->DownloadRequest {
            if let resumeData = getLastResumeData() {
                self.request = sessionManager.download(resumingWith: resumeData)
            }else {
                self.request = sessionManager.download(url) { [weak self](url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
                    let fileUrl = self?.documentUrl.appendingPathComponent(response.suggestedFilename!)
                    return (fileUrl!, [.removePreviousFile, .createIntermediateDirectories])//存储路径, [删除以前的文件, 创建中间目录]
                    }
            }
            return self.request!.response { (response) in
                print("完成")
            }.downloadProgress { (progress) in
                print("下载进度")
            }
        }
        //获取data
        func getLastResumeData() -> Data? {
            if self.resumeData == nil  {
                let fileUrl = DowloadManager.shared.documentUrl.appendingPathComponent("resumeData.tmp")
                self.resumeData = try? Data.init(contentsOf: fileUrl)
            }
            return self.resumeData
        }
        //保存data
        func saveResumeData(_: Data?) {
            let fileUrl = DowloadManager.shared.documentUrl.appendingPathComponent("resumeData.tmp")
            try? resumeData?.write(to: fileUrl)
        }
        //移动文件
        func moveFile(downloadTask: URLSessionDownloadTask, url: URL) {
            guard let response = downloadTask.response as? HTTPURLResponse else { return }
            let fileUrl = DowloadManager.shared.documentUrl.appendingPathComponent(response.suggestedFilename!)
            do {
                if FileManager.default.fileExists(atPath: fileUrl.path) {
                    try FileManager.default.removeItem(at: fileUrl)
                }
                let directory = fileUrl.deletingLastPathComponent()
                try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
                //移动下载好的文件
                try FileManager.default.moveItem(at: url, to: fileUrl)
            } catch {
                print(error)
            }
        }
        //暂停
        func suspend() {
            self.request?.suspend()
            //保存
            saveResumeData(self.request?.resumeData)
        }
        //启动
        func resume() {
            self.request?.resume()
        }
        //取消
        func cancel() {
            self.request?.cancel()
        }
    }
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
        
        func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
            //后台下载完成回调通知系统
            DowloadManager.shared.sessionManager.backgroundCompletionHandler = completionHandler
        }
    
    • 源码分析
    下载时强杀app,重启会来到这 下载时app闪退,系统后台下载完成后,重启会来到这 下载时app闪退,系统后台还在下载时,重启会来到这

    相关文章

      网友评论

          本文标题:Alamofire源码分析(10)——后台下载、断点续传

          本文链接:https://www.haomeiwen.com/subject/jvywnktx.html