MAUI自定义Markup扩展首次调用在亚马逊Fire平板崩溃求助
MAUI自定义本地化标记扩展在亚马逊Fire平板崩溃问题
在MAUI项目中,我通过现有标记扩展LocalizationResourceManager管理文本本地化,为了根据应用特定数据动态切换本地化键,基于它实现了自定义扩展DynamicTranslateExtension,同时配合单例DynamicTranslator在语言变化时触发XAML更新。
实现代码
namespace MyApp.Extensions { [ContentProperty(nameof(PrefixKey))] public class DynamicTranslateExtension : IMarkupExtension<BindingBase> { public string PrefixKey { get; set; } // 指定从AppData中获取后缀的属性类型 public string SuffixType { get; set; } public BindingBase ProvideValue(IServiceProvider serviceProvider) { // 返回Binding而非字符串,以便语言变化时触发刷新 return new Binding { Source = DynamicTranslator.Instance, Path = $"[{PrefixKey},{SuffixType}]", Mode = BindingMode.OneWay }; } object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider); } public class DynamicTranslator : INotifyPropertyChanged { #region 单例实现 private static DynamicTranslator m_Singleton; public static DynamicTranslator Instance { get { m_Singleton ??= new DynamicTranslator(); return m_Singleton; } } public DynamicTranslator() { WeakReferenceMessenger.Default.Register<LangChangedMessage>(this, (o, m) => Invalidate()); } #endregion public string this[string compositeKey] { get { var parts = compositeKey.Split(','); var prefixKey = parts[0]; var suffixType = parts.Length > 1 ? parts[1] : ""; return GetDynamicTranslation(prefixKey, suffixType); } } public static string GetDynamicTranslation(string prefixKey, string suffixType) { string finalKey; if (string.IsNullOrEmpty(prefixKey)) { finalKey = "!InvalidKey!"; } else { var ruleset = AppData.CurrentCompetition.Ruleset; string suffix = ""; if (suffixType == "Group") { suffix = (ruleset.GroupDenomination); } finalKey = $"{prefixKey}_{suffix}"; } try { var translation = App.LocalizationResourceManager[finalKey]; return translation; } catch { return $"!{finalKey}!"; } } public event PropertyChangedEventHandler PropertyChanged; public void Invalidate() { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); } } }
XAML使用方式
LabelText="{UBext:DynamicTranslate GroupSelect,SuffixType='Group'}"
根据ruleset.GroupDenomination的值(如"Squad"或"Relay"),会在LocalizationResourceManager中查询GroupSelect_Squad或GroupSelect_Relay对应的本地化文本。
崩溃异常信息
[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object. [mono-rt] at Microsoft.Maui.Controls.BindingExpression.BindingExpressionPart.TryGetValue(Object source, Object& value) in /_/src/Controls/src/Core/BindingExpression.cs:line 737 [mono-rt] at Microsoft.Maui.Controls.BindingExpression.ApplyCore(Object sourceObject, BindableObject target, BindableProperty property, Boolean fromTarget, SetterSpecificity specificity) in /_/src/Controls/src/Core/BindingExpression.cs:line 166 [mono-rt] at Microsoft.Maui.Controls.BindingExpression.Apply(Object sourceObject, BindableObject target, BindableProperty property, SetterSpecificity specificity) in /_/src/Controls/src/Core/BindingExpression.cs:line 94 [mono-rt] at Microsoft.Maui.Controls.Binding.Apply(Object context, BindableObject bindObj, BindableProperty targetProperty, Boolean fromBindingContextChanged, SetterSpecificity specificity) in /_/src/Controls/src/Core/Binding.cs:line 150 [mono-rt] at Microsoft.Maui.Controls.BindableObject.SetBinding(BindableProperty targetProperty, BindingBase binding, SetterSpecificity specificity) in /_/src/Controls/src/Core/BindableObject.cs:line 335 [mono-rt] at Microsoft.Maui.Controls.BindableObject.SetBinding(BindableProperty targetProperty, BindingBase binding) in /_/src/Controls/src/Core/BindableObject.cs:line 289 [mono-rt] at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.TrySetBinding(Object element, BindableProperty property, String localName, Object value, IXmlLineInfo lineInfo, BindingBase& binding, Exception& exception) in /_/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs:line 558 [mono-rt] at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.TrySetPropertyValue(Object element, XmlName propertyName, String xKey, Object value, Object rootElement, IXmlLineInfo lineInfo, IServiceProvider serviceProvider, Exception& xpe) in /_/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs:line 396 [mono-rt] at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.SetPropertyValue(Object xamlelement, XmlName propertyName, Object value, Object rootElement, INode node, HydrationContext context, IXmlLineInfo lineInfo) in /_/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs:line 358 [mono-rt] at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.Visit(ElementNode node, INode parentNode) in /_/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs:line 125 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 189 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 183 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 183 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 185 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 185 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 185 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 185 [mono-rt] at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 185 [mono-rt] at Microsoft.Maui.Controls.Xaml.RootNode.Accept(IXamlNodeVisitor visitor, INode parentNode) in /_/src/Controls/src/Xaml/XamlNode.cs:line 242 [mono-rt] at Microsoft.Maui.Controls.Xaml.XamlLoader.Visit(RootNode rootnode, HydrationContext visitorContext, Boolean useDesignProperties) in /_/src/Controls/src/Xaml/XamlLoader.cs:line 216 [mono-rt] at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, String xaml, Assembly rootAssembly, Boolean useDesignProperties) in /_/src/Controls/src/Xaml/XamlLoader.cs:line 82 [mono-rt] at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, String xaml, Boolean useDesignProperties) in /_/src/Controls/src/Xaml/XamlLoader.cs:line 57 [mono-rt] at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, Type callingType) in /_/src/Controls/src/Xaml/XamlLoader.cs:line 53 [mono-rt] at Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml[CompetitionPage](CompetitionPage view, Type callingType) in /_/src/Controls/src/Xaml/ViewExtensions.cs:line 42 [mono-rt] at UBScoring.CompetitionPage.InitializeComponent() in C:\Users\gregoire.chapeaux_ap\Local\ApexO Projects\MAUI Projects\UBApps\UBScoring\obj\Debug\net9.0-android\Microsoft.Maui.Controls.SourceGen\Microsoft.Maui.Controls.SourceGen.CodeBehindGenerator\Views_CompetitionPage.xaml.sg.cs:line 55 [mono-rt] at UBScoring.CompetitionPage..ctor(CompetitionViewModel vm, ILocalizationResourceManager _localizationResourceManager) in C:\Users\gregoire.chapeaux_ap\Local\ApexO Projects\MAUI Projects\UBApps\UBScoring\Views\CompetitionPage.xaml.cs:line 23 [mono-rt] at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Constructor(Object obj, IntPtr* args) [mono-rt] at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [mono-rt] at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) [mono-rt] at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(ServiceIdentifier serviceIdentifier) [mono-rt] at System.Collections.Concurrent.ConcurrentDictionary`2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceIdentifier, Microsoft.Extensions.DependencyInjection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.Extensions.DependencyInjection.ServiceProvider.ServiceAccessor, Microsoft.Extensions.DependencyInjection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].GetOrAdd(ServiceIdentifier key, Func`2 valueFactory) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope) [mono-rt] at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) [mono-rt] at Microsoft.Maui.MauiContext.WrappedServiceProvider.GetService(Type serviceType) in /_/src/Core/src/MauiContext.cs:line 72 [mono-rt] at Microsoft.Maui.Controls.ShellContent.<>c__DisplayClass20_0.<Microsoft.Maui.Controls.IShellContentController.GetOrCreateContent>b__0() in /_/src/Controls/src/Core/Shell/ShellContent.cs:line 78 [mono-rt] at Microsoft.Maui.Controls.ElementTemplate.CreateContent() in /_/src/Controls/src/Core/ElementTemplate.cs:line 87 [mono-rt] at Microsoft.Maui.Controls.Internals.DataTemplateExtensions.CreateContent(DataTemplate self, Object item, BindableObject container) in /_/src/Controls/src/Core/DataTemplateExtensions.cs:line 23 [mono-rt] at Microsoft.Maui.Controls.ShellContent.Microsoft.Maui.Controls.IShellContentController.GetOrCreateContent() in /_/src/Controls/src/Core/Shell/ShellContent.cs:line 91 [mono-rt] at Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) in /_/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellSectionRenderer.cs:line 124 [mono-rt] at AndroidX.Fragment.App.Fragment.n_OnCreateView_Landroid_view_LayoutInflater_Landroid_view_ViewGroup_Landroid_os_Bundle_(IntPtr jnienv, IntPtr native__this, IntPtr native_inflater, IntPtr native_container, IntPtr native_savedInstanceState) in D:\a\_work\1\s\generated\androidx.fragment.fragment\obj\Release\net8.0-android\generated\src\AndroidX.Fragment.App.Fragment.cs:line 2045 [mono-rt] at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLLL_L(_JniMarshal_PPLLL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 363 [BpBinder] onLastStrongRef automatically unlinking death recipients: <uncached descriptor>
问题原因与修复方案
原因分析
从异常栈和代码来看,崩溃的核心原因是页面初始化时AppData.CurrentCompetition或其Ruleset属性为null,导致GetDynamicTranslation方法中访问ruleset.GroupDenomination时触发空引用异常。亚马逊Fire平板的页面初始化时机可能比其他设备/模拟器更早,此时应用数据还未完成加载,而其他设备的初始化顺序刚好避开了这个问题。
修复步骤
给数据访问添加空值防护
修改GetDynamicTranslation方法,增加对AppData.CurrentCompetition和Ruleset的空值检查,避免直接访问null对象的属性:public static string GetDynamicTranslation(string prefixKey, string suffixType) { string finalKey; if (string.IsNullOrEmpty(prefixKey)) { finalKey = "!InvalidKey!"; } else { // 检查当前竞赛和规则集是否存在 var currentCompetition = AppData.CurrentCompetition; if (currentCompetition == null || currentCompetition.Ruleset == null) { // 返回默认占位符或默认键的翻译 return $"!{prefixKey}_Default!"; } var ruleset = currentCompetition.Ruleset; string suffix = ""; if (suffixType == "Group") { // 额外防护GroupDenomination为null的情况 suffix = ruleset.GroupDenomination ?? ""; } finalKey = $"{prefixKey}_{suffix}"; } try { // 防护LocalizationResourceManager为null的情况 var translation = App.LocalizationResourceManager?[finalKey] ?? $"!{finalKey}!"; return translation; } catch { return $"!{finalKey}!"; } }**给绑定
相关产品推荐
相关产品推荐

