Xamarin Android应用旋转屏幕时WebService调用崩溃的解决方案咨询
嘿,这个问题我太熟了——屏幕旋转时Android会销毁并重建Activity,你的异步WebService调用如果绑定在旧的Activity实例上,肯定会因为上下文丢失或者回调失效导致崩溃,甚至请求直接失败。下面给你几个经过实践验证的解决方案,按推荐程度排序:
方案1:使用ViewModel(最推荐,符合Android架构规范)
ViewModel是AndroidX Lifecycle库提供的组件,它的核心特性就是屏幕旋转时不会被销毁,完美适配这种跨配置变更的异步任务场景。你可以把WebService的调用逻辑放在ViewModel里,Activity只负责观察任务结果并更新UI,完全不用担心旋转导致的上下文失效。
代码示例:
首先创建你的ViewModel类:
using AndroidX.Lifecycle; using System.Threading.Tasks; using System; public class WebServiceViewModel : ViewModel { private bool _isTaskRunning; public bool IsTaskRunning => _isTaskRunning; // 定义回调事件,通知Activity结果 public event Action<string> OnCallSuccess; public event Action<Exception> OnCallFailed; public async Task ExecuteWebServiceCall() { if (_isTaskRunning) return; // 避免重复调用 _isTaskRunning = true; try { // 替换成你的实际WebService调用逻辑 var response = await YourWebServiceClient.FetchDataAsync(); OnCallSuccess?.Invoke(response); } catch (Exception ex) { OnCallFailed?.Invoke(ex); } finally { _isTaskRunning = false; } } }
然后在Activity中使用ViewModel:
protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.your_activity_layout); // 获取ViewModel实例,旋转时会复用同一个实例 var viewModel = new ViewModelProvider(this).Get<WebServiceViewModel>(); // 绑定结果回调(每次OnCreate都要绑定,因为Activity是新实例) viewModel.OnCallSuccess += result => { Toast.MakeText(this, $"请求成功:{result}", ToastLength.Short).Show(); // 这里可以放心更新UI,当前Activity是有效的 }; viewModel.OnCallFailed += ex => { Toast.MakeText(this, $"请求失败:{ex.Message}", ToastLength.Short).Show(); }; // 仅在首次创建Activity时启动调用(避免旋转后重复触发) if (savedInstanceState == null) { _ = viewModel.ExecuteWebServiceCall(); } }
方案2:禁止Activity在屏幕旋转时重建
如果你的应用不需要横竖屏切换布局,或者可以自己处理布局变更,最简单的方式就是让系统不要在旋转时重建Activity。只需在Activity的属性中添加ConfigChanges配置即可。
代码示例:
[Activity( Label = "@string/app_name", MainLauncher = true, // 声明监听屏幕方向和尺寸变化,避免Activity重建 ConfigChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.SmallestScreenSize )] public class MainActivity : AppCompatActivity { private bool _isWebServiceInProgress; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.your_activity_layout); if (!_isWebServiceInProgress) { _isWebServiceInProgress = true; _ = CallWebServiceAsync(); } } private async Task CallWebServiceAsync() { try { var result = await YourWebServiceClient.FetchDataAsync(); Toast.MakeText(this, $"请求成功:{result}", ToastLength.Short).Show(); } catch (Exception ex) { Toast.MakeText(this, $"请求失败:{ex.Message}", ToastLength.Short).Show(); } finally { _isWebServiceInProgress = false; } } // 可选:如果需要处理横竖屏布局切换,重写这个方法 public override void OnConfigurationChanged(Configuration newConfig) { base.OnConfigurationChanged(newConfig); // 根据newConfig.Orientation切换布局 // 比如:if (newConfig.Orientation == Orientation.Landscape) { SetContentView(...); } } }
这个方案的优点是实现简单,缺点是失去了系统自动适配横竖屏布局的能力,如果需要适配不同方向的布局,你得自己手动处理。
方案3:使用保留实例的Fragment(传统方案)
这是ViewModel出现之前常用的方案:创建一个无UI的Fragment,设置RetainInstance = true,这样屏幕旋转时Fragment不会被销毁,WebService调用放在Fragment中,Activity通过Fragment获取任务状态和结果。
代码示例:
首先创建保留Fragment:
public class WebServiceRetainedFragment : Fragment { public event Action<string> OnCallSuccess; public event Action<Exception> OnCallFailed; private bool _isTaskRunning; public override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); RetainInstance = true; // 关键:设置保留实例 } public async Task StartWebServiceCall() { if (_isTaskRunning) return; _isTaskRunning = true; try { var result = await YourWebServiceClient.FetchDataAsync(); OnCallSuccess?.Invoke(result); } catch (Exception ex) { OnCallFailed?.Invoke(ex); } finally { _isTaskRunning = false; } } }
然后在Activity中使用:
protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.your_activity_layout); WebServiceRetainedFragment fragment; if (savedInstanceState == null) { // 首次创建时添加Fragment fragment = new WebServiceRetainedFragment(); SupportFragmentManager.BeginTransaction() .Add(fragment, "WebServiceFragmentTag") .Commit(); // 启动WebService调用 _ = fragment.StartWebServiceCall(); } else { // 旋转后获取已存在的Fragment实例 fragment = SupportFragmentManager.FindFragmentByTag("WebServiceFragmentTag") as WebServiceRetainedFragment; } // 绑定回调 fragment.OnCallSuccess += result => { Toast.MakeText(this, $"请求成功:{result}", ToastLength.Short).Show(); }; fragment.OnCallFailed += ex => { Toast.MakeText(this, $"请求失败:{ex.Message}", ToastLength.Short).Show(); }; }
为什么你的原有方式会崩溃?
原来的WebService调用直接绑定在Activity中,当屏幕旋转时,旧的Activity被销毁,但异步任务还在执行,任务完成后回调引用的是旧的Activity实例,这会导致空引用异常或者上下文失效,最终引发崩溃。上面的方案都是通过将异步任务与Activity的生命周期解耦,避免了这个问题。
内容的提问来源于stack exchange,提问作者pasluc74669

