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

时间:2017-09-29 15:08:56

标签: c# xamarin xamarin.forms

我正在重写我现有的(快速)iOS物理治疗应用程序" On My Nerves"到Xamarin.Forms。它是一个计时器应用程序,以帮助神经受损的人(像我!)做脱敏练习。你有这些"面料" (例如羽毛)每个织物都有一个' x'第二次倒计时。当织物计时器达到0时,会出现一个消息框,提示"时间已经过了"。用户点击OK,下一个结构开始倒计时。冲洗并重复列表中的所有织物。这是video showing the workflow。相信视频中的用户体验。

这是我的sample app code demonstrating this behavior

NotificationBuilder.addAction(null)方法(原谅我的命名策略)是来自NotificationService的回调(参见第65行 - 没有足够的直接链接的SO重复点),在计时器启动时创建,以防应用程序获取背景

在设备上的发布模式下无效的特定呼叫位于第115行

DoSomethingForNow

Device.BeginInvokeOnMainThread( async () => await ShowAlertAndWaitForUser(currentFabricCount)); 在iOS模拟器上的调试和发布配置以及设备上的调试配置中按预期工作。

但是,指示时间已启动的消息框未显示在物理设备上的“发布配置”中。我无法弄清楚为什么async () => await ShowAlertAndWaitForUser(currentFabricCount))在设备上的Release配置中不起作用。到底是怎么回事?

备注使用 Device.BeginInvokeOnMainThread()

我之前从 Device.StartTimer() 切换到James的FrenchPressTimer解决方案(请参阅jamesmontemagno / FrenchPressTimer)的原因是因为我需要一种方法来取消/停止/无论 Device.StartTimer() ,以防用户需要暂停或停止应用倒计时。

Device.StartTimer() 代码对我来说非常适合我在模拟器和设备上的所有配置,谢天谢地有人向我展示了如何取消设备。 StartTimer(谢谢!)。如果你想看到这个有用的解决方案,请在GitHub上查看saraford / Device-StartTimer-Working-Xamarin。

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

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

4 个答案:

答案 0 :(得分:0)

对于IOS 8,请使用警报控制器。

https://developer.xamarin.com/recipes/ios/standard_controls/alertcontroller/

var okAlertController = UIAlertController.Create ("OK Alert", "This is a 
sample alert with an OK button.", UIAlertControllerStyle.Alert);

答案 1 :(得分:0)

尝试:

AttributeError Traceback (most recent call last) <ipython-input-71-0e5ff4660e45> in <module> 3 zipcode = [] 4 ----> 5 test2 = gent_df['Name'] = gent_df['Name'].astype(str).translate({ord('ZCTA5'): None}).astype(int) 6 # zipcode.append(test2) 7 test2.head() ~\Anaconda3\envs\MyPyEnv\lib\site-packages\pandas\core\generic.py in __getattr__(self, name) 5178 if self._info_axis._can_hold_identifiers_and_holds_name(name): 5179 return self[name] -> 5180 return object.__getattribute__(self, name) 5181 5182 def __setattr__(self, name, value): AttributeError: 'Series' object has no attribute 'translate'

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

因此,不要在UI线程上执行整个Device.BeginInvokeOnMainThread(ShowAlertAndWaitForUser(currentFabricCount).Result);,而是尝试仅在该线程上运行ShowAlert函数。

答案 2 :(得分:0)

在Android上显示“正在加载”屏幕时,我遇到了Acr.UserDialogs插件遇到的类似问题。

似乎您的主线程正在使用中,而不是Device.BeginInvokeOnMainThread,而是通过将await ShowAlertAndWaitForUser(currentFabricCount)设为DoSomethingForNow方法来使用async来调用代码。

在视图模型类中实现INotifyPropertyChanged然后绑定它,而不是将所有内容都转储到代码中,这是一个好主意。

答案 3 :(得分:0)

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

想象一下,我们有一个方法LogOut。当用户要注销应用程序时,将在 UI线程中退出方法LogOut

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,并且该调用将结束。这意味着没有时间显示警报,因为我们甚至没有正确等待呼叫!

如果这使您感到困惑,建议您阅读我对异步编程here的解释。

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

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

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

实际上,请确保在 UI线程上调用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体系结构的结合。对于这个问题的答案,如果有人可以用一些专业知识发表评论,那将是非常好的事情?

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