当应用程序移动到后台时,AFNetworking 3.0 继续上传

Posted

技术标签:

【中文标题】当应用程序移动到后台时,AFNetworking 3.0 继续上传【英文标题】:AFNetworking 3.0 continuing upload when app moves to background 【发布时间】:2016-02-10 17:21:43 【问题描述】:

我的应用在前台启动将内容上传到服务器。由于此过程可能需要一些时间,因此用户可以很好地将应用程序切换到后台。

我正在使用 AFHTTPSessionManager 提交上传:

let sessionManager = AFHTTPSessionManager()
sessionManager.requestSerializer.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-type")
sessionManager.POST(urlStr, parameters: params, constructingBodyWithBlock:  (formData) -> Void in
    formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
, progress:  (progress) -> Void in
, success:  (task, responseObject) -> Void in
    print(responseObject)
    success(responseObject: responseObject)
, failure:  (task, error) -> Void in
    print(error)
    if let errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData 
        do 
            let json = try NSJSONSerialization.JSONObjectWithData(errorData, options: NSJSONReadingOptions.MutableContainers)
            failure(error: error, responseObject: json)
         catch let error 
            print(error)
        
    
)

我需要在应用程序处于后台状态时继续执行此过程,直到完成。有一个很好的 SO 答案here 让我走上了正轨,但在某些地方我仍然一无所知。我尝试使用链接答案中的“BackgroundSessionManager”类将我的上传调用更改为:

BackgroundSessionManager.sharedManager().POST(NetworkManager.kBaseURLString + "fulfill_request", parameters: params, constructingBodyWithBlock:  (formData) -> Void in
    formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
, progress: nil, success: nil, failure: nil)?.resume()

并将其添加到我的 AppDelegate 中:

func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) 
    BackgroundSessionManager.sharedManager().savedCompletionHandler = completionHandler

但是我在一些后台线程上遇到了 EXC_BAD_ACCESS 崩溃,几乎没有信息。我是否正确地接近这个?

【问题讨论】:

【参考方案1】:

几个观察:

    您正在构建一个多部分请求,但随后将Content-Type 标头强制为application/x-www-form-urlencoded。但这不是多部分请求的有效内容标头。我建议删除请求标头的手动配置(或告诉我们你为什么这样做)。 AFNetworking 将为您设置Content-Type

    您可能只需要一点时间来完成请求,即使用户离开了应用程序,您也不必费心处理后台会话。

    var backgroundTask = UIBackgroundTaskInvalid
    
    func uploadImageWithData(dataObject: NSData, mimeType: String, urlStr: String, params: [String: String]?, success: (responseObject: AnyObject?) -> (), failure: (error: NSError, responseObject: AnyObject) -> ()) 
        let app = UIApplication.sharedApplication()
    
        let endBackgroundTask = 
            if self.backgroundTask != UIBackgroundTaskInvalid 
                app.endBackgroundTask(self.backgroundTask)
                self.backgroundTask = UIBackgroundTaskInvalid
            
        
    
        backgroundTask = app.beginBackgroundTaskWithName("com.domain.app.imageupload") 
            // if you need to do any addition cleanup because request didn't finish in time, do that here
    
            // then end the background task (so ios doesn't summarily terminate your app
            endBackgroundTask()
        
    
        let sessionManager = AFHTTPSessionManager()
        sessionManager.POST(urlStr, parameters: params, constructingBodyWithBlock:  (formData) -> Void in
            formData.appendPartWithFileData(dataObject, name: "object", fileName: mimeType == "image/jpg" ? "pic.jpg" : "pic.mp4", mimeType: mimeType)
            , progress:  (progress) -> Void in
            , success:  (task, responseObject) -> Void in
                print(responseObject)
                success(responseObject: responseObject)
                endBackgroundTask()
            , failure:  (task, error) -> Void in
                print(error)
                if let errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] as? NSData 
                    do 
                        let json = try NSJSONSerialization.JSONObjectWithData(errorData, options: NSJSONReadingOptions.MutableContainers)
                        failure(error: error, responseObject: json)
                     catch let error 
                        print(error)
                    
                
                endBackgroundTask()
        )
    
    

    我建议添加一个exception breakpoint,看看这是否可以帮助您找到违规行。

    坦率地说,如果异常在 NSURLSession 内部处理中异步发生,这可能无济于事,但这是我尝试追踪异常源时的第一件事。

    如果你真的觉得你必须使用后台会话(即你的上传任务可能需要超过beginBackgroundTaskWithName 允许的三分钟),那么请注意后台任务必须是上传或下载任务。

    但是,我相信POST 方法会创建一个数据任务。在 iOS 7 中,您根本不允许在后台会话中使用数据任务。在 iOS 8 中,您可以使用后台任务启动数据任务,但您必须使用 NSURLSessionResponseBecomeDownload 响应 didReceiveResponse,例如在configureDownloadFinished 的那个BackgroundSessionManager 中,你可以尝试添加:

    [self setDataTaskDidReceiveResponseBlock:^NSURLSessionResponseDisposition(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response) 
        return NSURLSessionResponseBecomeDownload;
    ];
    

    或者,如果您再次必须使用后台会话,那么您可能希望手动构建您的 NSURLRequest 对象并将其作为上传或下载任务提交,而不是使用 POST。 AFNetworking 提供了将请求的构建和请求的提交分离的机制,因此如果您需要走这条路,请告诉我们,我们可以向您展示如何做到这一点。

底线,beginBackgroundTaskWithName 将比使用后台NSURLSession 更容易请求一点时间来完成请求。仅在绝对必要时才执行后者(例如,您很可能需要超过三分钟才能完成请求)。

【讨论】:

非常感谢您如此彻底。我会尝试你建议的一些不同的东西并发布更新。 只是出于好奇,您从哪里获得更简单的后台任务的 3 分钟时间限制? Apple 似乎对实际时间限制非常模糊,但我在其他地方看到它最多可以支持 10 分钟。 我所知道的任何地方都没有正式记录它,尽管我模糊记得它在一个 WWDC 视频中被引用。以前是 10 分钟,但现在是 3 分钟,AFAIK。如果你打电话给backgroundTimeRemaining,它会准确地告诉你你被给予了多少时间。 再次感谢。最终选择了beginBackgroundTaskWithName。我们将来可能想使用后台会话,但更简单的方法效果很好。 @Rob 上传任务超过 3 分钟怎么办?我想通过参数和使用 AFNetworking 将视频上传到服务器。使用过发布、上传任务和数据任务方法。但是系统只提供了3分钟的上传时间。应用程序终止并停止上传后。

以上是关于当应用程序移动到后台时,AFNetworking 3.0 继续上传的主要内容,如果未能解决你的问题,请参考以下文章

AFNetworking 2.0.0 后台下载

当应用程序移动到后台时,android是不是会清除视图中的数据?

AFNetworking 3.0 下载后台模式

当app从后台移动到前台时会触发哪个JS事件

如何使用 Afnetworking 在 ios 中在后台上传图像

AFNetworking 后台会话 KVO 错误