Xamarin.Forms FreshMvvm 如何确保OnStartCall在VideoCallPage加载完成后触发
FreshMvvm 下视频通话页时序错位的调整方案
时序错位的核心根源是两处:
- 你重写的
Init方法用了async void签名,这类方法的执行状态外部无法追踪,PushPage的await只会等待页面入栈的动作完成,根本不会等Init内部的异步逻辑跑完- 静态事件
OnStartCall的触发逻辑完全脱离PageModel的生命周期,没有和页面初始化完成的状态做任何绑定
落地调整方案
方案1:替换异步初始化签名,对齐FreshMvvm的可等待生命周期
FreshMvvm的FreshBasePageModel本身提供了可被导航逻辑等待的异步初始化入口InitAsync,返回Task,导航服务的PushPage动作会自动await这个方法的执行完成,把原来的async void Init的逻辑全部迁过去就能解决大部分错位问题:
- 在
VideoCallPageModel里删掉原有的public async override void Init(object initData),重写InitAsync方法,并加一个初始化状态标记:
// 标记页面初始化是否完成 private bool _isInitialized; public bool IsInitialized { get => _isInitialized; set => SetProperty(ref _isInitialized, value); } public override async Task InitAsync(object initData) { // 把原来写在async void Init里的所有异步逻辑全部迁到这里 await InitCallRenderViewAsync(); await LoadLocalCallConfigAsync(); // 所有初始化逻辑跑完后,标记状态为完成 IsInitialized = true; }
- 调整
App.xaml.cs里NewCall方法的逻辑,拿到PageModel的实际实例,等初始化完成后再触发事件:
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () => { if (!IsVideoCallViewOpen) { var navService = FreshIOC.Container.Resolve<IFreshNavigationService>(FreshMvvm.Constants.DefaultNavigationServiceName); // 解析Page的同时拿到绑定的PageModel实例,不要重复Resolve var videoCallPage = FreshPageModelResolver.ResolvePageModel<VideoCallPageModel>(); var videoCallPageModel = videoCallPage.BindingContext as VideoCallPageModel; // PushPage的await会自动等待InitAsync的所有逻辑跑完 await navService.PushPage(videoCallPage, null); // 兜底等待,防止极端场景下状态标记延迟 while (!videoCallPageModel.IsInitialized) { await Task.Delay(30); } } VideoCallMessage caller = JsonConvert.DeserializeObject<VideoCallMessage>(message); VideoCallPeer peer = new VideoCallPeer(caller.UserId, caller.UserName, caller.FirstName); // 等所有初始化流程走完再触发事件 OnStartCall?.Invoke(null, peer); });
方案2:把通话逻辑闭环到PageModel内部,彻底解耦外部触发时序
这个方案稳定性更高,不需要外部代码关心页面什么时候初始化完成,把通话参数当导航值传递,让PageModel自己在初始化完成后处理来电逻辑:
- 定义导航参数类,封装通话需要的对方信息:
public class VideoCallNavParams { public VideoCallPeer Caller { get; set; } }
- 调整
NewCall的逻辑,不再单独触发静态事件,而是把对方信息当导航参数传给目标页面:
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () => { VideoCallMessage caller = JsonConvert.DeserializeObject<VideoCallMessage>(message); VideoCallPeer peer = new VideoCallPeer(caller.UserId, caller.UserName, caller.FirstName); var navService = FreshIOC.Container.Resolve<IFreshNavigationService>(FreshMvvm.Constants.DefaultNavigationServiceName); if (!IsVideoCallViewOpen) { // 构造导航参数,随页面一起传递 var navParams = new VideoCallNavParams { Caller = peer }; await navService.PushPage<VideoCallPageModel>(navParams, null); } else { // 页面已经打开的场景,直接把peer传给当前栈内的PageModel实例处理 var currentCallPageModel = 导航栈顶层页面?.BindingContext as VideoCallPageModel; currentCallPageModel?.HandleIncomingCall(peer); } });
- 在
VideoCallPageModel的InitAsync里,先做完所有页面初始化,再处理来电逻辑:
public override async Task InitAsync(object initData) { // 先跑页面自身的全部初始化逻辑 await InitCallUiResourcesAsync(); WireUpUiEventHandlers(); // 初始化完成后,从导航参数里取来电信息,走原有StartCallHandler的逻辑 var navParams = initData as VideoCallNavParams; if (navParams?.Caller != null) { StartCallHandler(this, navParams.Caller); } } // 补一个公开的来电处理方法,应对页面已打开的来电场景 public void HandleIncomingCall(VideoCallPeer remotePeer) { StartCallHandler(this, remotePeer); }
避坑提醒
- 别继续用
async void的Init重写,这个签名的方法FreshMvvm内部是直接同步调用、不等待执行结果的,所有写在里面的异步逻辑都是“fire and forget”,根本没法对齐时序 - 不要把需要等待的初始化逻辑放到页面的
OnAppearing生命周期里,这个节点的触发时机晚于Init,会比页面入栈的await返回点更晚,反而会拉大时间差 - 如果必须保留现有静态事件
OnStartCall的逻辑,记得在事件的订阅端(也就是VideoCallPageModel里)加判断,只有当自身初始化状态为完成时才执行StartCallHandler,没完成的话先把peer存下来,等初始化完成后再补跑逻辑
内容的提问来源于stack exchange,提问作者David Shochet
相关产品推荐
相关产品推荐

