Background task in iOS action extension

眉间皱痕 提交于 2020-04-08 08:54:06

问题


I'm working on an action extension for an app which prepares some data for the user and then uses MailCore2 to send this information to a SMTP server. Preparing data is done very fast, but sending the email might take some time (depending on its size). That's why I'm looking for a way to handle the sending activities in the background. But this results in some problems with different solutions:

  1. Using URLSession. This is the first way to go for handling big uploads or downloads in an iOS extension. But the problem is that MailCore2 does not use URLSessions to transmit the data to the mail server. So this is not usable. Or is it possible to somehow 'wrap' this call in an URLSession?

  2. Using UNUserNotificationCenter and send a local notification from the extension after data preparation. The idea is to start the sending task in the main app after receiving this notification. Problem: the notifications userNotificationCenter didReceive method is only called, when the user clicks the notification. A simple badge notification does not call the delegate method.

  3. Using Background Fetch. This is working fine in the simulator, but on an iOS device there is an error when connecting to the SMTP server. I came across the same HELO problem as described in github.com/MailCore/mailcore2/issues/252. For this issue, a solution should be MCOSMTPSession.isUseHeloIPEnabled = true, but this might not work for all servers. So also not a perfect solution.

Any ideas, how to handle such a task? Or how to deal with one of the solutions mentioned above?

Edit: as this question does not receive any answers yet: what is unclear or which additional information is required?


回答1:


It seems like you can't schedule long running running tasks in background from an extension.Refer here(Some APIs Are Unavailable to App Extensions)

Among the solutions you suggested,my suggestion would be to try using silent push notifications ,you can call an api at the preparing data stage,then sent a silent push from server and perform the background task when the silent push arrives.




回答2:


Judging from your response, your difficulties is that you need to allow background activities. You can do that by following a tutorial such as this one.




回答3:


The most useful solution is using push notifications. The solution I used in my own application:

  1. Create a simple server which can retrieve a simple request: device token and time when you'd like to wake up your device.
  2. When the application goes to background mode send the request to the server to wake up the device later. E.g. 5 minutes/20 minutes, etc. func applicationWillEnterForeground(_ application: UIApplication) { inside AppDelegate.
  3. Don't forget to allow the application to work in the background as much as possible.

For example,

   @UIApplicationMain
    class AppDelegate: UIResponder {
    var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    func application(_ application: UIApplication,
                         didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                         fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            if UIApplication.shared.applicationState != .active {
                doFetch(completionHandler: completionHandler)
            } else {
                // foreground here
                completionHandler(UIBackgroundFetchResult.noData)
            }
        }
    func application(_ application: UIApplication,
                         performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            doFetch(completionHandler: completionHandler)
        }
    private func doFetch(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    sendTheRequestToWakeApp()
    backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
                self?.endBackgroundTask()
            }
/// do work here
    completionHandler(UIBackgroundFetchResult.noData)
    }
    private func endBackgroundTask() {
            print("Background task ended.")
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = UIBackgroundTaskInvalid
        }
    private func sendTheRequestToWakeApp() {
    /// Implement request using native library or Alamofire. etc.
    }
    }

on server side use simple time or loop.

Disadvantages,

  1. Internet is required
  2. It doesn't perfectly work. When battery is low the background mode is restricted.

Don't forget to setup the project:




回答4:


After many tests and failures, I found the following solution to execute a long performing task in the background of an extension. This works as expected, even if the extension is already finished:

func performTask()
{
    // Perform the task in background.
    let processinfo = ProcessInfo()
    processinfo.performExpiringActivity(withReason: "Long task") { (expired) in
        if (!expired) {
            // Run task synchronously.
            self.performLongTask()
        }
        else {
            // Cancel task.
            self.cancelLongTask()
        }
    }
}

This code uses ProcessInfo.performExpiringActivity() to execute the task in another thread. It’s important that the task in performLongTask() is executed synchronously. When the end of the block is reached, the thread will terminate and end the execution of your task.

A similar approach is also working in the main app. It's described in detail in a small summary for background tasks in iOS.



来源:https://stackoverflow.com/questions/50693101/background-task-in-ios-action-extension

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!