MAUI复用Xamarin Forms渲染器时CreateRendererWithContext抛出异常
CreateRendererWithContext抛出异常 问题场景
从Xamarin.Forms迁移至MAUI多项目架构时,使用MAUI兼容包复用原有自定义渲染器,调用CreateRendererWithContext创建内部页面渲染器时抛出FragmentManager事务冲突异常。
相关代码文件
MauiProgram.cs(Android项目)
using System; using Microsoft.Maui.Controls.Compatibility.Hosting; namespace LibraryTestAppMaui.Android { public static class MauiProgram { public static MauiAppBuilder builder; public static MauiAppBuilder Builder { get { if (builder == null) { builder = MauiApp.CreateBuilder(); } return builder; } } public static MauiApp CreateMauiApp() { var builder = Builder; builder .UseMauiCompatibility() .UseMauiApp<App>(); builder.ConfigureMauiHandlers(handlers => { #if ANDROID handlers.AddHandler(typeof(KeyboardWrapperPage), typeof(KeyboardWrapperPageRenderer)); #endif }); builder.Services.AddTransient<MainPage>(); return builder.Build(); } } }
App.Xaml.cs
using System; using Microsoft.Maui; using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Xaml; namespace LibraryTestAppMaui { public partial class App : Application { public static new App Current => (App)Application.Current; public App () { InitializeComponent(); MainPage = CreateMainPage(new NavigationPage()); } private Page CreateMainPage(NavigationPage navigationPage) { return new KeyboardWrapperPage(navigationPage); } protected override void OnStart () {} protected override void OnSleep () {} protected override void OnResume () {} } }
KeyboardWrapperPage
using System; namespace LibraryTestAppMaui { public class KeyboardWrapperPage : Page, IPageContainer<Page> { public KeyboardWrapperPage(Page innerPage) { BackgroundColor = Colors.White; CurrentPage = innerPage; InternalChildren.Add(innerPage); } public Page CurrentPage { get; } protected override bool OnBackButtonPressed() { return CurrentPage.SendBackButtonPressed(); } } }
KeyboardWrapperPageRenderer(Android)
using System; using Android.Content; using Android.Views; using Android.Widget; using Microsoft.Maui.Controls.Compatibility.Platform.Android; using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Platform; using AndroidPlatform = Microsoft.Maui.Controls.Compatibility.Platform.Android.Platform; namespace LibraryTestAppMaui.Android { public class KeyboardWrapperPageRenderer : Microsoft.Maui.Controls.Handlers.Compatibility.VisualElementRenderer<KeyboardWrapperPage> { bool disposed; FrameLayout pageContainer; IPageController PageController => Element; int KEYBOARD_HEIGHT => (int)Context.Resources.GetDimension(260); public KeyboardWrapperPageRenderer(Context context) : base(context) { AutoPackage = false; } protected override void OnElementChanged(ElementChangedEventArgs<KeyboardWrapperPage> e) { base.OnElementChanged(e); if (e.NewElement != null) { var bottomBarPage = e.NewElement; if (pageContainer == null) { pageContainer = CreatePageContainer(Context); AddView(pageContainer); RequestLayout(); } UpdateContent(); } } protected override void OnAttachedToWindow() { base.OnAttachedToWindow(); PageController.SendAppearing(); } protected override void OnDetachedFromWindow() { base.OnDetachedFromWindow(); PageController.SendDisappearing(); } void UpdateContent() { pageContainer.RemoveAllViews(); var page = Element?.CurrentPage; if (page != null) { pageContainer.AddView(GetRenderer(page, Context).View); } } protected override void Dispose(bool disposing) { if (disposing && !disposed) { disposed = true; RemoveAllViews(); var pageRenderer = AndroidPlatform.GetRenderer(Element.CurrentPage); if (pageRenderer != null) { pageRenderer.View.RemoveFromParent(); pageRenderer.Dispose(); } pageContainer?.Dispose(); } base.Dispose(disposing); } static FrameLayout CreatePageContainer(Context context) { return new FrameLayout(context) { LayoutParameters = new FrameLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent, GravityFlags.Fill) }; } static IVisualElementRenderer GetRenderer(VisualElement element, Context context) { var renderer = AndroidPlatform.GetRenderer(element); if (renderer == null) { renderer = AndroidPlatform.CreateRendererWithContext(element, context); AndroidPlatform.SetRenderer(element, renderer); } return renderer; } } }
异常详情
Android.Views.InflateException: Binary XML file line #1 in com.companyname.librarytestappmaui:layout/fragment_backstack: Error inflating class androidx.fragment.app.FragmentContainerView
---> Java.Lang.IllegalStateException: FragmentManager is already executing transactions …
解决方案
1. 延迟渲染器创建,避免事务冲突
异常核心原因是OnElementChanged执行时,MAUI内部正在处理Fragment事务,此时手动创建渲染器会触发新的Fragment操作,导致冲突。将UpdateContent延迟到主线程消息队列末尾执行:
protected override void OnElementChanged(ElementChangedEventArgs<KeyboardWrapperPage> e) { base.OnElementChanged(e); if (e.NewElement != null) { var bottomBarPage = e.NewElement; if (pageContainer == null) { pageContainer = CreatePageContainer(Context); AddView(pageContainer); RequestLayout(); } // 延迟执行,避开当前Fragment事务周期 Post(() => UpdateContent()); } }
2. 简化渲染器生命周期管理
移除Dispose中手动操作渲染器父视图的逻辑,依赖MAUI兼容包自动处理渲染器生命周期,避免破坏内部Fragment状态:
protected override void Dispose(bool disposing) { if (disposing && !disposed) { disposed = true; pageContainer?.RemoveAllViews(); pageContainer?.Dispose(); pageContainer = null; } base.Dispose(disposing); }
3. 优化页面封装逻辑
避免直接封装NavigationPage,改为让KeyboardWrapperPage继承NavigationPage,复用MAUI原生的导航页渲染逻辑,减少自定义渲染器的复杂度:
public class KeyboardWrapperPage : NavigationPage { public KeyboardWrapperPage(Page innerPage) : base(innerPage) { BackgroundColor = Colors.White; } protected override bool OnBackButtonPressed() { return CurrentPage.SendBackButtonPressed(); } }
4. 确保版本一致性
检查Microsoft.Maui.Controls.Compatibility包版本与MAUI主包版本完全一致,避免版本不匹配导致的底层API冲突。
内容的提问来源于stack exchange,提问作者user20694704

