问题描述
我们正在开发一个 p#
应用,该应用应该在后台将照片上传到 API。该应用是根据客户的要求为客户定制的,因此他们会将手机设置为需要设置的任何权限。
如果插入充电线,下面的工作正常。
我正在使用 Xamarin Forms
(iOS13+) 并对两种类型的任务(BGTaskScheduler
和 BGProcessingTaskRequest
)进行排队,以便在插入电缆时会触发 BGAppRefreshTaskRequest
和如果不是,它将等待 BGProcessingTaskRequest
获得其处理时间。
我已将 BGAppRefreshTaskRequest
和 RefreshTaskId
添加到 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}");
}
}
}