为什么在 Azure MobileServiceClient LoginAsync() 后 Navigation.PushAsync 会崩溃?

11

编辑:可以在此处找到演示崩溃的示例项目:https://github.com/rringham/brokenazurexamforms - 您需要在以下位置设置自己的Azure App Service URL:

  • src/BrokenAzureForms/Droid/Services/User/DroidUserService.cs
  • src/BrokenAzureForms/iOS/Services/User/IosUserService.cs

我注意到当我尝试在Azure MobileServiceClient验证后再使用它时,Xamarin Forms 的Navigation.PushAsync()在Android上崩溃。这个崩溃是独立于iOS的。

这是设置-我有一个基本的NavigationPage作为我的主应用程序页面:

MainPage = new NavigationPage(new LoginPage());

在我的LoginPage上,我使用一个通过DependencyService注入的类进行身份验证,在我的Android项目中执行身份验证:

private async void OnMicrosoftAccountTapped(object sender, EventArgs args)
{
    IUserService userService = DependencyService.Get<IUserService>();
    bool authenticated = await userService.LoginWithAzureAD();
    if (authenticated)
    {
        await Navigation.PushAsync(new HomePage(), false);
    }
 }

在我的Android IUserService 实现中,我执行以下操作(基本上与Azure / Xamarin Forms教程所示的相同):

public async Task<bool> LoginWithAzureAD()
{
    try
    {
        _user = await _client.LoginAsync(Xamarin.Forms.Forms.Context, MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory);
    }
    catch (Exception)
    {
        return false;
    }

    return true;
}

这里是问题发生的地方。当LoginWithAzureAD()完成时,控件将恢复到OnMicrosoftAccountTapped(); 然后我们调用Navigation.PushAsync(),应用程序会崩溃,并且提供的细节非常少:

MobileServiceClient / Navigation crash

我能想到的只有Azure MobileServiceClient在内部使用了一些相当奇怪的东西,因为如果我删除对await userService.LoginWithAzureAD()的调用,则对Navigation.PushAsync()的调用可以正常工作。 MobileServiceClient中的某些内容可能已经损坏,或者正在破坏Xamarin Forms中的某些内容。

有人看到过类似的情况吗?


嗨@Rob Ringham,你最终解决了这个问题吗?这正是我目前面临的问题,并且我已经将其归结为与你相同的调用。谢谢。 - Brett Rigby
2个回答

4

嗯,我绝对没有在后台线程中调用“PushAsync”——我的“LoginWithAzureAD”方法是从UI线程中的一个按钮点击事件处理程序中调用的,该处理程序在同一线程上调用了“MobileServiceClient”的“LoginAsync”方法。 - Rob
@RobRingham 嗯,听起来仍然是UI线程的问题。为了隔离问题,请尝试将PushAsync代码移动到UI并等待登录结果,看看异常是否会重现。 - lindydonna
我不确定我理解了 - 在等待成功返回登录结果之后,PushAsync 调用肯定是在 UI 线程上调用的。我甚至尝试通过 Device.BeginInvokeOnMainThread() 将对 PushAsync 的调用放在 UI 运行循环中的更低位置,但没有成功。MobileServiceClient 的某些工作方式似乎与 Xamarin Forms 在 Android 上的工作方式不兼容; 我怀疑这可能是 Xamarin Forms 本身的一个 bug。 - Rob

4
当我执行此操作时,我使用以下内容:
在MainActivity.cs文件中:
// Initialize for Azure Mobile Apps
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();

// Initialize for Xamarin Forms
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

// Initialize for Login Provider
var lp = (DroidLoginProvider)DependencyService.Get<ILoginProvider>();
lp.Initialize(this);

然后,在我的DroidLoginProvider类中,我执行以下操作:

Context _context;

public void Initialize(Context context)
{
    this._context = context;
}

public async Task LoginAsync(MobileServiceClient client)
{
    await client.LoginAsync(this._context, MobileServiceAuthenticationProvider.whatever);
}

我在共享项目中的单例包装器中调用LoginAsync。这是一个单例非常重要,因为在一个项目中只应该有一个MobileServiceClient - 认证存储在MobileServiceClient.CurrentUser属性中,并且仅在当前客户端上设置。
您可以在此处查看具有此逻辑的工作项目:https://github.com/adrianhall/30-days-of-zumo-v2/tree/master/file-upload

谢谢Adrian - 我已经尝试过了,具体来说是在MainActivity中添加了Azure Mobile Apps的初始化调用,并将Android Context缓存在我的登录提供程序中;我已经更新了Git repo中的代码,使其与您建议的功能等效,但不幸的是我仍然看到崩溃(我也遵循Singleton方法RE:MobileServiceClient)。我会试试你的示例项目,看看是否有效! - Rob
最后一句话 - 我在我的项目中使用的是Xamarin Forms v2.3.x。虽然有更新版本,但我在Android上编译时遇到了问题,无法得到正确的组合。 - Adrian Hall
仅供参考,我刚刚阅读了有关单例模式的内容,并在MSDN上找到了这样的描述:“然而,[Singleton]实现的主要缺点是它不适用于多线程环境。如果独立的执行线程同时进入Instance属性方法,则可能创建Singleton对象的多个实例。”(摘自https://msdn.microsoft.com/en-us/library/ff650316.aspx) - Brett Rigby
通常应确保getInstance()方法是同步的,即由lock()语句包围。请参阅http://csharpindepth.com/Articles/General/Singleton.aspx,了解有关C#中单例模式的深入文章。 - Adrian Hall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接