iOS - 在应用关闭后在后台运行网络请求

3

我目前正在开发一款跨平台(使用原生Java和Swift/Objective-C)移动应用,用户可能经常面临网络连接受限的情况,但我们希望在网络恢复连接时记录并发送统计数据,即使我们的应用已关闭。我们已经在Android上使用AndroidX.WorkManager库轻松完成了这项工作,如下所示:

String URL = "https://myexampleurl.com";
Data inputData = new Data.Builder()
    .putString("URL", URL)
    .build();

Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();

OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(NetworkWorker.class)
    .setInputData(inputData)
    .setConstraints(constraints)
    .build();

WorkManager.getInstance(context).enqueueUniqueWork("com.lkuich.exampleWorker", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest);

这很好用,当我们的应用程序调用此方法时,我们的请求将被添加到队列中,即使用户离线并关闭了我们的应用程序,当他们重新连接到网络时,我们的请求仍然会被执行。
然而,我一直在寻找一个类似于这个API的iOS等效物,而且我对本地iOS开发还比较新。我们已经启用了后台任务功能,并在我们的项目中注册了我们的标识符Plist.info
首先,我们尝试了URLSession“background” API,代码如下:
class NetworkController: NSObject, URLSessionTaskDelegate {
    func buildTask() -> URLSession {
        let config = URLSessionConfiguration.background(withIdentifier: "com.lkuich.exampleWorker")
        config.waitsForConnectivity = true
        config.shouldUseExtendedBackgroundIdleMode = true
        
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
    }
    
    func makeNetworkRequest(session: URLSession) {
        let url = URL(string: "https://myexampleurl.com")!
        
        let task = session.dataTask(with: url)
        task.resume()
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        print("We're done here")
    }
}

当WiFi被禁用并重新启用时,这很有效,但似乎如果应用程序关闭,则请求永远不会被发送。

因此,我们尝试使用BGTaskScheduler,但我很难测试它是否在应用程序关闭后在后台工作,因为它通常需要数小时才能实际运行(通过网络连接立即运行它可以起到作用):

static let bgIdentifier = "com.lkuich.exampleWorker"

// Invoked on app start (didFinishLaunchingWithOptions)
static func registerBackgroundTasks() {
    BGTaskScheduler.shared.register(forTaskWithIdentifier: bgIdentifier, using: nil) { (task) in        
        task.expirationHandler = {
            task.setTaskCompleted(success: false)
        }
        
        // Make my network request in BG
        let url = URL(string: "https://myexampleworker.com")!
        let t = URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
            // Print the response
            print(response)

            task.setTaskCompleted(success: true)
        })
        t.resume()
    }
}

static func makeRequest() {
    do {
        let bgRequest = BGProcessingTaskRequest(identifier: bgIdentifier)
            bgRequest.requiresNetworkConnectivity = true
            bgRequest.requiresExternalPower = false
            
        try BGTaskScheduler.shared.submit(bgRequest)
        print("Submitted task request")
    } catch {
        print("Failed to submit BGTask")
    }
}

当网络可用时,即使调用应用程序关闭,是否可以安排一个网络请求在后台运行?如果可以的话,我该如何传递输入数据(例如URL参数)给我的后台任务,因为我的任务必须在应用程序启动时注册?

1个回答

1

一个关闭的应用有两种可能的状态。它要么在后台,要么被终止。应用程序可以由系统出于各种原因或用户从应用切换器中终止。

这很重要,因为当应用程序被终止时,您无法在后台执行任何操作。只有VoIP应用程序是例外(使用PushKit),如果应用程序没有VoIP功能,则不允许使用,否则将无法通过AppStore),以及watchOS的复杂情况。但对于所有其他应用程序,当应用程序被终止时,您根本没有任何选项。这只是苹果的标准,他们不希望没有运行的应用程序能够运行代码,出于安全原因。

您可以使用您提到的BGTask API注册一些任务,当应用程序仍在后台时完成。我也用过同样的API来制作我的一个应用程序,并发现它非常不稳定。有时每30分钟就会完成一项任务,然后直到明天早上才不会执行,有时甚至完全停止执行。

因此,我认为静默推送通知是最好的解决方法,但它们仅在应用程序仍处于后台状态时有效,而不是被杀死。除非您的应用程序具有VoIP功能,在这种情况下,您可以使用PushKit。

谢谢你的回复,安德烈。我想选择URLSession方法(URLSessionConfiguration.background),但似乎它实际上从未在后台运行过。这是否会遇到与使用BGTaskScheduler创建的后台任务相同的调度问题? - Loren Kuich
你不能单独调度URLSession,它必须作为BGTask的一部分才能被安排在后台执行。而且它必须具有URLSessionConfiguration.background。我遇到了一个问题,即后台执行会在随机行暂停,直到下一次调用。所以下次系统在后台运行应用程序时,执行将从停止的地方继续。很奇怪,无法弄清楚为什么会发生这种情况。 - andrija

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接