在Xamarin.Forms中,Device.BeginInvokeOnMainThread()在物理设备上的Release config中不显示来自通知回调* only *的消息框


69

我将现有的(快速)iOS物理治疗应用程序“ On My Nerves”重写为Xamarin.Forms。这是一个计时器应用程序,可帮助神经受损的人(如我!)进行脱敏锻炼。您拥有这些“面料”(例如羽毛),其中每个面料都有“ x”秒倒数。当结构的计时器到达0时,将出现一个消息框,显示“时间到”。用户单击确定,下一个结构开始其倒计时。冲洗并重复列表中的所有布料。这是显示工作流程视频。相信我在视频中的UX。

这是我的示例应用程序代码,演示了此行为

DoSomethingForNow方法(请原谅我的命名策略)是NotificationTime的回调(请参见第65行-没有足够的SO rep点用于直接链接),该回调是在计时器启动时创建的,以防应用获取背景。

在设备的“释放”模式下不起作用的特定呼叫在第115行

Device.BeginInvokeOnMainThread(
                async () => await ShowAlertAndWaitForUser(currentFabricCount));

async () => await ShowAlertAndWaitForUser(currentFabricCount))预期在调试和发布配置上的iOS模拟器,并在设备的调试配置的工作。

但是,指示时间已到的消息框不会出现在物理设备上的Release config中。我无法弄清楚为什么Device.BeginInvokeOnMainThread()在设备的Release config中不起作用。到底是怎么回事?

重用笔记 Device.StartTimer()

之所以我 早些时候改用James的FrenchPressTimer解决方案(请参阅jamesmontemagno / FrenchPressTimer),是因为我需要一种取消/停止/无论用户是否需要暂停或停止应用程序倒计时的方法。Device.StartTimer() Device.StartTimer()

代码对我在模拟器和设备上的所有配置都非常有效,并且值得庆幸的是有人向我展示了如何取消Device.StartTimer(谢谢!)。如果要查看此工作解决方案,请在GitHub上查看saraford / Device-StartTimer-Working-Xamarin。Device.StartTimer()

我的具体问题是,为什么Device.BeginInvokeOnMainThread()从物理设备上的Release config中的通知回调调用时不显示消息框。

我也尝试了不同的链接器组合。没有效果。


您是否尝试过其他平台(例如Android)?
马里奥·加尔万(MarioGalván)2017年

您可以添加更多日志记录DoSomethingForNow()吗?就像在IF / Else分支中一样。每个陈述一个。
spottedmahn

我为他们提供了最新的Xamarin(2.4.0.282)和iOS(11.0.1)的试用版。发布版本是否建立在物理iPhone 6上,除了工作版本中的取消超链接外,我没有看到应用程序行为之间的差异。
KnarfaLingus

5
@spottedmahn如何登录设备上的发行版?我希望能够比较iOS 11和iOS 10.3.3的日志,以查看增量。
萨拉福德'17

4
您可以尝试使用此库,看看是否可以解决您的问题:nuget.org/packages/Acr.UserDialogs
stiduck

Answers:


1

这是一个可行的答案,但是正如@JamesMallon所提到的,不要使用它:

Device.BeginInvokeOnMainThread(ShowAlertAndWaitForUser(currentFabricCount).Result);

在没有在Main / UI线程中运行代码的情况下,您的问题非常普遍。看来您是在主线程上开始调用的,但UI线程实际上并未读取该行,而另一个线程正在执行您需要的操作。这也是为什么它有时起作用而在其他时候不起作用的原因。

因此,与其在ShowAlertAndWaitForUser()UI线程上执行全部操作,不如仅运行DisplayAlert在该线程上函数。


4
不鼓励仅使用代码的答案。请添加一些有关如何解决问题或与现有答案有何不同的解释。来自评论
尼克,

1
嘿,谢谢@Nick提醒我!:)进行了更新
Saamer

2
这很危险。请任何人阅读,永远不要这样做!注释中没有足够的行,但请阅读以下内容:montemagno.com/c-sharp-developers-stop-calling-dot-result
James Mallon

@JamesMallon我同意。使用await ....;不要使用,.Result()除非绝对不可能通过添加async修饰符使封闭函数异步化
-Saamer

@saamer有志者事竟成,请看上面我的回答,以寻求一种Device.BeginInvokeOnMainThread等待的方式
James Mallon


0

我面临着类似的问题 Acr.UserDialogs在Android上显示“正在加载”屏幕时插件。

看来你的主线程中使用,而不是Device.BeginInvokeOnMainThread只是调用代码 await ShowAlertAndWaitForUser(currentFabricCount)通过使DoSomethingForNow一个async方法。

最好在viewmodel类中实现INotifyPropertyChanged,然后绑定它,而不是将所有内容都转储到后面的代码中。


0

因此,这里似乎存在一个普遍的误解。Device.BeginInvokeOnMainThread()是同步调用,这是这种情况下的问题。这是发生的情况的示例:

想象我们有一种方法LogOut。当用户要注销应用程序LogOut时,将从UI Thread中调用该方法。

async void LogOut() //Called on thread 5
{
    await ShowAlertAndWaitForUser(currentFabricCount); //pop up shows "You are about to be logged out"
    Console.WriteLine("User logged out");
}

好吧,我有一些坏消息要给您,因为已经从UI线程中取消了该对话框,该对话框不会显示给用户。

好吧,让我们尝试做您上面试图做的事情!

async void LogOut() //Called on thread 5
{
    Device.BeginInvokeOnMainThread(async () => await ShowAlertAndWaitForUser(currentFabricCount));
    Console.WriteLine("User logged out");
}

因此,既然我们在主线程上调用此方法并强制在主线程上调用它,那么肯定可以吗?

不幸的是,不,它不会。它不起作用的原因是您实际上没有ShowAlertAndWaitForUser直接打电话!您正在调用的Device.BeginOnMainThread对象将被调用,然后在其自身内部调用一个等待的调用,因此LogOut方法将继续进行调用,Console.WriteLine并且该调用将结束。这意味着没有时间显示警报,因为我们甚至没有正确等待呼叫!

如果这使您感到困惑,建议您在此处阅读我的异步编程说明。

太棒了,我们知道问题出在哪里,那么我们该如何解决呢?

有两种方法可以纠正此问题:

方法1(首选解决方案):

实际上,请确保在UI Thread上调用LogOut方法。我知道这听起来很简单,但实际上,我倾向于在编程时强制对UI线程进行调用,以尽可能地远离。有时这是完全必要的,但通常很多时候它是完全不必要的。在移动应用程序中,通常大多数方法是通过用户交互最初在更改中调用的。

在主线程上使用Device.Timer,大多数其他计时器在bg线程上调用方法

用户按下按钮->呼叫注销->显示对话

因为用户最初按下了按钮,所以此调用来自主线程,所以从此处进行的所有同步调用都发生在主线程上。

方法2(您将全部使用该方法,因为它是最快实现的方法):

因此,正如我上面所说,有时Device.BeginInvokeOnMainThread 必须使用。因此,对于这种情况,我编写了一个名为的小方法Device.BeginInvokeOnMainThreadAsync,该方法可以等待,这意味着您可以实际等待该方法,在Device.BeginInvokeOnMainThreadAsync该方法中,警报对话框将实际显示。所以这是代码。

public static Task<T> BeginInvokeOnMainThreadAsync<T>(Func<T> f)
{
    var tcs = new TaskCompletionSource<T>(); 
    Device.BeginInvokeOnMainThread(() =>
    {
        try
        {
            var result = f();
            tcs.SetResult(result);
        }
        catch(Exception ex)
        {
            tcs.SetException(ex);
        }
     }); 
     return tcs.Task;
}

这是您将如何使用它:

await BeginInvokeOnMainThreadAsync(async () => await ShowAlertAndWaitForUser(currentFabricCount));

因此,最后一个未解决的问题是,为什么它在模拟器的调试模式和释放模式下而不在设备的释放模式下起作用?

我的诚实回答是我真的不知道。我确实知道的一件事是不应该在调试模式下工作。我也知道调试和发布模式之间的编译过程存在很大差异。主要原因可能是释放模式和设备的CPU体系结构的结合。对于这个问题的答案,如果有人可以用一些专业知识发表评论,那就太好了吗?

希望这很清楚,可以帮助您。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.