Swift - 实现大文件的后台下载功能(附样例)
作者:hangge | 2018-11-07 08:10
这样对于一些大文件的下载并不友好,因为用户不可能一直开着 App 等待下载完毕。下面演示如何在程序退到后台时,下载任务仍然会继续进行。
1,实现原理
(1)URLSessionConfiguration 有如下三种模式:
- default:默认会话模式(使用的是基于磁盘缓存的持久化策略)
- ephemeral:暂时会话模式(该模式不使用磁盘保存任何数据。而是保存在 RAM 中,因此当程序使会话无效,这些缓存的数据就会被自动清空。)
- background:后台会话模式(该模式可以在后台完成上传和下载)
(2)之前我们使用的都是 default 模式,要实现后台下载就必须使用 background 模式:
- 当 app 被终止时,系统会接管下载任务。
- 等到下载完成或需要 app 关注时,系统又会在后台唤醒 app(注意是后台唤醒,该 app 不会切换到前台显示)。
- 然后 app 再对下载文件进行后续处理。
特别注意:如果应用被强制关闭(双击 home 调出后台列表,并将其上滑关闭),那么后台下载就不再起作用了。
2,准备工作
(1)为方便使用,首先我们封装一个后台下载的工具类(DownloadManager.swift),具体内容如下:
- 创建一个 background session 用于后台下载(需指定一个 identifier)
- 实现 URLSessionDelegate、URLSessionDownloadDelegate 相关的代理协议方法(比如下载进度反馈,文件下载完成后将其移至用户文档目录保存)
import UIKit
class DownloadManager: NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
//单例模式
static var shared = DownloadManager()
//下载进度回调
var onProgress: ((Float) -> ())?
//background session
lazy var session:URLSession = {
//只执行一次
let config = URLSessionConfiguration.background(withIdentifier:
"background-session")
let currentSession = URLSession(configuration: config, delegate: self,
delegateQueue: nil)
return currentSession
}()
//下载代理方法,下载结束
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
//下载结束
print("下载结束")
if let onProgress = onProgress {
onProgress(1)
}
//输出下载文件原来的存放目录
print("临时地址:\(location)")
//location位置转换
let locationPath = location.path
//拷贝到用户目录(文件名以时间戳命名)
let fileName = date2String(Date(), dateFormat: "yyyyMMddHHmmss")
let documnets = NSHomeDirectory() + "/Documents/" + fileName + ".tmp"
//创建文件管理器
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
print("文件保存到:\(documnets)")
}
//下载代理方法,监听下载进度
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
//获取进度
let written = (Float)(totalBytesWritten)
let total = (Float)(totalBytesExpectedToWrite)
let pro = written/total
if let onProgress = onProgress {
onProgress(pro)
}
}
//下载代理方法,下载偏移
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
//下载偏移,主要用于暂停续传
}
//session完成事件
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
//主线程调用
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
//调用此方法告诉操作系统,现在可以安全的重新suspend你的app
completionHandler()
}
}
}
//日期 -> 字符串
func date2String(_ date:Date, dateFormat:String = "yyyy-MM-dd HH:mm:ss") -> String {
let formatter = DateFormatter()
formatter.locale = Locale.init(identifier: "zh_CN")
formatter.dateFormat = dateFormat
let date = formatter.string(from: date)
return date
}
}
(2)同时 AppDelegate.swift 中要做如下修改:
- 后台下载完毕后会调用 handleEventsForBackgroundURLSession 方法。
- 我们在此用提供的 identifier 创建新的 URLSessionConfiguration 和 URLSession 对象。
- 然后将新的 session 对象重新连接到先前的任务,并调用相应的 delegate。
注意:在前面 DownloadManager 里的 urlSessionDidFinishEvents() 这个 session 代理方法中,我们需要要在主线程里调用 AppDelegate 里保存的 completionHandler。这样就会告诉操作系统,现在可以安全的重新 suspend 我们的 app 了。
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
//用于保存后台下载的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?
var window: UIWindow?
//后台下载完毕后会调用(我们将其交由下载工具类做后续处理)
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
//用于保存后台下载的completionHandler
backgroundSessionCompletionHandler = completionHandler
//创建download session
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
let downloadssession = URLSession(configuration: configuration,
delegate: DownloadManager.shared,
delegateQueue: nil)
//指定download session
DownloadManager.shared.session = downloadssession
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
}
3,使用样例
(1)样例代码
import UIKit
class ViewController: UIViewController {
//点击按钮,开始下载
@IBAction func startDownload(_ sender: Any) {
//下载地址
let url = URL(string: "https://dldir1.qq.com/dlomg/qqcom/mini/QQNewsMini5.exe")
//请求
let request = URLRequest(url: url!)
//下载任务
let downloadTask = DownloadManager.shared.session.downloadTask(with: request)
//使用resume方法启动任务
downloadTask.resume()
//实时打印出下载进度
DownloadManager.shared.onProgress = { (progress) in
OperationQueue.main.addOperation {
print("下载进度:\(progress)")
}
}
}
}
(2)运行效果:
- 点击界面上的按钮开始下载,可以看到控制台中不断地打印出下载进度。

- 按下 home 键将应用退到后台。虽然控制台不再继续打印下载进度,但事实上后台仍在继续下载,并在下载完毕后自动调用相关方法。
全部评论(0)