使用多个布局中断方向变化
Using multiple layouts breaks orientation change
我已经成功地处理了片段的配置更改,但我只为容器使用了一个 XML 布局。
现在我需要为横向模式使用布局,当我转动 phone 并尝试更改当前显示的片段时,出现错误:
E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1328)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1346)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:729)
at android.app.BackStackRecord.commit(BackStackRecord.java:705)
这是我的两个布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
而风景一号:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
如您所见,除了方向不同,两者完全相同。
我想问题可能来自于重复使用使用其他方向添加的旧片段?
感谢您的帮助
编辑:
我的 activity
的代码
package crysteo.pluggicontroller;
public class MainFragmentActivity extends FragmentActivity implements StateModifier,IpAddressChangedListener {
private RemoteFragment remoteFragment;
private MainFragment mainFragment;
private DeviceListFragment deviceListFragment;
private RetainedFragment retainedFragment;
private HandleConnection handleConnection;
private ConnectedFragment connectedFragment;
private CameraFragment cameraFragment;
private final PluggiHandler handler = new PluggiHandler(this);
private static class PluggiHandler extends Handler {
private final WeakReference<MainFragmentActivity> mainFragmentActivityWeakReference;
public PluggiHandler(MainFragmentActivity mainFragmentActivity) {
this.mainFragmentActivityWeakReference = new WeakReference<>(mainFragmentActivity);
}
@Override
public void handleMessage(Message msg) {
MainFragmentActivity mainFragmentActivity = this.mainFragmentActivityWeakReference.get();
BluetoothConstants.BluetoothMessageWhat msgEnum = BluetoothConstants.BluetoothMessageWhat.values()[msg.what];
switch (msgEnum) {
case MESSAGE_STATE_CHANGE:
BluetoothConstants.BluetoothStates argEnum = BluetoothConstants.BluetoothStates.values()[msg.arg1];
switch (argEnum) {
case STATE_CONNECTED:
mainFragmentActivity.connected();
break;
case STATE_CONNECTING:
Toast.makeText(mainFragmentActivity.getApplicationContext(), R.string.connecting_bluetooth, Toast.LENGTH_SHORT).show();
break;
case STATE_NONE:
break;
default:
break;
}
break;
case MESSAGE_WRITE:
/*byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer
String writeMessage = new String(writeBuf);
mConversationArrayAdapter.add("Me: " + writeMessage);*/
break;
case MESSAGE_READ:
/*byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, msg.arg1);
mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage);*/
break;
case MESSAGE_DEVICE_NAME:
Toast.makeText(mainFragmentActivity, String.format(mainFragmentActivity.getResources().getString(R.string.connected_bluetooth),
msg.getData().getString(Constants.DEVICE_NAME)), Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
if (null != mainFragmentActivity) {
Toast.makeText(mainFragmentActivity, msg.getData().getString(Constants.TOAST),
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
mainFragmentActivity.connected();
}
}
;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
if (savedInstanceState == null) {
retainedFragment = new RetainedFragment();
fragmentTransaction.add(retainedFragment, RetainedFragment.class.toString());
handleConnection = new HandleConnection(this, handler);
retainedFragment.setHandleConnection(handleConnection);
deviceListFragment = new DeviceListFragment();
remoteFragment = new RemoteFragment();
mainFragment = new MainFragment();
connectedFragment = new ConnectedFragment();
cameraFragment = new CameraFragment();
fragmentTransaction.add(R.id.main_layout_container, mainFragment, MainFragment.class.toString());
} else {
mainFragment = (MainFragment) getFragmentManager().findFragmentByTag(MainFragment.class.toString());
deviceListFragment = (DeviceListFragment) getFragmentManager().findFragmentByTag(DeviceListFragment.class.toString());
remoteFragment = (RemoteFragment) getFragmentManager().findFragmentByTag(RemoteFragment.class.toString());
retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag(RetainedFragment.class.toString());
connectedFragment = (ConnectedFragment) getFragmentManager().findFragmentByTag(ConnectedFragment.class.toString());
cameraFragment = (CameraFragment) getFragmentManager().findFragmentByTag(CameraFragment.class.toString());
if (mainFragment == null)
mainFragment = new MainFragment();
if (deviceListFragment == null)
deviceListFragment = new DeviceListFragment();
if (remoteFragment == null)
remoteFragment = new RemoteFragment();
if (connectedFragment == null)
connectedFragment = new ConnectedFragment();
if (cameraFragment == null)
cameraFragment = new CameraFragment();
handleConnection = retainedFragment.getHandleConnection();
}
fragmentTransaction.commit();
mainFragment.setBluetoothListener(handleConnection);
if (retainedFragment.getSelectedMac() != null)
mainFragment.onMacAddressChanged(retainedFragment.getSelectedMac());
remoteFragment.setHandleConnection(handleConnection);
mainFragment.setStateModifier(this);
deviceListFragment.setStateModifier(this);
deviceListFragment.setMacAddressChangedListener(mainFragment);
connectedFragment.setStateModifier(this);
connectedFragment.setIpAddressChangedListener(this);
}
@Override
public void onDestroy() {
retainedFragment.setHandleConnection(handleConnection);
super.onDestroy();
}
@Override
public void listDevices() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.main_layout_container, deviceListFragment, DeviceListFragment.class.toString());
fragmentTransaction.addToBackStack("listDevices");
fragmentTransaction.commit();
}
@Override
public void deviceSelected(String macAddress) {
getFragmentManager().popBackStack();
retainedFragment.setSelectedMac(macAddress);
mainFragment.onMacAddressChanged(macAddress);
}
@Override
public void connected() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.main_layout_container, connectedFragment, ConnectedFragment.class.toString());
fragmentTransaction.addToBackStack("connected");
fragmentTransaction.commit();
}
@Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
@Override
public void remoteControl() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.remove(connectedFragment);
fragmentTransaction.add(R.id.main_layout_container, cameraFragment, CameraFragment.class.toString());
fragmentTransaction.add(R.id.main_layout_container, remoteFragment, RemoteFragment.class.toString());
fragmentTransaction.addToBackStack("manualMode");
fragmentTransaction.commit();
}
@Override
public void soundsControl() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.main_layout_container, cameraFragment, CameraFragment.class.toString());
fragmentTransaction.addToBackStack("soundControl");
fragmentTransaction.commit();
}
@Override
public void infoDisplay() {
}
@Override
public void onIpChangedListener(String ip) {
cameraFragment.setIp(ip);
}
}
对于应该显示的片段:
public class ConnectedFragment extends Fragment {
private StateModifier stateModifier;
private IpAddressChangedListener ipAddressChangedListener;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.connected_fragment_layout, container, false);
root.findViewById(R.id.remote_control_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stateModifier.remoteControl();
}
});
root.findViewById(R.id.sounds_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stateModifier.soundsControl();
}
});
final EditText editText = (EditText)root.findViewById(R.id.ip_edit_text);
root.findViewById(R.id.change_ip_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ipAddressChangedListener.onIpChangedListener(editText.getText().toString());
}
});
return root;
}
public void setStateModifier(StateModifier stateModifier) {
this.stateModifier = stateModifier;
}
public void setIpAddressChangedListener(IpAddressChangedListener ipAddressChangedListener) {
this.ipAddressChangedListener = ipAddressChangedListener;
}
}
问题源于蓝牙连接消息事件调用您的 connected()
方法,该方法然后执行片段事务以显示 ConnectedFragment
。
这就是正在发生的事情:
- 用户旋转设备
- Activity开始关机,状态保存
- 连接事件发生(因为activity还没有完全关闭)
- 尝试进行片段交易,但状态已保存
你现在有一个竞争条件。
您需要更仔细地管理这些碎片交易。我建议首先在 connected()
的开头添加此代码以及事件可能导致片段事务的其他任何地方:
if (isFinishing()) {
return;
}
那么当 activity 的状态(可能)已经保存时,您将不会尝试片段交易。
这个单一的代码片段可能无法解决您的所有问题。您可能需要添加一些生命周期日志记录来分析何时发生了什么。然后您可以添加必要的代码以确保事情按正确的顺序发生。
顺便说一句:这段代码
@Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
是多余的; FragmentActivity
的 onBackPressed
方法已经处理了检查片段返回堆栈并在必要时弹出它。也许您打算添加更多自定义后退按钮处理?
所以问题出在我的 HandleConnection 对象上。我没有更新对处理程序的引用,这意味着调用的处理程序指的是已经销毁的 activity.
我发现通过在我的片段事务中使用 commitAllowingStateLoss()。我没有得到 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
,而是得到了 java.lang.IllegalStateException: Activity has been destroyed
错误,这让我猜出了问题所在。
我已经成功地处理了片段的配置更改,但我只为容器使用了一个 XML 布局。
现在我需要为横向模式使用布局,当我转动 phone 并尝试更改当前显示的片段时,出现错误:
E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1328)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1346)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:729)
at android.app.BackStackRecord.commit(BackStackRecord.java:705)
这是我的两个布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
而风景一号:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
如您所见,除了方向不同,两者完全相同。 我想问题可能来自于重复使用使用其他方向添加的旧片段?
感谢您的帮助
编辑: 我的 activity
的代码package crysteo.pluggicontroller;
public class MainFragmentActivity extends FragmentActivity implements StateModifier,IpAddressChangedListener {
private RemoteFragment remoteFragment;
private MainFragment mainFragment;
private DeviceListFragment deviceListFragment;
private RetainedFragment retainedFragment;
private HandleConnection handleConnection;
private ConnectedFragment connectedFragment;
private CameraFragment cameraFragment;
private final PluggiHandler handler = new PluggiHandler(this);
private static class PluggiHandler extends Handler {
private final WeakReference<MainFragmentActivity> mainFragmentActivityWeakReference;
public PluggiHandler(MainFragmentActivity mainFragmentActivity) {
this.mainFragmentActivityWeakReference = new WeakReference<>(mainFragmentActivity);
}
@Override
public void handleMessage(Message msg) {
MainFragmentActivity mainFragmentActivity = this.mainFragmentActivityWeakReference.get();
BluetoothConstants.BluetoothMessageWhat msgEnum = BluetoothConstants.BluetoothMessageWhat.values()[msg.what];
switch (msgEnum) {
case MESSAGE_STATE_CHANGE:
BluetoothConstants.BluetoothStates argEnum = BluetoothConstants.BluetoothStates.values()[msg.arg1];
switch (argEnum) {
case STATE_CONNECTED:
mainFragmentActivity.connected();
break;
case STATE_CONNECTING:
Toast.makeText(mainFragmentActivity.getApplicationContext(), R.string.connecting_bluetooth, Toast.LENGTH_SHORT).show();
break;
case STATE_NONE:
break;
default:
break;
}
break;
case MESSAGE_WRITE:
/*byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer
String writeMessage = new String(writeBuf);
mConversationArrayAdapter.add("Me: " + writeMessage);*/
break;
case MESSAGE_READ:
/*byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, msg.arg1);
mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage);*/
break;
case MESSAGE_DEVICE_NAME:
Toast.makeText(mainFragmentActivity, String.format(mainFragmentActivity.getResources().getString(R.string.connected_bluetooth),
msg.getData().getString(Constants.DEVICE_NAME)), Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
if (null != mainFragmentActivity) {
Toast.makeText(mainFragmentActivity, msg.getData().getString(Constants.TOAST),
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
mainFragmentActivity.connected();
}
}
;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
if (savedInstanceState == null) {
retainedFragment = new RetainedFragment();
fragmentTransaction.add(retainedFragment, RetainedFragment.class.toString());
handleConnection = new HandleConnection(this, handler);
retainedFragment.setHandleConnection(handleConnection);
deviceListFragment = new DeviceListFragment();
remoteFragment = new RemoteFragment();
mainFragment = new MainFragment();
connectedFragment = new ConnectedFragment();
cameraFragment = new CameraFragment();
fragmentTransaction.add(R.id.main_layout_container, mainFragment, MainFragment.class.toString());
} else {
mainFragment = (MainFragment) getFragmentManager().findFragmentByTag(MainFragment.class.toString());
deviceListFragment = (DeviceListFragment) getFragmentManager().findFragmentByTag(DeviceListFragment.class.toString());
remoteFragment = (RemoteFragment) getFragmentManager().findFragmentByTag(RemoteFragment.class.toString());
retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag(RetainedFragment.class.toString());
connectedFragment = (ConnectedFragment) getFragmentManager().findFragmentByTag(ConnectedFragment.class.toString());
cameraFragment = (CameraFragment) getFragmentManager().findFragmentByTag(CameraFragment.class.toString());
if (mainFragment == null)
mainFragment = new MainFragment();
if (deviceListFragment == null)
deviceListFragment = new DeviceListFragment();
if (remoteFragment == null)
remoteFragment = new RemoteFragment();
if (connectedFragment == null)
connectedFragment = new ConnectedFragment();
if (cameraFragment == null)
cameraFragment = new CameraFragment();
handleConnection = retainedFragment.getHandleConnection();
}
fragmentTransaction.commit();
mainFragment.setBluetoothListener(handleConnection);
if (retainedFragment.getSelectedMac() != null)
mainFragment.onMacAddressChanged(retainedFragment.getSelectedMac());
remoteFragment.setHandleConnection(handleConnection);
mainFragment.setStateModifier(this);
deviceListFragment.setStateModifier(this);
deviceListFragment.setMacAddressChangedListener(mainFragment);
connectedFragment.setStateModifier(this);
connectedFragment.setIpAddressChangedListener(this);
}
@Override
public void onDestroy() {
retainedFragment.setHandleConnection(handleConnection);
super.onDestroy();
}
@Override
public void listDevices() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.main_layout_container, deviceListFragment, DeviceListFragment.class.toString());
fragmentTransaction.addToBackStack("listDevices");
fragmentTransaction.commit();
}
@Override
public void deviceSelected(String macAddress) {
getFragmentManager().popBackStack();
retainedFragment.setSelectedMac(macAddress);
mainFragment.onMacAddressChanged(macAddress);
}
@Override
public void connected() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.main_layout_container, connectedFragment, ConnectedFragment.class.toString());
fragmentTransaction.addToBackStack("connected");
fragmentTransaction.commit();
}
@Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
@Override
public void remoteControl() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.remove(connectedFragment);
fragmentTransaction.add(R.id.main_layout_container, cameraFragment, CameraFragment.class.toString());
fragmentTransaction.add(R.id.main_layout_container, remoteFragment, RemoteFragment.class.toString());
fragmentTransaction.addToBackStack("manualMode");
fragmentTransaction.commit();
}
@Override
public void soundsControl() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.main_layout_container, cameraFragment, CameraFragment.class.toString());
fragmentTransaction.addToBackStack("soundControl");
fragmentTransaction.commit();
}
@Override
public void infoDisplay() {
}
@Override
public void onIpChangedListener(String ip) {
cameraFragment.setIp(ip);
}
}
对于应该显示的片段:
public class ConnectedFragment extends Fragment {
private StateModifier stateModifier;
private IpAddressChangedListener ipAddressChangedListener;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.connected_fragment_layout, container, false);
root.findViewById(R.id.remote_control_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stateModifier.remoteControl();
}
});
root.findViewById(R.id.sounds_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stateModifier.soundsControl();
}
});
final EditText editText = (EditText)root.findViewById(R.id.ip_edit_text);
root.findViewById(R.id.change_ip_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ipAddressChangedListener.onIpChangedListener(editText.getText().toString());
}
});
return root;
}
public void setStateModifier(StateModifier stateModifier) {
this.stateModifier = stateModifier;
}
public void setIpAddressChangedListener(IpAddressChangedListener ipAddressChangedListener) {
this.ipAddressChangedListener = ipAddressChangedListener;
}
}
问题源于蓝牙连接消息事件调用您的 connected()
方法,该方法然后执行片段事务以显示 ConnectedFragment
。
这就是正在发生的事情:
- 用户旋转设备
- Activity开始关机,状态保存
- 连接事件发生(因为activity还没有完全关闭)
- 尝试进行片段交易,但状态已保存
你现在有一个竞争条件。
您需要更仔细地管理这些碎片交易。我建议首先在 connected()
的开头添加此代码以及事件可能导致片段事务的其他任何地方:
if (isFinishing()) {
return;
}
那么当 activity 的状态(可能)已经保存时,您将不会尝试片段交易。
这个单一的代码片段可能无法解决您的所有问题。您可能需要添加一些生命周期日志记录来分析何时发生了什么。然后您可以添加必要的代码以确保事情按正确的顺序发生。
顺便说一句:这段代码
@Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
是多余的; FragmentActivity
的 onBackPressed
方法已经处理了检查片段返回堆栈并在必要时弹出它。也许您打算添加更多自定义后退按钮处理?
所以问题出在我的 HandleConnection 对象上。我没有更新对处理程序的引用,这意味着调用的处理程序指的是已经销毁的 activity.
我发现通过在我的片段事务中使用 commitAllowingStateLoss()。我没有得到 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
,而是得到了 java.lang.IllegalStateException: Activity has been destroyed
错误,这让我猜出了问题所在。