Google Play游戏服务多玩家设备方向更改将用户踢出房间

我正在开发一个只有一个活动(扩展了BaseGameActivity )的应用程序,并在多个片段之间切换(很像Google的示例代码状态)。

我现在正在testing一个多人游戏,在两个不同的设备上。 两个用户都可以成功login,向对方发送消息等。但是,即时一个用户旋转他们的设备,他们会被踢出房间。

我认为这是有道理的,因为活动正在被破坏和重新创build。 但我不明白的是,我们需要做些什么来让用户旋转他们的设备,并保持游戏状态(login,join房间等)?

  • 一个想法:android:configChanged =“orientation | screenSize” – 但是Android不鼓励这样做(在大多数情况下,这是出于很好的理由) – 但这是我们必须与Google Play游戏服务一起留在设备方向变化的空间?

  • 那么使用“onRetainNonConfigurationInstance()”来保存GameHelper实例,并在活动重新创build时再次使用它呢?

  • 或者以某种方式在服务中实现游戏连接(login,join房间等)?

还是我在想这个错误的方法? 感谢您的想法和帮助。 如果可能,代码示例(s)也将非常感激。

Solutions Collecting From Web of "Google Play游戏服务多玩家设备方向更改将用户踢出房间"

感谢@Sheldon指出我在正确的方向上关于setRetainInstance(true)的一个“无头”片段。 这是我解决这个问题的途径,现在我想在这里粘贴我的代码,希望可以帮助别人。 但首先:

口头解释

如问题中所述, 设备方向更改将破坏MainActivity extends BaseGameActivity ,并使其与您的游戏状态(即与Google Play服务的连接)相关联。 但是,我们可以将所有的GameHelper代码放入一个“无头的”Fragment(一个没有UI的片段),并声明setRetainInstance(true) 。 现在,当我们的MainActivity extends FragmentActivity在方向变化时被破坏,无头碎片被停止,甚至分离,但不被破坏 ! ( onDestroy()不被调用))当MainActivity被Android重新创build时,我们的无头碎片被自动重新附加到它上面。 在这个时候,在我们的无头片段中, onCreate()不被调用。 所以onCreate()是我们连接到GameHelper的地方。 我们可以从onDestroy() GameHelper断开连接,因为除非应用程序完成(当时可以终止连接onDestroy() ,否则这将永远不会被调用。

注意:我认为GameHeaderFragment.java可能应该被分解成一个Abstract类和一个inheritance它的游戏特定的类(但是我​​在这里没有这样做)。

这就是我想出的(请原谅我的游戏特定代码交织的地方):

GameHeaderFragment.java

 public class GameHelperFragment extends Fragment implements GameHelperListener, OnInvitationReceivedListener, RoomUpdateListener, RoomStatusUpdateListener, RealTimeMessageReceivedListener { protected MainActivity mActivity = null; // The game helper object. This class is mainly a wrapper around this object. protected GameHelper mHelper; final static int MAX_NUM_PLAYERS = 4; // Request codes for the UIs that we show with startActivityForResult: final static int RC_SELECT_PLAYERS = 10000; final static int RC_INVITATION_INBOX = 10001; final static int RC_WAITING_ROOM = 10002; // We expose these constants here because we don't want users of this class // to have to know about GameHelper at all. public static final int CLIENT_GAMES = GameHelper.CLIENT_GAMES; public static final int CLIENT_APPSTATE = GameHelper.CLIENT_APPSTATE; public static final int CLIENT_PLUS = GameHelper.CLIENT_PLUS; public static final int CLIENT_ALL = GameHelper.CLIENT_ALL; // Requested clients. By default, that's just the games client. protected int mRequestedClients = CLIENT_GAMES; protected String mSigningInMessage = "Signing in with Google"; protected String mSigningOutMessage = "Signing out"; // Custom Members String mMyId = ""; String mRoomId = ""; ArrayList<Participant> mParticipants = null; int mCurrentlyPlayingIdx = 0; // idx into mParticipants boolean mIsMultiplayer = false; boolean mWaitRoomDismissedFromCode = false; public interface GameHelperFragmentListener { void onSignInFailed(); void onSignInSucceeded(); void onInvitationReceived(Invitation invitation); void showMainMenu(); void showWaitScreen(); void startGame(); void participantLeftAtIdx(int idx); void handleRealTimeMessage(RealTimeMessage rtm); } GameHelperFragmentListener mListener; public GameHelperFragment() { super(); Log.d("mab", "GHFrag.Constructor()"); } /** * Sets the requested clients. The preferred way to set the requested clients is * via the constructor, but this method is available if for some reason your code * cannot do this in the constructor. This must be called before onCreate in order to * have any effect. If called after onCreate, this method is a no-op. * * @param requestedClients A combination of the flags CLIENT_GAMES, CLIENT_PLUS * and CLIENT_APPSTATE, or CLIENT_ALL to request all available clients. */ protected void setRequestedClients(int requestedClients) { mRequestedClients = requestedClients; } @Override public void onAttach(Activity activity) { Log.d("mab", this + ": onAttach(" + activity + ")"); super.onAttach(activity); mActivity = (MainActivity) activity; mListener = (GameHelperFragmentListener) activity; } @Override public void onCreate(Bundle b) { Log.d("mab", this + ": onCreate()"); super.onCreate(b); setRetainInstance(true); mHelper = new GameHelper(mActivity); mHelper.setup(this, mRequestedClients); //'this' => GameHelperListener mHelper.setSigningInMessage(mSigningInMessage); mHelper.setSigningOutMessage(mSigningOutMessage); mHelper.onStart(mActivity); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return null; // Headless Fragment } @Override public void onActivityCreated(Bundle savedInstanceState) { Log.d("mab", this + ": onActivityCreated()"); super.onActivityCreated(savedInstanceState); } @Override public void onDestroy() { Log.d("mab", this + ": onDestroy()"); super.onDestroy(); mHelper.onStop(); } @Override public void onActivityResult(int requestCode, int responseCode, Intent data) { Log.d("mab", this + ": onActivityResult(" + requestCode + ")"); super.onActivityResult(requestCode, responseCode, data); mHelper.onActivityResult(requestCode, responseCode, data); switch (requestCode) { case RC_SELECT_PLAYERS: // we got the result from the "select players" UI -- ready to create the room handleSelectPlayersResult(responseCode, data); break; case RC_INVITATION_INBOX: // we got the result from the "select invitation" UI (invitation inbox). We're // ready to accept the selected invitation: handleInvitationInboxResult(responseCode, data); break; case RC_WAITING_ROOM: // ignore result if we dismissed the waiting room from code: if (mWaitRoomDismissedFromCode) break; // we got the result from the "waiting room" UI. if (responseCode == Activity.RESULT_OK) { } else if (responseCode == GamesActivityResultCodes.RESULT_LEFT_ROOM) { // player actively indicated that they want to leave the room leaveRoom(); } else if (responseCode == Activity.RESULT_CANCELED) { leaveRoom(); } break; } } // Handle the result of the "Select players UI" we launched when the user clicked the // "Invite friends" button. We react by creating a room with those players. private void handleSelectPlayersResult(int responseCode, Intent data) { if (responseCode != Activity.RESULT_OK) { Log.w("mab", "*** select players UI cancelled, " + responseCode); showMainMenu(); return; } Log.d("mab", "Select players UI succeeded."); // get the invitee list final ArrayList<String> invitees = data.getStringArrayListExtra(GamesClient.EXTRA_PLAYERS); Log.d("mab", "Invitee count: " + invitees.size()); // get the automatch criteria Bundle autoMatchCriteria = null; int minAutoMatchPlayers = data.getIntExtra(GamesClient.EXTRA_MIN_AUTOMATCH_PLAYERS, 0); int maxAutoMatchPlayers = data.getIntExtra(GamesClient.EXTRA_MAX_AUTOMATCH_PLAYERS, 0); if (minAutoMatchPlayers > 0 || maxAutoMatchPlayers > 0) { autoMatchCriteria = RoomConfig.createAutoMatchCriteria( minAutoMatchPlayers, maxAutoMatchPlayers, 0); Log.d("mab", "Automatch criteria: " + autoMatchCriteria); } // create the room Log.d("mab", "Creating room..."); RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this); rtmConfigBuilder.addPlayersToInvite(invitees); rtmConfigBuilder.setMessageReceivedListener(this); rtmConfigBuilder.setRoomStatusUpdateListener(this); if (autoMatchCriteria != null) { rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria); } showWaitScreen(); keepScreenOn(); getGamesClient().createRoom(rtmConfigBuilder.build()); Log.d("mab", "Room configured, waiting for it to be created..."); } // Handle the result of the invitation inbox UI, where the player can pick an invitation // to accept. We react by accepting the selected invitation, if any. private void handleInvitationInboxResult(int response, Intent data) { if (response != Activity.RESULT_OK) { Log.d("mab", "*** invitation inbox UI cancelled, " + response); showMainMenu(); return; } Log.d("mab", "Invitation inbox UI succeeded."); Invitation inv = data.getExtras().getParcelable(GamesClient.EXTRA_INVITATION); // accept invitation acceptInviteToRoom(inv.getInvitationId()); } protected GamesClient getGamesClient() { return mHelper.getGamesClient(); } protected AppStateClient getAppStateClient() { return mHelper.getAppStateClient(); } protected PlusClient getPlusClient() { return mHelper.getPlusClient(); } protected boolean isSignedIn() { return mHelper.isSignedIn(); } protected void beginUserInitiatedSignIn() { mHelper.beginUserInitiatedSignIn(); } protected void signOut() { mHelper.signOut(); } protected void showAlert(String title, String message) { mHelper.showAlert(title, message); } protected void showAlert(String message) { mHelper.showAlert(message); } protected void enableDebugLog(boolean enabled, String tag) { mHelper.enableDebugLog(enabled, tag); } protected String getInvitationId() { return mHelper.getInvitationId(); } protected void reconnectClients(int whichClients) { mHelper.reconnectClients(whichClients); } protected String getScopes() { return mHelper.getScopes(); } protected boolean hasSignInError() { return mHelper.hasSignInError(); } protected ConnectionResult getSignInError() { return mHelper.getSignInError(); } protected void setSignInMessages(String signingInMessage, String signingOutMessage) { mSigningInMessage = signingInMessage; mSigningOutMessage = signingOutMessage; } public void setRoomId(String rid) { mRoomId = rid; } public String getRoomId() { return mRoomId; } @Override public void onRealTimeMessageReceived(RealTimeMessage rtm) { mListener.handleRealTimeMessage(rtm); } // Called when we are connected to the room. We're not ready to play yet! (maybe not everybody is connected yet). @Override public void onConnectedToRoom(Room room) { Log.d("mab", "onConnectedToRoom."); // get room ID, participants and my ID: mRoomId = room.getRoomId(); mParticipants = room.getParticipants(); mMyId = room.getParticipantId(getGamesClient().getCurrentPlayerId()); // print out the list of participants (for debug purposes) Log.d("mab", "Room ID: " + mRoomId); Log.d("mab", "My ID " + mMyId); Log.d("mab", "<< CONNECTED TO ROOM>>"); Log.d("mab", " Number of Joined Participants: " + getNumJoinedParticipants()); } // Called when we get disconnected from the room. We return to the main screen. @Override public void onDisconnectedFromRoom(Room room) { mIsMultiplayer = false; mRoomId = null; showGameError("Disconnected from room"); } @Override public void onJoinedRoom(int statusCode, Room room) { Log.d("mab", "onJoinedRoom(" + statusCode + ")"); if (room != null) { Log.d("mab", " roomId: " + room.getRoomId()); } if (statusCode != GamesClient.STATUS_OK) { mIsMultiplayer = false; Log.e("mab", "*** Error: onJoinedRoom, status " + statusCode); showGameError("Joined room unsuccessfully: " + statusCode); return; } mRoomId = room.getRoomId(); // show the waiting room UI showWaitingRoom(room); } // Called when we've successfully left the room (this happens a result of voluntarily leaving // via a call to leaveRoom(). If we get disconnected, we get onDisconnectedFromRoom()). @Override public void onLeftRoom(int statusCode, String roomId) { // we have left the room; return to main screen. Log.d("mab", "onLeftRoom, code " + statusCode); mRoomId = null; //????? right? showMainMenu(); } // Called when room is fully connected. @Override public void onRoomConnected(int statusCode, Room room) { Log.d("mab", "onRoomConnected(" + statusCode + ")"); if (room != null) { Log.d("mab", " roomId: " + room.getRoomId()); } if (statusCode != GamesClient.STATUS_OK) { mIsMultiplayer = false; Log.d("mab", "*** Error: onRoomConnected, status " + statusCode); showGameError("Roon connected unsuccessfully: " + statusCode); return; } mRoomId = room.getRoomId(); mParticipants = room.getParticipants(); // not sure if we need this here again, but shouldn't hurt (or maybe we want this ONLY here) mIsMultiplayer = true; // Set 1st player to take a turn mCurrentlyPlayingIdx = 0; // Start Game! mListener.startGame(); } // Called when room has been created @Override public void onRoomCreated(int statusCode, Room room) { Log.d("mab", "onRoomCreated(" + statusCode + ")"); if (room != null) { Log.d("mab", " roomId: " + room.getRoomId()); } if (statusCode != GamesClient.STATUS_OK) { mIsMultiplayer = false; Log.e("mab", "*** Error: onRoomCreated, status " + statusCode); showGameError("Room not created successfully: " + statusCode); return; } mRoomId = room.getRoomId(); // show the waiting room UI showWaitingRoom(room); } // Called when we get an invitation to play a game. We react by showing that to the user. @Override public void onInvitationReceived(Invitation invitation) { Log.d("mab", "ghFrag.onInvitationReceived()"); mListener.onInvitationReceived(invitation); } @Override public void onSignInFailed() { mListener.onSignInFailed(); } @Override public void onSignInSucceeded() { // install invitation listener so we get notified if we receive an invitation to play a game. getGamesClient().registerInvitationListener(this); if (getInvitationId() != null) { acceptInviteToRoom(getInvitationId()); return; } mListener.onSignInSucceeded(); } // Accept the given invitation. void acceptInviteToRoom(String invId) { // accept the invitation Log.d("mab", "Accepting invitation: " + invId); keepScreenOn(); RoomConfig.Builder roomConfigBuilder = RoomConfig.builder(this); roomConfigBuilder.setInvitationIdToAccept(invId) .setMessageReceivedListener(this) .setRoomStatusUpdateListener(this); showWaitScreen(); getGamesClient().joinRoom(roomConfigBuilder.build()); } // Sets the flag to keep this screen on. It's recommended to do that during the handshake when setting up a game, because if the screen turns off, the game will be cancelled. void keepScreenOn() { getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } // Clears the flag that keeps the screen on. void stopKeepingScreenOn() { getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } public void inviteFriends() { // show list of invitable players Intent intent = getGamesClient().getSelectPlayersIntent(1, 3); showWaitScreen(); startActivityForResult(intent, RC_SELECT_PLAYERS); } // Leave the room. void leaveRoom() { Log.d("mab", "Leaving room."); mIsMultiplayer = false; stopKeepingScreenOn(); if (mRoomId != null) { getGamesClient().leaveRoom(this, mRoomId); mRoomId = null; showWaitScreen(); } else { showMainMenu(); } } // Show the waiting room UI to track the progress of other players as they enter the // room and get connected. void showWaitingRoom(Room room) { Log.d("mab", "GHFrag.showWaitingRoom()"); mWaitRoomDismissedFromCode = false; int minPlayers = MAX_NUM_PLAYERS; // This just means the "Start" menu item will never be enabled (waiting room will exit automatically once everyone has made a decision) Intent i = getGamesClient().getRealTimeWaitingRoomIntent(room, minPlayers); // show waiting room UI getActivity().startActivityForResult(i, RC_WAITING_ROOM); } // Forcibly dismiss the waiting room UI (this is useful, for example, if we realize the // game needs to start because someone else is starting to play). void dismissWaitingRoom() { mWaitRoomDismissedFromCode = true; getActivity().finishActivity(RC_WAITING_ROOM); //getActivity() ????? } // Show error message about game being cancelled and return to main screen. void showGameError(String msg) { showAlert("Error", "Game Error: " + msg); showMainMenu(); } private void showMainMenu() { mListener.showMainMenu(); } private void showWaitScreen() { mListener.showWaitScreen(); } } 

MainActivity.java

 public class MainActivity extends FragmentActivity implements MainMenuFragment.Listener, PlayFragment.Listener, GameHelperFragmentListener, AlertDialogFragmentListener { public static final String MAIN_MENU_FRAGMENT = "MainMenuFragment"; public static final String PLAY_FRAGMENT = "PlayFragment"; public static final String WAIT_FRAGMENT = "WaitFragment"; // Fragments MainMenuFragment mMainMenuFragment; PlayFragment mPlayFragment; WaitFragment mWaitFragment; GameHelperFragment gameHelperFragment = null; String mIncomingInvitationId = null; @SuppressLint("NewApi") @Override public void onCreate(Bundle savedInstanceState) { Log.d("mab", "MainActivity.onCreate()"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Add Headless Fragment (if not already retained) gameHelperFragment = (GameHelperFragment) getSupportFragmentManager().findFragmentByTag("GameHelperFragment"); if (gameHelperFragment == null) { Log.d("mab", this + ": Existing fragment not found.!!!"); gameHelperFragment = new GameHelperFragment(); gameHelperFragment.setSignInMessages("Signing in with Google", "Signing out"); getSupportFragmentManager().beginTransaction().add(gameHelperFragment, "GameHelperFragment").commit(); } else { Log.d("mab", this + ": Existing fragment found.!!!"); } } @Override public void onSignInFailed() { Log.d("mab", "MainActivity.onSignInFailed()"); if (mMainMenuFragment != null) { mMainMenuFragment.updateUi(); } } @Override public void onSignInSucceeded() { Log.d("mab", "MainActivity.onSignInSuccedded()"); if (mMainMenuFragment != null) { mMainMenuFragment.updateUi(); } } @Override public void onSignInButtonClicked() { Log.d("mab", "MainActivity.onSignInButtonClicked()"); // start the sign-in flow beginUserInitiatedSignIn(); } @Override public void onSignOutButtonClicked() { Log.d("mab", "MainActivity.onSignOutButtonClicked()"); signOut(); if (mMainMenuFragment != null) { mMainMenuFragment.updateUi(); } } @Override public void onInvitationReceived(Invitation invitation) { mIncomingInvitationId = invitation.getInvitationId(); // show accept/decline dialog box here. String dispName = invitation.getInviter().getDisplayName(); DialogFragment alertInvitationReceived = AlertDialogFragment.newInstance("Invitation Received", dispName + " is inviting you to play Yahtzee Blast.", "Accept", "Decline", null); alertInvitationReceived.show(getSupportFragmentManager(), DLG_INVITATION_RECVD); } @Override protected void onPause() { Log.d("mab", "MainActivity.onPause()"); super.onPause(); } @Override protected void onStop() { Log.d("mab", "MainActivity.onStop()"); super.onStop(); } @Override protected void onStart() { Log.d("mab", "MainActivity.onStart()"); super.onStart(); } @Override protected void onResume() { Log.d("mab", "MainActivity.onResume()"); super.onResume(); } @Override protected void onDestroy() { Log.d("mab", "MainActivity.onDestroy()"); super.onDestroy(); mHelper = null; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("mIncomingInvitationId", mIncomingInvitationId); // ? need this ? } @Override public void onInviteFriendsClicked() { Log.d("mab", "MainActivity.onInviteFriendsClicked()"); gameHelperFragment.inviteFriends(); } @Override public void onSeeAllInvitationsClicked() { Log.d("mab", "MainActivity.onSeeAllInvitationsClicked()"); gameHelperFragment.seeAllInvitations(); } @Override public void onActivityResult(int requestCode, int responseCode, Intent intent) { Log.d("mab", this + ": onActivityResult(requestCode: " + requestCode + ", responseCode: " + responseCode + ")"); super.onActivityResult(requestCode, responseCode, intent); // Call GameHelper's onActivityResult in case this result pertains to it gameHelperFragment.onActivityResult(requestCode, responseCode, intent); } public void onAlertDialogFragmentPositiveClicked(String tag) { Log.d("mab", "MainActivity.onAlertDialogFragmentPositiveClicked(" + tag + ")"); if (tag == DLG_INVITATION_RECVD) { gameHelperFragment.acceptInviteToRoom(mIncomingInvitationId); } } // Called when we receive a real-time message from the network. public void handleRealTimeMessage(RealTimeMessage rtm) { Log.d(TAG, "MainActivity.onRealTimeMessageReceived()"); // Handle it here... } // Headless Fragment Functions private void setSignInMessages(String signingInMessage, String signingOutMessage) { gameHelperFragment.setSignInMessages(signingInMessage, signingOutMessage); } private GamesClient getGamesClient() { return gameHelperFragment.getGamesClient(); } private String getInvitationId() { return gameHelperFragment.getInvitationId(); } private void beginUserInitiatedSignIn() { gameHelperFragment.beginUserInitiatedSignIn(); } private void signOut() { gameHelperFragment.signOut(); } private void showAlert(String message) { gameHelperFragment.showAlert(message); } private void showAlert(String title, String message) { gameHelperFragment.showAlert(title, message); } public GameHelperFragment getGameHelperFragment() { return gameHelperFragment; } @Override public void showMainMenu() { switchToFragment(MAIN_MENU_FRAGMENT, false); } @Override public void showWaitScreen() { switchToFragment(WAIT_FRAGMENT, false); } @Override public void participantLeftAtIdx(int idx) { // Handle here, if there's anything you need to do. } } 

这是一个想法。 但在此之前,有一点插图。

Android应用程序可以随时由Android资源pipe理器杀死,由于内存或任何它决定的原因。 所以,为了保留“永远在线”的永久deamon,我们使用服务。

因为你的应用程序可以将它的状态传递给服务,而服务又保存着所有的真实数据(连接,连接到哪个服务器,服务器连接等),并重新连接到服务,所以这里的服务是整洁的。

有了这个服务将增加一个附加的好处,有可能告诉你,你的远程客户端已经断开连接(如果服务没有绑定到应用程序,用户def断开连接),并可以帮助细化连接,因为服务介于服务器和GUI客户端。 对于所有的意图和目的,服务是玩游戏的真正的客户,并且正由gui客户驱动,告诉服务该做什么。 这样的服务对用户来说是无形的,他的玩牌总是被保留下来。

但是,首先我试图通过AndroidManifest来使应用程序成为一个单例,只使用应用程序的一个实例(singleTop),或者总是使用相同的过程(sameProcess,但不确定这个过程是否有用)。

那么如果失败了,我会采取好的,不那么痛苦的路线,直到最后我看到服务是要走的路。 所以也许有一个轻量级的修复你的问题,也许你只需要一个简单的deamon服务,并称之为一天。

我遇到了完全相同的问题,我目前正在解决这个问题。

最初我刚刚实施使用

  android:configChanges="keyboardHidden|orientation|screenSize" android:screenOrientation="portrait" 

在清单中,因为无论是我的游戏还是基于片段的游戏服务实现,都无法正确支持方向更改。 即我相信缺点是在我自己的代码中,而不是Google提供的东西。

在我的情况下,用户被踢出游戏,因为当一个或其他玩家“旋转”,我不正确重新启动我的片段。 然后我收到onLeftRoomcallback,我select在这一点上完成。

我正在借此机会改进和简化我自己基于片段的游戏服务的实现,这是我的基本计划:

片段活性(ABS)包括:

  • “快速游戏”,“排行榜”等一些简单的UI片段标签

  • “无头”(无UI)setRetainInstance(true)片段,与我的“BaseGameActivity”示例等价,并执行所有对GameHelper的调用。

  • GameHelper – 不变的提供的版本。

这种方法的优点是,通过使用“无头”片段,我应该避免目前我看到的一些棘手的问题。 即使重新启动一些片段后,我仍然得到getActivity()null错误,我发现自己试图解决这样一个简单的小游戏似乎太复杂的问题。

顺便说一下,如果有任何游戏(至less是像我这样一个愚蠢的游戏)在我的手机/标签上作为服务运行,我就不会高兴 – 但这只是我的看法。 我认为,一个死亡与它的父母活动setRetainInstance(true)片段是完全足够的。

我有兴趣听到任何人对此的想法。

对于那些不知道(来源http://www.vogella.com/articles/AndroidFragments/article.html#headlessfragments1 ):

8.2。 保留无头碎片来处理configuration更改无头碎片通常用于在configuration更改或后台处理任务中封装某些状态。 为此目的,你会设置你的无头碎片保留。 保留的片段在configuration更改期间不会被破坏。