当应用程序处于后台/非活动状态时,GCD`asyncAfter`无法启动

时间:2017-05-09 15:07:35

标签: swift multithreading background

我使用AVAudioPlayer播放唱片。在每个回放会话之间,我有0到10秒的间隔。为了使这个间隔我使用AVAudioPlayerDelegate并且在播放完成后我在延迟后开始新的播放:

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
    guard let session = playbackSessionId,
          let audioTrack = audioTrack,
          let failureHandler = playingFailure,
          let successHandler = playingSuccess else {
        playingFinished(flag, error: nil)
        return
    }
    print("audioPlayerDidFinishPlaying fired")
    DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + LoopInterval.currentInterval) { [weak self] in
        print("asyncAfter fired")
        guard let strongSelf = self,
              let currentSession = strongSelf.playbackSessionId, session == currentSession else { return }
        strongSelf.startPlayingRecordInLoop(audioTrack, success: successHandler, failure: failureHandler)
    }
}

应用转到后台(home按钮)后,audioPlayerDidFinishPlaying会触发,但DispatchQueue.global(qos: .utility).asyncAfter不会触发。所以在控制台中我看到:

audioPlayerDidFinishPlaying fired

只要应用激活,asyncAfter就会触发,我会看到下一条日志消息:

asyncAfter fired

当应用处于有效状态时,全部按预期工作。

1 个答案:

答案 0 :(得分:2)

希望它能帮助别人。我发现问题:当应用程序进入后台时,它会停止后台任务,并且只有在激活后才会触发它们。为避免需要,您应该让您的应用程序在后台运行并等待长时间运行的后台任务。

backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: {
    UIApplication.shared.endBackgroundTask(backgroundTaskID)
})
  

此方法可让您的应用在转换为背景后继续运行一段时间。您应该在未完成任务的情况下调用此方法,这可能会损害您应用的用户体验。例如,您的应用可以调用此方法以确保有足够的时间将重要文件传输到远程服务器,或者至少尝试进行传输并记录任何错误。您不应该仅仅使用此方法使应用程序在移动到后台后继续运行。

任务完成后,您应致电endBackgroundTask。如果您在backgroundTimeRemaining变为0之前不能结束后台任务,则应用将终止:

UIApplication.shared.endBackgroundTask(backgroundTaskID)
  

必须通过对endBackgroundTask( :)方法的匹配调用来平衡对此方法的每次调用。运行后台任务的应用程序运行它们的时间有限。 (您可以使用backgroundTimeRemaining属性找出可用的时间。)如果在时间到期之前没有为每个任务调用endBackgroundTask( :),系统会终止应用程序。如果在handler参数中提供块对象,系统会在时间到期之前调用您的处理程序,以便您有机会结束任务。

这就是我在案件中所做的:

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
    guard let session = playbackSessionId,
          let audioTrack = audioTrack,
          let failureHandler = playingFailure,
          let successHandler = playingSuccess else {
        playingFinished(flag, error: nil)
        return
    }
    backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
        guard let taskId = self?.backgroundTaskID else { return }
        UIApplication.shared.endBackgroundTask(taskId)
    })
    DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + LoopInterval.currentInterval) { [weak self] in
        guard let strongSelf = self,
              let currentSession = strongSelf.playbackSessionId, session == currentSession else { return }
        strongSelf.startPlayingRecordInLoop(audioTrack, success: successHandler, failure: failureHandler)
        if let backgroundTaskID = strongSelf.backgroundTaskID {
            UIApplication.shared.endBackgroundTask(backgroundTaskID)
            strongSelf.backgroundTaskID = nil
        }
    }
}