it-swarm.com.de

iPhone - Hintergrundinformationen für Ereignisse

Ich habe schon eine ganze Weile nach einer Möglichkeit gesucht, in meiner iPhone-App alle X Minuten nach den Datenzählern zu suchen. Nach langem Lesen der Dokumentation zur Hintergrundausführung und einigen Test-Apps hatte ich dies als unmöglich abgetan, ohne die Hintergrund-APIs zu missbrauchen.

Letzte Woche habe ich diese Anwendung gefunden, die genau das tut. http://iTunes.Apple.com/us/app/dataman-real-time-data-usage/id393282873?mt=8

Es läuft im Hintergrund und verfolgt die Anzahl der von Ihnen verwendeten Mobilfunk-/WLAN-Daten. Ich vermute, dass der Entwickler seine App als Standortverfolgung registriert, das Symbol für die Standortdienste jedoch nicht sichtbar ist, während die App ausgeführt wird.

Hat jemand irgendwelche Anhaltspunkte, wie dies erreicht werden kann? 

65
NeilInglis

Ich habe auch dieses Verhalten gesehen. Nach vielem Ausprobieren entdeckte ich zwei Dinge, die helfen könnten. Ich bin jedoch immer noch unsicher, wie dies den Überprüfungsprozess beeinflussen kann.

Wenn Sie eine der Hintergrundfunktionen verwenden, wird die App nach dem Beenden (vom System) erneut im Hintergrund von iOS gestartet. Das werden wir später missbrauchen.

In meinem Fall habe ich VoIP-Hintergrund in meiner Liste aktiviert. Der gesamte Code hier wird in Ihrem AppDelegate erstellt:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");
    // try to do sth. According to Apple we have ONLY 30 seconds to perform this Task!
    // Else the Application will be terminated!
    UIApplication* app = [UIApplication sharedApplication];
    NSArray*    oldNotifications = [app scheduledLocalNotifications];

     // Clear out the old notification before scheduling a new one.
    if ([oldNotifications count] > 0) [app cancelAllLocalNotifications];

    // Create a new notification
    UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
    if (alarm)
    {
        alarm.fireDate = [NSDate date];
        alarm.timeZone = [NSTimeZone defaultTimeZone];
        alarm.repeatInterval = 0;
        alarm.soundName = @"alarmsound.caf";
        alarm.alertBody = @"Don't Panic! This is just a Push-Notification Test.";

        [app scheduleLocalNotification:alarm];
    }
}

und die Registrierung erfolgt in

- (void)applicationDidEnterBackground:(UIApplication *)application {

    // This is where you can do your X Minutes, if >= 10Minutes is okay.
    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }
}

Nun kommt der Zauber: Ich benutze keine VoIP-Sockets. Dieser Rückruf von 10 Minuten hat jedoch einen netten Nebeneffekt: Nach 10 Minuten (manchmal früher) stellte ich fest, dass meine Timer und meine vorherigen Laufschritte für kurze Zeit ausgeführt werden. Sie können dies sehen, wenn Sie NSLog (..) in Ihren Code einfügen. Dies bedeutet, dass dieses kurze "Aufwachen" den Code für eine Weile ausführt. Laut Apple haben wir noch 30 Sekunden Ausführungszeit. Ich gehe davon aus, dass Hintergrundcode wie Threads für fast 30 Sekunden ausgeführt werden. Dies ist ein nützlicher Code, wenn Sie "manchmal" etwas überprüfen müssen.

Das Dokument besagt, dass alle Hintergrundaufgaben (VoIP, Audio, Standortaktualisierungen) automatisch im Hintergrund neu gestartet werden, wenn die App beendet wurde. VoIP-Apps werden nach dem Booten automatisch im Hintergrund gestartet!

Wenn Sie dieses Verhalten missbrauchen, können Sie dafür sorgen, dass Ihre App "für immer" ausgeführt wird . Registrieren Sie sich für einen Hintergrundprozess (d. H. VoIP). Dadurch wird Ihre App nach dem Beenden neu gestartet.

Schreiben Sie jetzt den Code "Aufgabe muss abgeschlossen werden". Laut Apple haben Sie noch etwas Zeit (5 Sekunden?), Um die Aufgaben abzuschließen. Ich entdeckte, dass dies CPU-Zeit sein muss. Das heißt also: Wenn Sie nichts tun, wird Ihre App noch ausgeführt! Apple schlägt vor, einen Expirationhandler anzurufen, wenn Sie mit Ihrer Arbeit fertig sind. Im folgenden Code können Sie sehen, dass ich beim expirationHandler einen Kommentar habe. Dadurch wird Ihre App ausgeführt, solange das System die Ausführung Ihrer App zulässt. Alle Timer und Threads bleiben aktiv, bis iOS Ihre App beendet.

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // you can do sth. here, or simply do nothing!
    // All your background treads and timers are still being executed
    while (background) 
       [self doSomething];
       // This is where you can do your "X minutes" in seconds (here 10)
       sleep(10);
    }

    // And never call the expirationHandler, so your App runs
    // until the system terminates our process
    //[app endBackgroundTask:bgTask];
    //bgTask = UIBackgroundTaskInvalid;

    }); 
}

Seien Sie mit CPU-Time hier sehr frei und Ihre App läuft länger! Eines ist jedoch sicher: Ihre App wird nach einiger Zeit beendet. Da Sie Ihre App jedoch als VoIP oder als eine der anderen Apps registriert haben, startet das System die App im Hintergrund neu, wodurch der Hintergrundprozess erneut gestartet wird. Mit diesem PingPong kann ich viel Hintergrundbild erstellen. Denken Sie jedoch daran, dass Sie mit CPU-Zeit sehr viel Zeit sparen. Speichern Sie alle Daten, um Ihre Ansichten wiederherzustellen - Ihre App wird einige Zeit später beendet. Damit es immer noch als lauffähig erscheint, müssen Sie nach dem Aufwachen in Ihren letzten "Status" zurückspringen.

Ich weiß nicht, ob dies der Ansatz der Apps ist, die Sie zuvor erwähnt haben, aber es funktioniert für mich.

Ich hoffe ich konnte helfen

Update:

Nach der Messung der Zeit der BG-Aufgabe gab es eine Überraschung. Die BG-Aufgabe ist auf 600 Sekunden begrenzt. Dies ist die genaue Mindestzeit der VoIP-Mindestzeit (setKeepAliveTimeout: 600).

DIESER Code führt also im Hintergrund zu einer "unendlichen" Ausführung:

Header:

UIBackgroundTaskIdentifier bgTask; 

Code:

// if the iOS device allows background execution,
// this Handler will be called
- (void)backgroundHandler {

    NSLog(@"### -->VOIP backgrounding callback");

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    while (1) {
        NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
        sleep(1);
    }   
});     

- (void)applicationDidEnterBackground:(UIApplication *)application {

    BOOL backgroundAccepted = [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [self backgroundHandler]; }];
    if (backgroundAccepted)
    {
        NSLog(@"VOIP backgrounding accepted");
    }

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    // Start the long-running task
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        while (1) {
            NSLog(@"BGTime left: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
           [self doSomething];
           sleep(1);
        }    
    }); 
}

Nachdem Ihre App abgelaufen ist, wird der VoIP expirationHandler aufgerufen, in dem Sie einfach eine lange laufende Task erneut starten. Diese Aufgabe wird nach 600 Sekunden beendet. Es wird jedoch wieder ein Aufruf an den Ablauf-Handler gegeben, der eine weitere lang laufende Aufgabe startet, usw. Jetzt müssen Sie nur noch prüfen, ob die App wieder in den Vordergrund kommt. Schließen Sie dann die bgTask und fertig. Vielleicht kann man etw. so im expirationHandler von der lang laufenden Aufgabe. Probieren Sie es einfach aus. Verwenden Sie Ihre Konsole, um zu sehen, was passiert ... Viel Spaß!

Update 2:

Manchmal hilft es, Dinge zu vereinfachen. Mein neuer Ansatz ist dieser:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    // it's better to move "dispatch_block_t expirationHandler"
    // into your headerfile and initialize the code somewhere else
    // i.e. 
    // - (void)applicationDidFinishLaunching:(UIApplication *)application {
//
// expirationHandler = ^{ ... } }
    // because your app may crash if you initialize expirationHandler twice.
    dispatch_block_t expirationHandler;
    expirationHandler = ^{

        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;


        bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];
    };

    bgTask = [app beginBackgroundTaskWithExpirationHandler:expirationHandler];


    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // inform others to stop tasks, if you like
        [[NSNotificationCenter defaultCenter] postNotificationName:@"MyApplicationEntersBackground" object:self];

        // do your background work here     
    }); 
}

Dies funktioniert ohne der VoIP-Hack. Gemäß der Dokumentation wird der Ablaufhandler (in diesem Fall mein 'expirationHandler'-Block) ausgeführt, wenn die Ausführungszeit abgelaufen ist. Durch die Definition des Blocks in einer Blockvariablen kann die lange laufende Task innerhalb des Ablaufhandlers rekursiv gestartet werden. Dies führt auch zu einer endlosen Ausführung.

Denken Sie daran, die Aufgabe zu beenden, wenn Ihre Anwendung wieder in den Vordergrund wechselt. Und beenden Sie die Aufgabe, wenn Sie sie nicht mehr benötigen. 

Für meine eigene Erfahrung habe ich etwas gemessen. Die Verwendung des Standortrückrufs bei eingeschaltetem GPS-Funkgerät saugt meinen Akku sehr schnell an. Die Verwendung des Ansatzes, den ich in Update 2 veröffentlicht habe, erfordert fast keine Energie. Laut der "Benutzererfahrung" ist dies ein besserer Ansatz. Möglicherweise funktionieren andere Apps so und verbergen ihr Verhalten hinter der GPS-Funktionalität ...

91
JackPearse

Was funktioniert und was nicht

Es ist nicht ganz klar, welche dieser Antworten funktionieren und ich habe viel Zeit damit verbracht, sie alle auszuprobieren. Also hier meine Erfahrung mit jeder Strategie:

  1. VOIP-Hack - funktioniert, aber wird Sie zurückweisen, wenn Sie keine VOIP-App sind
  2. Rekursiver beginBackgroundTask... - funktioniert nicht. Es wird nach 10 Minuten beendet. Auch wenn Sie die Korrekturen in den Kommentaren versuchen (zumindest die Kommentare bis zum 30. November 2012).
  3. Silent Audio - funktioniert, aber dafür wurden die Leute abgelehnt
  4. Lokale/Push-Benachrichtigungen - Benutzerinteraktion erforderlich, bevor Ihre App aktiviert wird
  5. Hintergrundposition verwenden - arbeitet. Hier sind die Details:

Grundsätzlich verwenden Sie den Hintergrundmodus "location", damit Ihre App im Hintergrund läuft. Es funktioniert auch, wenn der Benutzer keine Standortaktualisierungen zulässt. Auch wenn der Benutzer die Home-Taste drückt und eine andere App startet, wird Ihre App weiterhin ausgeführt. Es ist auch eine Batterieentleerung und ist möglicherweise ein Teil des Genehmigungsprozesses, wenn Ihre App nichts mit dem Standort zu tun hat, aber meines Wissens ist dies die einzige Lösung, die eine gute Chance hat, genehmigt zu werden.

So funktioniert das:

In Ihrem Plist-Set:

  • Die Anwendung läuft nicht im Hintergrund: NEIN
  • Erforderliche Hintergrundmodi: Standort

Verweisen Sie dann auf das CoreLocation-Framework (in Build-Phasen) und fügen Sie diesen Code an einer beliebigen Stelle in Ihrer App ein (bevor er in den Hintergrund geht):

#import <CoreLocation/CoreLocation.h>

CLLocationManager* locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];

Hinweis: startMonitoringSignificantLocationChanges wird nicht funktionieren. 

Erwähnenswert ist auch, dass iOS die App bei einem Absturz nicht wieder zum Leben erweckt. Der VOIP-Hack ist der einzige, der ihn zurückbringen kann.

17
bendytree

Es gibt eine andere Technik, um für immer im Hintergrund zu bleiben - Starten/Stoppen des Standortmanagers in Ihrer Hintergrundaufgabe , wird zurücksetzen den Hintergrund Timer, wenn didUpdateToLocation: aufgerufen wird.

Ich weiß nicht, warum es funktioniert, aber ich denke, didUpdateToLocation wird auch als Task aufgerufen und setzt dadurch den Timer zurück.

Ich bin der Meinung, dass DataMan Pro diese Funktion verwendet.

Siehe diesen Beitrag https://stackoverflow.com/a/646528 woher ich Trick habe.

Hier sind einige Ergebnisse unserer App:

2012-02-06 15:21:01.520 **[1166:4027] BGTime left: 598.614497 
2012-02-06 15:21:02.897 **[1166:4027] BGTime left: 597.237567 
2012-02-06 15:21:04.106 **[1166:4027] BGTime left: 596.028215 
2012-02-06 15:21:05.306 **[1166:4027] BGTime left: 594.828474 
2012-02-06 15:21:06.515 **[1166:4027] BGTime left: 593.619191
2012-02-06 15:21:07.739 **[1166:4027] BGTime left: 592.395392 
2012-02-06 15:21:08.941 **[1166:4027] BGTime left: 591.193865 
2012-02-06 15:21:10.134 **[1166:4027] BGTime left: 590.001071
2012-02-06 15:21:11.339 **[1166:4027] BGTime left: 588.795573
2012-02-06 15:21:11.351 **[1166:707] startUpdatingLocation
2012-02-06 15:21:11.543 **[1166:707] didUpdateToLocation
2012-02-06 15:21:11.623 **[1166:707] stopUpdatingLocation
2012-02-06 15:21:13.050 **[1166:4027] BGTime left: 599.701993
2012-02-06 15:21:14.286 **[1166:4027] BGTime left: 598.465553
6
Kashif Shaikh

Ich habe das Update 2 ausprobiert, aber es funktioniert einfach nicht. Wenn der Ablaufhandler aufgerufen wird, wird die Hintergrundaufgabe beendet. Wenn Sie dann eine neue Hintergrundaufgabe starten, wird der Ablaufhandler sofort erneut aufgerufen (der Timer wird nicht zurückgesetzt und ist noch abgelaufen). So bekam ich 43 Starts/Stopps von Hintergrundaufgaben, bevor die App angehalten wurde.

1
iOSTester

Wenn es kein GPS ist, denke ich, ist die einzige andere Möglichkeit die Hintergrundmusik-Funktion, d. Beide klingen wie ein Missbrauch der APIs für die Hintergrundverarbeitung und sind daher möglicherweise den Launen des Überprüfungsprozesses unterworfen.

1

Dies ist eine ziemlich alte Frage, aber der richtige Weg, dies jetzt zu tun, ist über Background App Refresh , das vom Betriebssystem voll unterstützt wird und keine Hacks benötigt werden.

Beachten Sie, dass Sie immer noch dem Betriebssystem ausgeliefert sind, wann Ihre Hintergrundaktualisierung tatsächlich stattfinden wird. Sie können eine Mindestabrufzeit festlegen, es gibt jedoch keine Garantie, dass sie bei dieser Frequenz abgerufen wird, da das Betriebssystem seine eigene Logik anwendet, um die Akkulaufzeit und die Funknutzung zu optimieren.

Als Erstes müssen Sie Ihr Projekt so konfigurieren, dass die Hintergrundaktualisierung auf der Registerkarte Funktionen unterstützt wird. Dadurch werden Ihrem Info.plist die erforderlichen Schlüssel hinzugefügt:

 enter image description here 

Dann müssen Sie Ihrer AppDelegate ein paar Dinge hinzufügen, die sowohl URLSessionDelegate als auch URLSessionDownloadDelegate implementieren müssen:

private var _backgroundCompletionHandler: ((UIBackgroundFetchResult) -> Void)?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...

    application.setMinimumBackgroundFetchInterval(5 * 60) // set preferred minimum background poll time (no guarantee of this interval)

    ...
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    _backgroundCompletionHandler = completionHandler

    let sessionConfig = URLSessionConfiguration.background(withIdentifier: "com.yourapp.backgroundfetch")
    sessionConfig.sessionSendsLaunchEvents = true
    sessionConfig.isDiscretionary = true

    let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
    let task = session.downloadTask(with: url)
    task.resume()
}

func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
    NSLog("URLSession error: \(error.localizedDescription)")
    _backgroundCompletionHandler?(.failed)
    _backgroundCompletionHandler = nil
}

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    NSLog("didFinishDownloading \(downloadTask.countOfBytesReceived) bytes to \(location)")
    var result = UIBackgroundFetchResult.noData

    do {
        let data = try Data(contentsOf: location)
        let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)

        // process the fetched data and determine if there are new changes
        if changes {
            result = .newData

            // handle the changes here
        }
    } catch {
        result = .failed
        print("\(error.localizedDescription)")
    }

    _backgroundCompletionHandler?(result)
    _backgroundCompletionHandler = nil
}
0
devios1

in meinen Tests mit iOS5 habe ich festgestellt, dass es hilfreich ist, die Überwachung von CoreLocation über startMonitoringForLocationChangeEvents (nicht SignificantLocationChange) zu starten. Die Genauigkeit spielt keine Rolle. __ sinkt. 

0
dkzm