美文网首页
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