MAUI中使用AppShell FlyoutHeader与IsBusy实现全局ActivityIndicator崩溃问题
解决方案:全局ActivityIndicator设置IsBusy崩溃问题
问题根源分析
崩溃大概率由两个原因导致:
- UI线程访问异常:虽然
await后理论上会回到UI线程,但数据库异步操作可能导致同步上下文丢失,直接设置Shell.Current.IsBusy时不在UI线程,触发跨线程访问UI控件的异常。 - BindingContext不匹配:FlyoutHeader的默认BindingContext并非Shell实例,导致绑定的
IsBusy属性未正确关联到Shell的IsBusy,引发绑定更新时的异常。
方案1:强制在UI线程更新IsBusy
无论异步操作上下文如何,通过MainThread确保UI属性的更新在主线程执行:
public async static Task<List<T>> GetAsync<T>(Expression<Func<T, bool>>? predicate = null, bool includeActiveOnly = true) where T : BaseModel, new() { // 确保在UI线程设置IsBusy为true MainThread.BeginInvokeOnMainThread(() => { if (Shell.Current != null) Shell.Current.IsBusy = true; }); var query = DbContext.Table<T>(); if (includeActiveOnly) query = query.Where(x => x.IsActive == true); if (predicate != null) query = query.Where(predicate); query = query.OrderByDescending(x => x.DateAdded); var list = await query.ToListAsync(); // 同样在UI线程设置IsBusy为false MainThread.BeginInvokeOnMainThread(() => { if (Shell.Current != null) Shell.Current.IsBusy = false; }); return list; }
同时修正FlyoutHeader的绑定,确保关联到Shell的IsBusy属性:
<!-- 先给Shell设置x:Name --> <Shell x:Name="rootShell" ...> <Shell.FlyoutHeader> <StackLayout x:Name="profileCard" BindingContext="{x:Reference rootShell}"> <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" HorizontalOptions="Center" VerticalOptions="Center" Color="Red" /> <!-- 其他布局内容 --> </StackLayout> </Shell.FlyoutHeader> </Shell>
方案2:使用全局状态管理(更可靠)
避免直接依赖Shell.Current,改用单例状态类管理全局IsBusy状态,降低耦合度:
- 创建全局状态类:
using System.ComponentModel; using System.Runtime.CompilerServices; public class AppGlobalState : INotifyPropertyChanged { private bool _isBusy; public bool IsBusy { get => _isBusy; set { if (_isBusy != value) { _isBusy = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // 单例实例 public static AppGlobalState Instance { get; } = new AppGlobalState(); }
- 修改FlyoutHeader绑定:
<!-- 顶部声明命名空间前缀,替换为你的项目命名空间 --> xmlns:local="clr-namespace:你的项目命名空间" <Shell.FlyoutHeader> <StackLayout x:Name="profileCard"> <ActivityIndicator IsRunning="{Binding Source={x:Static local:AppGlobalState.Instance}, Path=IsBusy}" IsVisible="{Binding Source={x:Static local:AppGlobalState.Instance}, Path=IsBusy}" HorizontalOptions="Center" VerticalOptions="Center" Color="Red" /> <!-- 其他布局内容 --> </StackLayout> </Shell.FlyoutHeader>
- 更新GetAsync方法:
public async static Task<List<T>> GetAsync<T>(Expression<Func<T, bool>>? predicate = null, bool includeActiveOnly = true) where T : BaseModel, new() { MainThread.BeginInvokeOnMainThread(() => AppGlobalState.Instance.IsBusy = true); var query = DbContext.Table<T>(); if (includeActiveOnly) query = query.Where(x => x.IsActive == true); if (predicate != null) query = query.Where(predicate); query = query.OrderByDescending(x => x.DateAdded); var list = await query.ToListAsync(); MainThread.BeginInvokeOnMainThread(() => AppGlobalState.Instance.IsBusy = false); return list; }
额外注意事项
- 加入空值判断:操作
Shell.Current前先检查是否为null,避免Shell未初始化时的空引用异常。 - 异常捕获:可以在设置IsBusy的代码块中添加try-catch,记录日志排查问题。
内容的提问来源于stack exchange,提问作者Sandeep Sirohi
相关产品推荐
相关产品推荐

