在 iOS 14.4 上拔下 iOS 充电器电缆时,不会执行到 BGTaskScheduler 内的 AzureFunction 的 NSUrlSession 照片上传任务

问题描述

我们正在开发一个 p# 应用,该应用应该在后台将照片上传到 API。该应用是根据客户的要求为客户定制的,因此他们会将手机设置为需要设置的任何权限。

如果插入充电线,下面的工作正常。

我正在使用 Xamarin Forms (iOS13+) 并对两种类型的任务(BGTaskSchedulerBGProcessingTaskRequest)进行排队,以便在插入电缆时会触发 BGAppRefreshTaskRequest 和如果不是,它将等待 BGProcessingTaskRequest 获得其处理时间。

我已将 BGAppRefreshTaskRequestRefreshTaskId 添加UploadTaskId

Info.plist 在 iOS 项目中看起来如下

AppDelegate.cs

上传 public override bool FinishedLaunching(UIApplication app,NSDictionary options) { global::Xamarin.Forms.Forms.Init(); LoadApplication(new App()); BGTaskScheduler.Shared.Register(UploadTaskId,null,task => HandleUpload(task as BGProcessingTask)); BGTaskScheduler.Shared.Register(RefreshTaskId,task => HandleAppRefresh(task as BGAppRefreshTask)); return base.FinishedLaunching(app,options); } public override void HandleEventsForBackgroundUrl(UIApplication application,string sessionIdentifier,Action completionHandler) { Console.WriteLine("HandleEventsForBackgroundUrl"); BackgroundSessionCompletionHandler = completionHandler; } public override void OnActivated(UIApplication application) { Console.WriteLine("OnActivated"); } public override void OnResignActivation(UIApplication application) { Console.WriteLine("OnResignActivation"); } private void HandleAppRefresh(BGAppRefreshTask task) { HandleUpload(task); } public override void DidEnterBackground(UIApplication application) { ScheduleUpload(); } private void HandleUpload(BGTask task) { var uploadService = new UploadService(); uploadService.EnqueueUpload(); task.SetTaskCompleted(true); } private void ScheduleUpload() { var upload = new BGProcessingTaskRequest(UploadTaskId) { RequiresnetworkConnectivity = true,RequiresExternalPower = false }; BGTaskScheduler.Shared.Submit(upload,out NSError error); var refresh = new BGAppRefreshTaskRequest(RefreshTaskId); BGTaskScheduler.Shared.Submit(refresh,out NSError refreshError); if (error != null) Console.WriteLine($"Could not schedule BGProcessingTask: {error}"); if (refreshError != null) Console.WriteLine($"Could not schedule BGAppRefreshTask: {refreshError}"); } 的机制是使用UploadService,它还写了一个临时文件来使用应该在后台工作的NSUrlSession,整个机制如下:

CreateUploadTask(request,NSUrl.FromFilename(tempFileName))

如果 iOS 充电器电缆已插入,则一切正常,但是,如果不是,则不会发生任何故障。我有一个网络调试设置,并有大量登录到控制台,我可以看到 iPhone 上没有任何反应。

iOS 上的“低功耗模式”设置已关闭

我已经观看了 Background execution demystified 并且我正在设置会话 public NSUrlSession uploadSession; public async void EnqueueUpload() { var accountsTask = await App.PCA.GetAccountsAsync(); var authResult = await App.PCA.AcquiretokenSilent(App.Scopes,accountsTask.First()) .ExecuteAsync(); if (uploadSession == null) uploadSession = InitBackgroundSession(authResult.Accesstoken); var datastore = DependencyService.Get<IDataStore<Upload>>(); var uploads = await datastore.GetUnuploaded(); foreach (var unUploaded in uploads) { try { string folder = unUploaded.Description; string subfolder = unUploaded.Category; if (string.IsNullOrEmpty(folder) || string.IsNullOrEmpty(subfolder)) continue; var uploadDto = new Dtos.Upload { FolderName = folder,SubFolderName = subfolder,Image = GetimageAsBase64(unUploaded.ImagePath) }; var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var fileName = Path.GetFileName(unUploaded.ImagePath); var tempFileName = Path.Combine(documents,$"{fileName}.txt"); string stringContent = await new StringContent(JsonConvert.SerializeObject(uploadDto),Encoding.UTF8,"application/json").ReadAsstringAsync(); await File.WriteallTextAsync(tempFileName,stringContent); using (var url = NSUrl.FromString(UploadUrlString)) using (var request = new NSMutableurlRequest(url) { HttpMethod = "POST",}) { request.Headers.SetValueForKey(NSObject.FromObject("application/json"),new Nsstring("Content-type")); try { uploadSession.CreateUploadTask(request,NSUrl.FromFilename(tempFileName)); } catch (Exception e) { Console.WriteLine($"NSMutableurlRequest Failed {e.Message}"); } } } catch (Exception e) { if (e.Message.Contains("Could not find a part of the path")) { await datastore.DeleteItemAsync(unUploaded.Id); Console.WriteLine($"deleted"); } Console.WriteLine($"uploadStore Failed {e.Message}"); } } } private string GetimageAsBase64(string path) { using (var reader = new StreamReader(path)) using (MemoryStream ms = new MemoryStream()) { reader.BaseStream.copyTo(ms); return Convert.ToBase64String(ms.ToArray()); } } public NSUrlSession InitBackgroundSession(string authToken = null,IDataStore<Upload> dataStore = null) { Console.WriteLine("InitBackgroundSession"); using (var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(Identifier)) { configuration.AllowsCellularaccess = true; configuration.discretionary = false; configuration.AllowsConstrainednetworkAccess = true; configuration.AllowsExpensiveNetworkAccess = true; if (string.IsNullOrWhiteSpace(authToken) == false) { configuration.HttpAdditionalHeaders = NSDictionary.FromObjectsAndKeys(new string[] { $"Bearer {authToken}" },new string[] { "Authorization" }); } return NSUrlSession.FromConfiguration(configuration,new UploadDelegate(dataStore),null); } } } public class UploadDelegate : NSUrlSessionTaskDelegate,INSUrlSessionDelegate { public IDataStore<Upload> Datastore { get; } public UploadDelegate(IDataStore<Upload> datastore) { this.Datastore = datastore; } public override void DidCompleteWithError(NSUrlSession session,NSUrlSessionTask task,NSError error) { Console.WriteLine(string.Format("DidCompleteWithError TaskId: {0}{1}",task.TaskIdentifier,(error == null ? "" : " Error: " + error.Description))); if (error == null) { ProcessCompletedTask(task); } } public void ProcessCompletedTask(NSUrlSessionTask sessionTask) { try { Console.WriteLine(string.Format("Task ID: {0},State: {1},Response: {2}",sessionTask.TaskIdentifier,sessionTask.State,sessionTask.Response)); if (sessionTask.Response == null || sessionTask.Response.ToString() == "") { Console.WriteLine("ProcessCompletedTask no response..."); } else { var resp = (NSHttpUrlResponse)sessionTask.Response; Console.WriteLine("ProcessCompletedTask got response..."); if (sessionTask.State == NSUrlSessionTaskState.Completed && resp.StatusCode == 201) { Console.WriteLine("201"); } } } catch (Exception ex) { Console.WriteLine("ProcessCompletedTask Ex: {0}",ex.Message); } } public override void DidBecomeInvalid(NSUrlSession session,NSError error) { Console.WriteLine("DidBecomeInvalid" + (error == null ? "undefined" : error.Description)); } public override void DidFinishEventsForBackgroundSession(NSUrlSession session) { Console.WriteLine("DidFinishEventsForBackgroundSession"); } public override void DidSendBodyData(NSUrlSession session,long bytesSent,long totalBytesSent,long totalBytesExpectedToSend) { } }

在 iOS 14.4 上拔下 iOS 充电器电缆时,如何使 configuration.discretionary = false; 上传任务触发?

解决方法

以下不带充电线的工作:

public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
   
    public Action BackgroundSessionCompletionHandler { get; set; }

    public static string UploadTaskId { get; } = "XXX.upload";
    public static NSString UploadSuccessNotificationName { get; } = new NSString($"{UploadTaskId}.success");
    public static string RefreshTaskId { get; } = "XXX.refresh";
    public static NSString RefreshSuccessNotificationName { get; } = new NSString($"{RefreshTaskId}.success");

    public override bool FinishedLaunching(UIApplication app,NSDictionary options)
    {
        global::Xamarin.Forms.Forms.Init();
        LoadApplication(new App());

        BGTaskScheduler.Shared.Register(UploadTaskId,null,task => HandleUpload(task as BGProcessingTask));
        BGTaskScheduler.Shared.Register(RefreshTaskId,task => HandleAppRefresh(task as BGAppRefreshTask));

        return base.FinishedLaunching(app,options);
    }

    public override bool OpenUrl(UIApplication app,NSUrl url,NSDictionary options)
    {
        AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
        return true;
    }

    public override void HandleEventsForBackgroundUrl(UIApplication application,string sessionIdentifier,Action completionHandler)
    {
        Console.WriteLine("HandleEventsForBackgroundUrl");
        BackgroundSessionCompletionHandler = completionHandler;
    }

    public override void OnActivated(UIApplication application)
    {
        Console.WriteLine("OnActivated");
        var uploadService = new UploadService();
        uploadService.EnqueueUpload();
    }

    public override void OnResignActivation(UIApplication application)
    {
        Console.WriteLine("OnResignActivation");
    }

    private void HandleAppRefresh(BGAppRefreshTask task)
    {
        task.ExpirationHandler = () =>
        {
            Console.WriteLine("BGAppRefreshTask ExpirationHandler");

            var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
            BGTaskScheduler.Shared.Submit(refresh,out NSError refreshError);

            if (refreshError != null)
                Console.WriteLine($"BGAppRefreshTask ExpirationHandler Could not schedule BGAppRefreshTask: {refreshError}");
        };

        HandleUpload(task);
    }

    public override void DidEnterBackground(UIApplication application) => ScheduleUpload();

    private void HandleUpload(BGTask task)
    {
        Console.WriteLine("HandleUpload");
        var uploadService = new UploadService();
        uploadService.EnqueueUpload();
        task.SetTaskCompleted(true);
    }

    private void ScheduleUpload()
    {
        Console.WriteLine("ScheduleUpload");
        var upload = new BGProcessingTaskRequest(UploadTaskId)
        {
            RequiresNetworkConnectivity = true,RequiresExternalPower = false
        };

        BGTaskScheduler.Shared.Submit(upload,out NSError error);

        var refresh = new BGAppRefreshTaskRequest(RefreshTaskId);
        BGTaskScheduler.Shared.Submit(refresh,out NSError refreshError);

        if (error != null)
            Console.WriteLine($"Could not schedule BGProcessingTask: {error}");
        if (refreshError != null)
            Console.WriteLine($"Could not schedule BGAppRefreshTask: {refreshError}");
    }
}

然后上传服务:

public class UploadService : IUploadService
{
    private const string uploadUrlString = "https://Yadyyadyyada";

    public async void EnqueueUpload()
    {
        var accountsTask = await App.PCA.GetAccountsAsync();
        var authResult = await App.PCA.AcquireTokenSilent(App.Scopes,accountsTask.First())
                                      .ExecuteAsync();

                try
                {
                    var uploadDto = new object();

                    var message = new HttpRequestMessage(HttpMethod.Post,uploadUrlString);
                    message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",authResult.AccessToken);
                    message.Content = new StringContent(JsonConvert.SerializeObject(uploadDto),Encoding.UTF8,"application/json");

                    var response = await httpClient.SendAsync(message);
                    if (response.IsSuccessStatusCode)
                    {
                        var json = await response.Content.ReadAsStringAsync();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine($"EnqueueUpload {e.Message}");
                }
    }
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...