Android:如何让Fragment后台运行时保持状态不重置?
看起来你遇到的核心问题是Fragment在应用进入后台后被系统销毁重建,导致计时状态和按钮UI都回到了初始值。同时,你虽然用了RecordingService处理录制逻辑,但Fragment重建后没有和Service同步当前状态,才会出现重置的情况。下面分步骤帮你解决:
1. 先搞懂为什么会重置
Android系统在内存不足时,会销毁后台的Activity/Fragment释放资源。当用户重新打开应用时,系统会重建这些组件,但默认不会帮你保存之前的UI状态和变量值——这就是计时和按钮状态回到初始状态的原因。
2. 保存并恢复Fragment的实例状态
首先,我们需要在Fragment被销毁前保存关键状态,重建时再恢复回来:
保存状态
重写onSaveInstanceState方法,把录制状态、计时基准时间这些关键数据存起来:
@Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); // 保存当前是否正在录制的状态 outState.putBoolean("isRecording", mStartRecording); // 保存计时控件的基准时间 outState.putLong("chronometerBase", chronometer.getBase()); // 保存暂停时的时间(如果有的话) outState.putLong("timeWhenPaused", timeWhenPaused); }
恢复状态
在onViewCreated里,从savedInstanceState中取出保存的数据,恢复UI状态:
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); btnPause.setVisibility(View.GONE); recordBtn.setColorPressed(getResources().getColor(R.color.colorPrimary)); // 恢复之前的状态 if (savedInstanceState != null) { mStartRecording = savedInstanceState.getBoolean("isRecording"); long chronoBase = savedInstanceState.getLong("chronometerBase"); timeWhenPaused = savedInstanceState.getLong("timeWhenPaused"); // 根据恢复的状态更新UI if (mStartRecording) { recordBtn.setImageResource(R.drawable.ic_media_stop); chronometer.setBase(chronoBase); chronometer.start(); recordingStatusTxt.setText("Recording now..."); // 重新添加保持屏幕常亮的flag getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { recordBtn.setImageResource(R.drawable.ic_mic_white); chronometer.setBase(chronoBase); recordingStatusTxt.setText("Click the button to start recording"); } } }
3. 与RecordingService同步状态(关键!)
上面的方法只能处理配置变化(比如旋转屏幕)或临时销毁重建的情况,但如果RecordingService一直在后台运行,Fragment重建后必须主动和Service同步状态:
绑定Service获取实时状态
在Fragment里定义ServiceConnection,绑定到RecordingService来获取状态:
private RecordingService mRecordingService; private boolean mServiceBound = false; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { RecordingService.RecordingBinder binder = (RecordingService.RecordingBinder) service; mRecordingService = binder.getService(); mServiceBound = true; // 绑定后同步Service的状态到UI if (mRecordingService.isRecording()) { mStartRecording = true; recordBtn.setImageResource(R.drawable.ic_media_stop); // 从Service获取当前计时基准时间 chronometer.setBase(mRecordingService.getChronometerBase()); chronometer.start(); recordingStatusTxt.setText("Recording now..."); getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } @Override public void onServiceDisconnected(ComponentName name) { mServiceBound = false; } };
然后在Fragment的生命周期方法里绑定/解绑Service:
@Override public void onStart() { super.onStart(); // 绑定到RecordingService Intent intent = new Intent(getActivity(), RecordingService.class); getActivity().bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } @Override public void onStop() { super.onStop(); // 解绑Service if (mServiceBound) { getActivity().unbindService(mServiceConnection); mServiceBound = false; } }
同时,需要在RecordingService里添加方法,暴露状态和计时信息:
public class RecordingService extends Service { private boolean isRecording = false; private long chronometerBase; // 定义Binder供外部调用 public class RecordingBinder extends Binder { RecordingService getService() { return RecordingService.this; } } private final IBinder mBinder = new RecordingBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } // 暴露录制状态 public boolean isRecording() { return isRecording; } // 暴露计时基准时间 public long getChronometerBase() { return chronometerBase; } // 录制开始时更新状态 public void startRecording() { isRecording = true; chronometerBase = SystemClock.elapsedRealtime(); // ... 你的其他录制逻辑 } // 录制停止时更新状态 public void stopRecording() { isRecording = false; // ... 你的其他停止逻辑 } }
4. 进阶:用ViewModel持久化状态
如果你的Fragment经常遇到销毁重建的情况,推荐用ViewModel保存UI状态——它的生命周期独立于Fragment,不会随Fragment销毁而消失:
public class RecordViewModel extends ViewModel { private MutableLiveData<Boolean> isRecording = new MutableLiveData<>(false); private MutableLiveData<Long> chronometerBase = new MutableLiveData<>(SystemClock.elapsedRealtime()); public LiveData<Boolean> getIsRecording() { return isRecording; } public LiveData<Long> getChronometerBase() { return chronometerBase; } public void setIsRecording(boolean recording) { isRecording.setValue(recording); } public void setChronometerBase(long base) { chronometerBase.setValue(base); } }
然后在Fragment里获取ViewModel并观察状态变化:
private RecordViewModel mViewModel; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mViewModel = new ViewModelProvider(this).get(RecordViewModel.class); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // ... 初始化UI的其他代码 // 观察录制状态变化,自动更新UI mViewModel.getIsRecording().observe(getViewLifecycleOwner(), isRecording -> { mStartRecording = isRecording; if (isRecording) { recordBtn.setImageResource(R.drawable.ic_media_stop); chronometer.start(); recordingStatusTxt.setText("Recording now..."); getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { recordBtn.setImageResource(R.drawable.ic_mic_white); chronometer.stop(); recordingStatusTxt.setText("Click the button to start recording"); } }); // 观察计时基准时间变化 mViewModel.getChronometerBase().observe(getViewLifecycleOwner(), base -> { chronometer.setBase(base); }); } // 在onRecord方法里更新ViewModel状态 private void onRecord(boolean start) { Intent intent = new Intent(getActivity(), RecordingService.class); if(start){ mViewModel.setIsRecording(true); long base = SystemClock.elapsedRealtime(); mViewModel.setChronometerBase(base); recordBtn.setImageResource(R.drawable.ic_media_stop); chronometer.setBase(base); chronometer.start(); getActivity().startService(intent); getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); recordingStatusTxt.setText("Recording now..."); } else { mViewModel.setIsRecording(false); long base = SystemClock.elapsedRealtime(); mViewModel.setChronometerBase(base); recordBtn.setImageResource(R.drawable.ic_mic_white); chronometer.stop(); timeWhenPaused = 0; getActivity().stopService(intent); recordingStatusTxt.setText("Click the button to start recording"); } }
这样即使Fragment被销毁重建,ViewModel里的状态也会保留,UI会自动同步最新状态。
内容的提问来源于stack exchange,提问作者artzy

