應用沉睡之時:後台傳輸服務
當用戶在下載文件的時候,強制讓用戶一直保持應用的開啟就如同當水壺在燒水的時候,強迫人一直看著水有沒有燒開。在本次演講中,Gwendolyn Weston 將會給大家介紹如何使用 iOS 的後台傳輸服務(Background Transfer Service) API 在後台下載文件,演講中還涉及到了實現過程中的常見問題以及使用案例。了解如何在您的應用中輕鬆地實現此功能,以減少用戶的時間,提高用戶的滿意度。:tea:
大家好,我是Gwendolyn Weston,是PlanGrid公司的一名開發者。今天在這裡我想跟大家談一談關於「後台傳輸服務」的有關知識,我們就只圍繞後台下載來進行介紹,教大家如何在應用中實現此項功能。首先,我想先討論一下如何在前台實現下載機制,因為可能有人對 不是很熟悉。然後,我將會談論如何讓這個下載功能請求與後台兼容。最後,我會演示如何避開該功能使用過程中常見的陷阱。
後台傳輸服務 — 我們用水壺來比喻
後天傳輸服務是 iOS 7 引進的 API,它准許應用暫停或者中止之後,在後台繼續執行網路服務(比如下載或者上傳)。舉個例子,這正是 Dropbox 為什麼能夠在後台執行同步文件到設備功能的原因。
為了解釋這個功能為什麼很有用,請試想一下我們有一個水壺。我們將水倒入,按下燒水鍵,這時候我們本應該離開去做別的事,然而不行,我們必須站在水壺旁邊,否則的話水壺就不能燒水了。這是一個非常奇怪的規則,我們不得不遵循它。我們只好站在水壺旁邊,拿出手機開始刷微博。當我們看到好友們三三兩兩的出去遊玩,而我們卻沒有辦法,為什麼呢?「我們必須站在水壺旁邊,否則的話水壺就不能燒水」。於是,這個水壺就不會有人用了,就像前台下載也不會有人喜歡一樣。
我所在的公司PlanGrid可以被形容為是一個「施工圖紙的 GitHub」,它為施工項目提供了版本控制以及項目管理的功能。
有一個很常見的用例:某個承包商登入了網站,標記了一個圖紙。這個標記會同步到其他承包商的設備中,以節省人們的時間、精力和金錢,因為人們無需再重新列印這個圖紙,每一步變化都能夠即時展現。
這就意味著我們的用戶經常會上傳高清的圖紙到我們的倉庫當中。每個新加入此項目中的人,通常都需要花費數個小時將所有的圖紙同步到設備當中。我們必須告訴用戶修改它們的設備設置,取消屏幕自動鎖定,將應用一直開著直到下載全部完成。這是不是非常讓人惱火?同樣這個操作也是一個巨大的安全隱患,因為你不知道一個未鎖定的設備在你離開期間會發生什麼事情,而這個設備此時往往包含了重要的圖紙信息。如果我們能夠告訴用戶:「點擊下載後,你就不用管了,都交給我們了!」,那一切就大有不同了。
沒有後台傳輸服務的下載就像一直盯著水壺燒水那樣無趣。而包含這項功能的話,你就可以不用盯著水壺燒水了,你只需設定好相關功能,然後做自己的事情就可以了。
下面是示例代碼:
let urlstring = "https://remoteteakettle.com/boiledwater.pdf" let filepath = "Documents/local_teakettle" if let url = NSURL(string: urlstring) { let task = NSURLSession.sharedSession().downloadTaskWithURL(url, completionHandler: { (location:NSURL?, response:NSURLResponse?, error:NSError?) in if let loc = location, path = loc.path { try! NSFileManager.defaultManager().moveItemAtPath(path, toPath:filepath) } }) task.resume() }
Get more development news like this
讓我們一行一行來看。第一行是我們遠程伺服器中存放的文件地址。我們前往 remoteteakettle.com 嘗試下載一個名為 的文件。第二行我們指定了我們想要存儲該文件的路徑。至於第三行,你會看到從 框架中引用了許許多多的成員對象,讓我們對其仔細解讀。
用於聲明和管理網路請求。 是基於某些默認設置的會話(session)單例。比如說,它使用默認的緩存機制以及超時時間。我們可以自定義自己的會話,不過默認的會話對我們來說就足夠了。最後,我們執行基於我們網路請求的 的下載任務。
好的,我現在需要暫停一下,先不繼續介紹剩餘的代碼,在此之前,我想先聊一聊 下載任務。下載任務實際上是 的一個子類,它們實際上有三種類型:
(針對即時的網路請求,例如口令驗證)
我們通過會話對象來獲取我們的會話任務,而不是為了調用某些會話任務的便利構造器方法。也就是說,我們通過會話 URL 來進行反饋。
這意味著什麼呢?我喜歡將 視為小狗狗,而我們的 URL 則是那些骨頭,因此每次我們給它骨頭的時候,它就會被歡樂所淹沒,然後去抓兔子。而這恰好是 我們想要的 。我們可以從同一個 中多次獲取不同的會話任務,只需要為會話和會話任務創建一對多的關係即可。一個會話可以有多個會話任務,但是每個會話任務只能夠給一個會話反饋。在代碼中,我們有許多在會話對象中創建任務的方法,比如 。
好的,我們回到之前的代碼,下面是談論完成處理回調(completion handler) 裡面內容的時候了。對於這個完成處理回調來說,它當中有三個參數:
Location (文件即將下載到的臨時路徑)
Response (可在此獲取網路請求的狀態碼)
Error (如果有問題出現,可在此捕獲)
你可能注意到了,我們有對錯誤進行任何的處理,也沒有捕獲任何異常。我假設我們的代碼處於一個沒有錯誤和異常的世界。因此,我們所有做的就是將文件從臨時目錄移到在此之前指定的下載路徑當中。
代碼很成功,但是當用戶切到其他應用的時候怎麼辦?我們不想讓我們的這壺熱水變成徹頭徹尾的恥辱。
後台下載 — NSURLSessionConfiguration
要實現後台下載,我們需要使用 告知系統這些任務需要後台服務兼容。之前我說過我們可以初始化自定義的會話,這樣我們可以自定義某些屬性,比如說緩存機制以及超時時間。其實並不是這樣的,我們並不會用這個方式來設置會話的某些屬性。我們所做的就是在某個「配置對象」中設置這些屬性,然後使用該配置來初始化會話。這是我們邁向後台的第一步。
我們通過某種名為後台配置的東西來創建自定義的會話。它只不過是在配置對象上的另一種設置罷了。下面的代碼解釋得更清晰一些:
let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("i am the batman") let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
我們繼續一行一行地看,第一行代碼用名為 的方法創建了一個會話配置,這個標誌符可以任意取名,比如說你最喜歡的顏色,Moby Dick 的首字母,或者我們這裡寫的「I am the batman」。第二行我們用這個配置初始化了一個 。我們將這個類本身設為委託,因此它將可以獲取該會話接收到的所有委託方法了。最後,我們設置委託隊列(delegate queue)。
委託隊列可以設置為任意一個您打算用以調用委託方法的隊列。不過,如果設置為 的話,它會使用默認的委託隊列。在我們將這些代碼加回我們的初始示例之前,我想要強調一下這些標識符必須是唯一的。至於為什麼,我們首先需要了解一下後台請求的生命周期。
我們在 Dropbox 應用中執行這個前台網路請求。我們將文件同步到設備中,接著殺死這個應用。這個時候,會話中的所有網路請求仍在繼續,這是因為這些請求都在後台進行。當系統回告應用,說「該會話中的所有請求都已結束」的時候,將會調用所有的 方法,從而讓我們知道接下來該做什麼操作。
唯一區別是,除了調用 之外,它還會觸發一個新的方法,名為 。這個方法中會傳回你所定義的會話標誌符。比如說我們之前所定義的「I am the Batman」。整個過程就像這樣:名為「I am the Batman」 的會話結束了!之後您打算怎麼做呢?無論我們之後是在發生錯誤時處理錯誤,還是會話成功完成,我們都必須要在這個方法中重新創建這個會話。至於代碼是什麼樣子的,它實際上意味著使用同一個標識符創建另一個後台配置,然後用這個後台配置創建另一個會話。
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler:() -> Void) { let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(identifier) let session = NSURLSession(configuration: config, delegate: self, delegateQueue: NSOperationQueue.mainQueue()) session.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) -> Void in // 執行您自己的任務請求! } }
這就是您需要執行的循環鏈,當我們重新獲取了新的會話任務後,系統會知道「會話重新激活,現有的任務都是與該會話建立關聯的任務」。
然而,由於系統只知道是哪一個任務是被會話標識符單獨重建的,但是如果有兩個會話擁有同一個標識符的話,它就力不從心了。這樣一來,系統該如何知曉哪一個任務是您想要重新創建的呢?事實上文檔中有一個很不好的消息是,多個共享相同標誌符的會話行為是不確定的。因此我們不要這樣做。現在我們將剛才創建的後台配置的代碼放到之前的代碼當中去。絕大部分代碼都差不多一樣,除了這兩行新代碼:
let urlstring = "https://remoteteakettle.com/boiledwater.pdf" let filepath = "Documents/local_teakettle" if let url = NSURL(string: urlstring) { let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier((NSUUID().UUIDString)) let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil) let task = NSURLSession.sharedSession().downloadTaskWithURL(url, completionHandler: { (location:NSURL?, response:NSURLResponse?, error:NSError?) in if let loc = location, path = loc.path { try! NSFileManager.defaultManager().moveItemAtPath(path, toPath:filepath) } }) task.resume() }陷阱 #1: 沒有完成處理回調
很遺憾的是,我們並不能讓後台請求結束時立即做些什麼。當您試圖為後台任務創建一個擁有完成處理回調的任務時,控制台會警報說這項功能不被支持。因此我們需要使用委託方法。當應用重新啟動以及在 方法中重建會話之後,應用將調用該會話中的所有委託方法。通常情況下,我們一般關注的是 這個方法。它將會給予回調:
會話對象
完成的任務
文件被下載到的臨時路徑
這就是我們要做的第二個步驟。我們將完成處理回調中的代碼移到這個委託方法中。下面就是代碼應該有的樣子:
let urlstring = "https://remoteteakettle.com/boiledwater.pdf" let filepath = "Documents/local_teakettle" if let url = NSURL(string: urlstring) { let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier((NSUUID().UUIDString)) let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil) let task = session.downloadTaskWithURL(url) task.resume() } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { if let path = location.path { try! NSFileManager.defaultManager().moveItemAtPath(path, toPath:filepath) } }
我們使用此委託方法而不是初始化一個帶有完成處理回調的下載任務。不幸的是,這個辦法也不是很成功,因為 在委託方法的識別範圍之外。
陷阱 #2: 沒有附屬請求信息
由於我們必須獲取不同方法中關於網路請求的相關信息,比如說最終的文件路徑,因此我們必須用某種方式將這些信息存儲下來。我們可以使用 來進行處理,但是文檔說明「這個屬性需要包含用戶可讀的字元串,也就是說它可以展示在應用界面當中」。由於我們可能會想要存儲許多請求信息,比如說文件路徑、模型 UUID 或者文件名字等等內容,因此這個方法不是最佳選擇。 的子類同樣也不是一個好選擇,因為我們的 只會返回預定義的類,並不能返回我們自定義的子類。
解決方法: 存儲請求信息
如果系統不給我們提供這個功能,那麼我們就自己實現它!我們將要自己存儲這些數據:
public class HalfBoiledWater: NSObject { public let sessionId: String public let taskId: Int public let filepath: String init(sessionId:String, taskId:Int, filepath:String) { self.sessionId = sessionId self.taskId = taskId self.filepath = filepath } func save() { // 保存到數據存儲區域 } } public func fetchModel(sessionId:String, taskId:Int) -> HalfBoiledWater { // 從數據存儲區域中檢索 }
好的,現在我們可以有多種方法來對其進行存儲了。我們每個人都有自己最喜歡的數據存儲方式。它可能是 FMDB、SQLite,甚至是 Core Data。我打算讓大家自行決定,在這兩個方法 和 中自行實現。注意到我們在這個打算放入資料庫並執行檢索的序列化模型中添加了文件路徑。我們同樣也使用 和 作為主鍵以便能夠存儲這個模型。下面是我們添加的示例代碼:
let urlstring = "https://remoteteakettle.com/boiledwater.pdf" let filepath = "Documents/local_teakettle" if let url = NSURL(string: urlstring) { let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier((NSUUID().UUIDString)) let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil) let task = session.downloadTaskWithURL(url) if let sessionId = session.configuration.identifier { let persistedModel = HalfBoiledWater(sessionId:sessionId, taskId:task.taskIdentifier, filepath:filepath) persistedModel.save() } task.resume() } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { if let sessionId = session.configuration.identifier { let persistedModel = fetchModel(sessionId, taskId:downloadTask.taskIdentifier) if let path = location.path { try! NSFileManager.defaultManager().moveItemAtPath(path, toPath:persistedModel.filepath) } }
我們創建了下載任務,還同樣創建了所有我們所需要的模型信息,並將其存儲在資料庫當中。接下來,在我們的委託方法中,當我們需要返迴文件路徑的時候,我們只需要用會話 ID 以及任務標識符作為鍵來檢索這個模型即可。
一般情況下,作為最基本的後台下載來說,這些代碼就已經足夠了。總的來說,我們首先創建了後台配置用以告知系統我們想讓該會話中所有的任務都能夠在後台運行。接著,我們將代碼從完成處理回調中移到了委託方法裡面。然後,我們用某種方法存儲了我們在委託方法中所需的請求信息,這樣我們就可以在需要的時候檢索調用。
這並沒有結束,藉助這個 API 我們還有許多事情可以做。我們可以顯示進度,取消下載,或者在錯誤發生的時候重置下載等等。此外,還有很多潛在的問題等著你去發現,去解決。
我希望本次講座能夠幫助大家將後台下載功能添加到應用當中,這樣用戶就不必一直開著前台等待下載了。謝謝大家!
※RabbitMQ 實戰教程 路由
※AtomicInteger 與樂觀鎖
※EA 商業遊戲新作 NBA LIVE 2018 正式出爐,籃球遊戲又迎來血戰
※原來,中國的設計師一直缺一個像樣的協同工具
TAG:推酷 |
※影視夢工廠·當你沉睡時
※《當你沉睡時》的她被爆戀愛傳聞!
※虧韓國想得出來!《當你沉睡時》把死神來了之蝴蝶效應拍成偶像劇
※《當你沉睡時》通過做夢預知未來事件!
※還記得當你沉睡時的女主嗎?獨特的衣品讓她如此吸睛!
※《當你沉睡時》女主裴秀智,是好演員也是好歌手
※鏡頭下:疲憊後隨時隨地,沉沉睡去的中國人
※當你沉睡的時候,你的身體會趁機做一些奇怪的事
※《爐石傳說》薩滿的傳說法術可以影響沉睡的屍魔花嗎?真相了
※茶包大測評之挽救沉睡的你
※寵主叫不醒沉睡的柴犬,最後用神器一滴即可,模樣心都要被暖化
※鬣狗偷襲沉睡中的獅子,獅子的反應讓人目瞪口呆!
※《當你沉睡時》熱播後,裴秀智的盛世美顏再次火了,你喜歡嗎?
※李鍾碩接新劇了!零片酬合作《當你沉睡時》PD
※繼《當你沉睡時》後,裴秀智又1作品即將播出,網友:男主超帥!
※當你沉睡時秀智歐尼運動裝帥氣上線啦!
※時髦指數爆棚的衛衣,率性中有帶著一絲青春少女氣息,喚醒沉睡的魅力與潮流范!
※李鍾碩和裴秀智太養眼,《當你沉睡時》CP再次合作時尚芭莎。
※一部喚醒沉睡女性的作品,讓你能在迷茫之時找到方向
※《當你沉睡時》高顏值組合甜虐眾人,這個冬天就畫「秀智妝」