web-dev-qa-db-ja.com

iPhone-イベントのポーリングの背景

かなり前から、iPhoneアプリでX分ごとにポーリングしてデータカウンターをチェックする方法を探していました。バックグラウンド実行のドキュメントといくつかのトライアルアプリをよく読んだ後、バックグラウンドAPIを悪用しない限りこれを不可能だと却下しました。

先週、私はまさにそれを行うこのアプリケーションを見つけました。 http://iTunes.Apple.com/us/app/dataman-real-time-data-usage/id393282873?mt=8

バックグラウンドで実行され、使用したCellular/WiFiデータの数を追跡します。開発者は自分のアプリを位置の変化の追跡として登録しているのではないかと疑っていますが、アプリの実行中は位置情報サービスのアイコンは表示されません。

誰がこれをどのように達成できるかについての手がかりはありますか?

66
NeilInglis

私もこの動作を見ました。たくさん試した後、私は2つのことがわかりました。しかし、これがレビュープロセスにどのように影響するかはまだわかりません。

バックグラウンド機能のいずれかを使用する場合、アプリは(システムによって)終了すると、iOSによってバックグラウンドで再度起動されます。これは後で悪用します。

私の場合、plistで有効になっているVoIPバックグラウンドを使用しました。ここのすべてのコードは、AppDelegateで実行されます。

// 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];
    }
}

そして登録はで行われます

- (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");
    }
}

今、魔法が起こります:私はVoIPソケットさえ使用しません。しかし、この10分間のコールバックには素晴らしい副作用があります。10分間後(時には以前)、タイマーと以前の実行中のトレッドが短時間実行されていることがわかりました。 NSLog(..)をコードに配置すると、これを見ることができます。これは、この短い「ウェイクアップ」がしばらくの間コードを実行することを意味します。 Appleによると、実行時間は30秒残っています。スレッドのようなバックグラウンドコードは30秒近く実行されていると思います。これは何かをチェックする必要がある場合に便利なコードです。

ドキュメントには、アプリが終了すると、すべてのバックグラウンドタスク(VoIP、オーディオ、位置情報の更新)がバックグラウンドで自動的に再起動されると書かれています。 VoIPアプリは、起動後に自動的にバックグラウンドで開始されます!

この動作を悪用すると、アプリを「永久に」実行しているように見せることができます。 1つのバックグラウンドプロセス(VoIPなど)に登録します。これにより、終了後にアプリが再起動されます。

次に、「タスクを完了する必要があります」コードを記述します。 Appleによると、タスクを完了するための時間(5秒?)があります。これはCPU時間でなければならないことがわかりました。つまり、何もしなければ、アプリはまだ動作しています。 execute!Apple作業が終了したら、expirationhandlerを呼び出すことをお勧めします。以下のコードでは、expirationHandlerにコメントがあることがわかります。これにより、アプリがシステムがアプリの実行を許可している限り、すべてのタイマーとスレッドは、iOSがアプリを終了するまで実行され続けます。

- (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;

    }); 
}

ここでCPU時間に余裕を持たせてください。そうすれば、アプリの実行時間が長くなります。ただし、確実なことは、しばらくするとアプリが終了することです。ただし、アプリをVoIPまたは他のいずれかとして登録したため、システムはアプリをバックグラウンドで再起動し、バックグラウンドプロセスを再起動します;-)このPingPongを使用すると、多くのバックグラウンド処理を実行できます。ただし、CPU時間に余裕があることを忘れないでください。ビューを復元するためにすべてのデータを保存します-アプリはしばらくして終了します。まだ実行されているように表示するには、ウェイクアップ後に最後の「状態」に戻る必要があります。

これがあなたが前に言及したアプリのアプローチであるかどうかはわかりませんが、私にとってはうまくいきます。

私が助けてくれることを願っています

更新:

BGタスクの時間を測定した後、驚きがありました。 BGタスクは600秒に制限されています。これは、VoIP最小時間の正確な最小時間です(setKeepAliveTimeout:600)。

したがって、このコードはバックグラウンドで「無限」に実行されます。

ヘッダ:

UIBackgroundTaskIdentifier bgTask; 

コード:

// 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);
        }    
    }); 
}

アプリがタイムアウトした後、VoIP expirationHandlerが呼び出されます。ここでは、長時間実行されているタスクを単純に再起動します。このタスクは600秒後に終了します。しかし、別の長時間実行タスクなどを開始する有効期限ハンドラーへの呼び出しが再びあります。これで、アプリがフォアグラウンドに戻っている天気を確認するだけです。次に、bgTaskを閉じて完了です。たぶん人はできることがあります。長時間実行タスクのexpirationHandler内でこのようにします。試してみてください。コンソールを使用して、何が起こるかを確認してください...楽しんでください!

更新2:

物事を単純化すると役立つ場合があります。私の新しいアプローチはこれです:

- (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     
    }); 
}

これは、VoIPハッキングなしで動作しています。ドキュメントによると、実行時間が終了すると、有効期限ハンドラー(この場合は「expirationHandler」ブロック)が実行されます。ブロックをブロック変数に定義することにより、有効期限ハンドラー内で長時間実行タスクを再度再帰的に開始できます。これも無限の実行につながります。

アプリケーションが再びフォアグラウンドに入る場合、タスクを終了することに注意してください。不要になったらタスクを終了します。

私自身の経験のために、私は何かを測定しました。 GPSラジオをオンにしてロケーションコールバックを使用すると、バッテリーが急速に消費されます。 Update 2で投稿したアプローチを使用しても、ほとんどエネルギーを消費しません。 「userexperience」によると、これはより良いアプローチです。たぶん他のアプリはこのように動作し、その動作をGPS機能の背後に隠します...

91
JackPearse

動作するものと動作しないもの

これらの回答のどれが機能するかは完全には明らかではありません。それらすべてを試すのに多くの時間を費やしました。だから、各戦略での私の経験は次のとおりです。

  1. VOIPハック-動作しますが、will VOIPアプリでない場合は拒否されます
  2. 再帰beginBackgroundTask... - 動作しません。 10分後に終了します。コメント(少なくとも2012年11月30日までのコメント)の修正を試みても。
  3. サイレントオーディオ-動作しますが、これに対して人々は拒否されました
  4. ローカル/プッシュ通知-アプリが起動される前にユーザーの操作が必要です
  5. バックグラウンドロケーションの使用-作品。詳細は次のとおりです。

基本的に、「ロケーション」バックグラウンドモードを使用して、アプリをバックグラウンドで実行し続けます。ユーザーが場所の更新を許可していなくても機能します。ユーザーがホームボタンを押して別のアプリを起動しても、アプリは実行されたままです。アプリは場所に関係ない場合、バッテリーの消耗品でもあり、承認プロセスの範囲を広げる可能性がありますが、私が知る限り、承認される可能性が高い唯一のソリューションです。

仕組みは次のとおりです

Plistセットで:

  • アプリケーションはバックグラウンドで実行されません:NO
  • 必要なバックグラウンドモード:場所

次に、(ビルドフェーズで)CoreLocationフレームワークを参照し、アプリのどこかにこのコードを追加します(バックグラウンドに移行する前に)。

#import <CoreLocation/CoreLocation.h>

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

注:startMonitoringSignificantLocationChangesnot動作します。

また、アプリがクラッシュした場合、iOSはアプリを復活させません。 VOIPハックは、それを取り戻すことができる唯一のハックです。

17
bendytree

バックグラウンドで永久に滞在する別の手法があります-バックグラウンドタスクでロケーションマネージャーを起動/停止しますresetdidUpdateToLocation:が呼び出されたときのバックグラウンドタイマー。

なぜ機能するのかはわかりませんが、didUpdateToLocationはタスクとしても呼び出され、それによってタイマーがリセットされると思います。

テストに基づいて、これがDataMan Proが使用しているものだと思います。

この投稿を参照してください https://stackoverflow.com/a/646528 ここで私はトリックを得ました。

アプリからの結果は次のとおりです。

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

Update 2を試しましたが、機能しません。期限切れハンドラーが呼び出されると、バックグラウンドタスクが終了します。次に、新しいバックグラウンドタスクを開始すると、ただちに強制終了ハンドラーの呼び出しが強制されます(タイマーはリセットされず、期限切れのままです)。したがって、アプリが中断される前に、43のバックグラウンドタスクの開始/停止がありました。

1
iOSTester

GPS以外の場合は、バックグラウンドミュージック機能、つまり有効になっている間は常に4 "33"を再生する方法しかないと思います。どちらも、バックグラウンド処理APIの悪用のように聞こえるので、レビュープロセスの気まぐれにさらされる可能性があります。

1

これはかなり古い質問ですが、これを行う正しい方法は Background App Refresh を使用することです。これはOSによって完全にサポートされており、ハックは必要ありません。

バックグラウンドリフレッシュが実際にいつ行われるかについては、OSに左右されることに注意してください。最小ポーリング時間を設定できますが、OSが独自のロジックを適用してバッテリー寿命と無線使用を最適化するため、その頻度で呼び出される保証はありません。

最初に行う必要があるのは、[機能]タブでバックグラウンド更新をサポートするようにプロジェクトを構成することです。これにより、必要なキーがInfo.plistに追加されます。

enter image description here

次に、AppDelegateにいくつかのものを追加する必要があります。これには、URLSessionDelegateURLSessionDownloadDelegateの両方を実装する必要があります。

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

iOS5でのテストでは、startMonitoringForLocationChangeEvents(SignificantLocationChangeではなく)を介してCoreLocationの監視を開始すると役立つことがわかりました。精度は重要ではありません。

0
dkzm