Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tell if my app has suspended?

My understanding of iOS state management is that when the user hits the home button, the app becomes inactive, then enters the background, and then after a few seconds is suspended. A suspended app is then terminated if the system needs to free memory or if the user swipes the app away from the recents list.

My question is, is there any way for me to tell that my app has left the background state and entered the suspended state? I'm aware of the application delegate methods like applicationDidEnterBackground etc, but is there a way that I can tell the app was suspended? Am I correct in thinking that being suspended is not the same as being terminated?

My context for asking this question is that I'm creating an audio player app. I've enabled background audio in Info.plist and so when audio is playing and I press the home button I can see that the app remains in the background indefinitely (which is good). However, when audio is not playing there's no need to keep the app in the background and, as far as I understand it, the app ought to suspend. I want to be able to check whether or not this is happening!

Thanks very much - and do correct any misunderstandings I have.

like image 554
ben Avatar asked Jun 16 '14 13:06

ben


People also ask

How do I restore a suspended app from Google Play?

You can appeal your app's removal from Google Play. We will reinstate apps in appropriate circumstances, including if an error was made and we find that your app does not violate the Google Play Developer Program Policies and Developer Distribution Agreement.

What does suspend an app mean?

Print. Modified on: Mon, Dec 14, 2020 at 4:17 PM. When the app is running in the background (for both iOS and Android), the operating system may suspend it. When that happens, the data from your devices will NOT sync.

What happens if an app gets 1 star on Play Store?

However, even if the rating of the short-video platform falls to 1 star it is highly unlikely that it will be removed from Google Play store. “An app with 1 star rating will still be available for download even after it gets poor ratings.

What iOS app suspended?

Active - the normal state of "in use" for an app. Background - the app is no longer on-screen but is still executing code. Suspended - the app is still resident in memory but is not executing code.


2 Answers

iOS doesn't report when an app will be suspended (placed from background into a non-processing state) nor does it seem to fire a notification once the app has resumed. There is some confusion about this as there is a notification when the app becomes "active" or will resign the "active" state, however this is not always the right value needed. iOS Apps have a number of states:

  • Active: App is in the foreground (frontmost) and there are no notifications or menu's pulled over it. Pulling a menu down or getting an external notification or text message will cause the app to "resign" active, and resume active once the alert has been dealt with.
  • Background: App is not in the foreground but still processing. This happens briefly before suspend if there are no background tasks running, or can be a permanent state if there is a long running background mode (audio, location, etc) running.
  • Suspended: App is in memory, but run loops and processing is paused
  • Terminated: App is not running or in memory, has been quit either by user (force quit) or OS (to free up memory) or was never launched

In order to capture when app is suspended, you'll have to capture when app is being put into background and use values like backgroundTimeRemaining to estimate when a suspension will occur. Actual suspension can only be calculated as a "gap" in run loop processing, which can be done with a scheduled recurring timer and subtraction. I've created a helper class for this:

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()
    }
}
like image 161
BadPirate Avatar answered Oct 04 '22 18:10

BadPirate


You don't get a notification about being suspended:

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

"Suspended: The app is in the background but is not executing code. The system moves apps to this state automatically and does not notify them before doing so. While suspended, an app remains in memory but does not execute any code.

When a low-memory condition occurs, the system may purge suspended apps without notice to make more space for the foreground app."

like image 38
Almo Avatar answered Oct 04 '22 19:10

Almo