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

iOS - Running a network request in the background with app closed

您好,我目前正在开发跨平台(本机 Java 和 Swift/Objective-C)移动应用程序,用户可能经常访问网络连接受限,但我们想记录并在网络可用时发送统计信息,即使我们的应用程序已关闭。我们已经使用他们的 AndroidX.WorkManager 库在 Android 上相当轻松地完成了此操作,如下所示:

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“背景”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 args)传递给我的后台任务,因为我的任务必须在应用程序启动时注册?

谢谢你,罗兰 K

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

这很重要,因为当应用程序被终止时,您无法在后台执行任何操作。只有 VoIP 应用程序例外(使用 PushKit,如果应用程序没有 VoIP 功能,你现在可以使用它,你不会'无法通过 AppStore) 和 watchOS 并发症。但是对于所有其他应用程序,当应用程序被终止时,您根本没有任何选择。这只是 Apple 的标准,出于安全原因,他们不希望非 运行 的应用能够 运行 编码。

您可以注册一些要完成的任务,当应用程序仍在后台时,使用您提到的 BGTask API。我对我的一个应用程序使用了相同的 API,发现它非常不稳定。有时一个任务每30分钟完成一次,然后直到明天早上才执行,然后有一次根本不执行。

因此我认为 静默推送通知 是最好的出路,但它们也只有在应用程序仍在后台而不是被杀死时才有效。除非您的应用程序具有 VoIP 功能,在这种情况下您可以使用 PushKit。