Retained Fragment+AsyncTask处理屏幕旋转时的空指针异常问题
首先得明确问题根源:当你旋转设备时,Retained Fragment确实被保留了,但后台的AsyncTask还没跑完,MyApp.getStoredData()返回的还是null,这时候直接调用toString()自然会触发空指针异常。下面给你几个实用的解决思路:
思路1:让Retained Fragment直接管理AsyncTask和UI回调
把AsyncTask的控制权交给Retained Fragment,而不是依赖Application存储数据,这样能更精准地控制异步任务的生命周期和UI更新时机:
在Fragment中持有AsyncTask引用和当前View的引用
在Fragment类里添加成员变量:private MyDownloadTask mDownloadTask; private View mCurrentView;修改onCreateView逻辑
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mCurrentView = inflater.inflate(R.layout.fragment, container, false); setWidgets(mCurrentView); if (mDownloadTask == null) { // 首次创建或任务已完成,启动新任务 mDownloadTask = new MyDownloadTask(); mDownloadTask.execute(); } else { // 任务正在运行,检查是否已有数据 Object data = MyApp.getStoredData(); if (data != null) { // 已有数据,直接更新UI updateUI(data.toString()); } else { // 任务还在跑,显示加载状态 showLoadingState(); } } return mCurrentView; }给AsyncTask添加回调,完成后直接通知Fragment更新
修改你的AsyncTask,在onPostExecute里调用Fragment的更新方法:private class MyDownloadTask extends AsyncTask<Void, Void, Object> { @Override protected Object doInBackground(Void... voids) { // 调用WebService下载数据 return downloadDataFromWebService(); } @Override protected void onPostExecute(Object result) { super.onPostExecute(result); MyApp.setStoredData(result); // 依然可以存在Application里做缓存 mDownloadTask = null; // 任务完成后清空引用 // 如果当前View存在,直接更新UI if (mCurrentView != null) { updateUI(result.toString()); hideLoadingState(); } } }处理Fragment的生命周期,避免内存泄漏
在Fragment的onDestroyView里把View引用置空:@Override public void onDestroyView() { super.onDestroyView(); mCurrentView = null; }
思路2:添加非空判断+加载状态提示(最小改动方案)
如果你不想大改现有代码,最简单的方式就是在取数据时做非空校验,同时给用户显示加载中的状态:
修改onCreateView的else分支:
else { Object myData = MyApp.getStoredData(); if (myData != null) { // 已有数据,更新UI updateUI(myData.toString()); } else { // 任务还在运行,显示加载动画/提示 showLoadingState(); // 这里可以注册一个监听,比如用LocalBroadcastManager,当AsyncTask完成后发送广播,Fragment接收后更新UI registerDataLoadedReceiver(); } }
然后在AsyncTask的onPostExecute里发送广播:
@Override protected void onPostExecute(Object result) { super.onPostExecute(result); MyApp.setStoredData(result); // 发送广播通知数据已加载 Intent intent = new Intent("DATA_LOADED_ACTION"); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); }
记得在Fragment的onDestroy里注销广播接收器,避免内存泄漏。
额外建议:考虑改用ViewModel
虽然你现在用的是Retained Fragment,但Android Jetpack的ViewModel天生就是为了解决配置变更时保存数据和异步任务的问题,它比Retained Fragment更简洁、更易维护。ViewModel会自动在配置变更时保留,你可以把AsyncTask或者更推荐的Coroutines放在ViewModel里,然后Fragment只负责观察数据变化更新UI,这样能彻底避免这类生命周期相关的问题。
内容的提问来源于stack exchange,提问作者giozh

