如何判断我的应用是否已暂停?

时间:2014-06-16 13:26:49

标签: ios objective-c

我对iOS状态管理的理解是,当用户点击主页按钮时,应用程序变为非活动状态,然后进入后台,然后暂停几秒钟。如果系统需要释放内存或者用户将应用程序从最近列表中移开,则会终止暂停的应用程序。

我的问题是,有没有办法告诉我我的应用已离开后台状态并进入暂停状态?我了解应用委托方法,例如{{ 1}}等,但有没有办法可以告诉应用程序被暂停?我是否认为暂停与终止相同?

我提出这个问题的背景是我正在创建一个音频播放器应用。我已经在applicationDidEnterBackground中启用了背景音频,所以当播放音频时我按下主页按钮我可以看到应用程序无限期地保留在后台(这很好)。但是,当音频没有播放时,不需要将应用程序保留在后台,据我所知,该应用程序应该暂停。我希望能够检查这是否正在发生!

非常感谢 - 并纠正我的任何误解。

3 个答案:

答案 0 :(得分:3)

iOS不会报告应用何时将被挂起(从后台置于非处理状态),也不会在应用恢复后触发通知。对此有些困惑,因为当应用程序变为“活动”状态或将退出“活动”状态时会发出通知,但这并不总是所需的正确值。 iOS应用具有多种状态:

  • 活动:应用位于最前面(最前面),没有通知或菜单被拉到上方。下拉菜单或获取外部通知或短信将使应用程序“退出”活动状态,并在警报处理后恢复活动状态。
  • 背景:应用程序不在前台,但仍在处理中。如果没有后台任务在运行,这种情况会在挂起之前短暂发生;如果长时间运行的后台模式(音频,位置等)正在运行,则此状态可能会变为永久状态。
  • 已暂停:应用程序在内存中,但运行循环和处理已暂停
  • 已终止:应用未运行或在内存中,已被用户(强制退出)或操作系统(以释放内存)退出,或者从未启动

为了捕获何时暂停应用程序,您必须捕获何时将应用程序置于后台,并使用backgroundTimeRemaining之类的值来估计何时暂停。实际的暂停只能计算为运行循环处理中的“间隔”,可以使用计划的循环计时器和减法来完成。我为此创建了一个帮助器类:

https://gist.github.com/BadPirate/0a480b947744c8c0e326daa4ab479b09

import UIKit
import Foundation

internal extension Notification.Name {
    static let applicationWillSuspend = Notification.Name("application-will-suspend")
    /// This notification gets called after the fact, but the `object` parameter is set to the `Date` of when the suspend occurred
    static let applicationDidSuspend = Notification.Name("application-did-suspend")
    static let applicationDidUnsuspend = Notification.Name("application-did-unsuspend")
    static let suspendStatusRecorderFailed = Notification.Name("suspend-status-recorder-failed")
}

internal class SuspendStatusRecorder {
    private var timer : Timer?
    private var task : UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid

    /// Start monitoring for suspend
    /// - parameter stallThreshold: Number of seconds of no processing before reporting a stall event
    internal func start() {
        stop() // If already going.
        startTask()
        let timer = Timer(timeInterval: 1, repeats: true) { [weak self] (_) in
            self?.checkStatus()
        }
        RunLoop.main.add(timer, forMode: .common)
    }

    internal func stop() {
        if let timer = timer {
            timer.invalidate()
            self.timer = nil
        }
        endTask()
    }

    private var lastPing : Int = 0
    private func willExpire() {
        endTask() // Allow app to suspend
        NotificationCenter.default.post(name: .applicationWillSuspend, object: nil)
        expectingSuspend = true
    }

    /// Set to an uptime value for when we expect our app to be suspended based on backgroundTimeRemaining
    private var expectingSuspend = false

    private func checkStatus() {
        let ping = uptime()
        if expectingSuspend {
            if ping - lastPing > 3 ||
            UIApplication.shared.applicationState == .active
            {
                // Timer stalled, either CPU failure or we were suspended.
                NotificationCenter.default.post(name: .applicationDidSuspend, object: Date(timeIntervalSinceNow: TimeInterval(lastPing - ping)))
                NotificationCenter.default.post(name: .applicationDidUnsuspend, object: nil)
                expectingSuspend = false
                startTask() // New background task so that we can make sure to catch next event
            }
        }
        lastPing = uptime()

        // In background, time is going to expire (resulting in suspend), report and end task
        if UIApplication.shared.applicationState == .background &&
           UIApplication.shared.backgroundTimeRemaining != Double.greatestFiniteMagnitude &&
           task != UIBackgroundTaskIdentifier.invalid
        {
            willExpire()
        }
    }

    private func endTask() {
        if task != UIBackgroundTaskIdentifier.invalid {
            UIApplication.shared.endBackgroundTask(task)
            self.task = UIBackgroundTaskIdentifier.invalid
        }
    }

    private func startTask() {
        task = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
            self?.willExpire()
        })
    }

    private func uptime() -> Int {
        var uptime = timespec()
        if 0 != clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) {
            NotificationCenter.default.post(name: .suspendStatusRecorderFailed, object: "Could not execute clock_gettime, errno: \(errno)")
            stop()
        }
        return uptime.tv_sec
    }

    deinit {
        stop()
    }
}

答案 1 :(得分:2)

您没有收到有关被暂停的通知:

https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html

“暂停:应用程序在后台但没有执行代码。系统会自动将应用程序移动到此状态,并且在执行此操作之前不会通知它们。暂停时,应用程序仍保留在内存中但不执行任何代码。

当内存不足时,系统可能会清除已暂停的应用程序,恕不另行通知,以便为前台应用程序腾出更多空间。“

答案 2 :(得分:1)

您可以使用后台任务来检查应用是否已暂停。

  1. 创建一个SuspensionObserver之类的帮助器类。
  2. SuspensionObserver内,您应该有一个类似wasAppSuspended的标志。
  3. func observe()内创建UIApplication.shared.beginBackgroundTask
  4. wasAppSuspended内将false标志设置为observe()
  5. wasAppSuspended内将true标志设置为expirationHandler
  6. observe()didFinishLaunchingWithOptions呼叫applicationDidEnterBackground
  7. 现在在applicationWillEnterForeground中,您可以检查该应用是否已暂停。

此外,您还可以将委托/处理程序设置为在应用暂停之前执行所需的操作。