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

时间:2013-07-11 13:00:41

标签: android multiplayer device-orientation google-play-games

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

我正在两台独立的设备上测试一款多人游戏。两个用户都可以成功登录,相互发送消息等。然而,当一个用户旋转他们的设备时,他们就会被踢出房间。

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

  • 一想法:android:configChanged =“orientation | screenSize” - 但Android不鼓励这种情况(大多数情况下都是这样) - 但这是我们采用Google Play游戏服务保留的方式设备方向改变的房间?

  • 如何使用“onRetainNonConfigurationInstance()”保存GameHelper实例,并在重新创建活动时再次使用它?

  • 或以某种方式在服务中实施游戏连接(登录,加入房间等)?

或者我是否以错误的方式思考这个问题?!谢谢你的想法和救命。如果可能的话,也会非常感谢代码示例。

3 个答案:

答案 0 :(得分:10)

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

口头解释

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

注意:我认为GameHeaderFragment.java应该被分解为一个抽象类和一个继承自它的游戏特定类(但我没有在这里这样做)。

以下是我的想法(请原谅我的游戏专用代码交织的区域):

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.
    }

}

答案 1 :(得分:1)

这是一个想法。但在此之前,有点说明。

由于内存或其决定的原因,Android资源管理器可随时杀死Android应用。因此,为了保留“永远在线”的永久守护,我们使用服务。

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

拥有此服务将增加一个额外的好处,可能会告诉您远程客户端已断开连接(如果服务未绑定到应用程序,用户断开连接)并且可以帮助实现细粒度连接,作为服务在服务器和GUI客户端之间进行调解。出于所有意图和目的,该服务是玩游戏的真正客户端,并且仅由gui客户端驱动,该客户端告诉服务它应该尝试做什么。这样,服务对用户来说是无用的,并且他的播放状态始终保留。

但首先我尝试通过AndroidManifest使我的应用程序成为一个单例,只使用一个应用程序实例(singleTop)或总是使用相同的进程(sameProcess但不确定这是否有助于微小的一点)。

然后如果失败了,我会采取好的,不那么痛苦的路线,直到最后我看到服务是要走的路。所以也许你的问题有一个轻量级的解决方案,也许你只需要一个简单的deamon服务并且每天都可以调用它。

答案 2 :(得分:1)

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

最初我刚刚使用

实现了
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:screenOrientation="portrait"
清单中的

因为我的游戏和基于游戏服务的片段实现都没有正确支持方向更改。即我相信这些缺点都在我自己的代码中,而不是谷歌提供的东西。

在我的情况下,用户被踢出游戏,因为当一个或另一个玩家“旋转”时,我不会正确地重新启动我的碎片。然后我收到了onLeftRoom回调,我选择在此时完成。

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

片段活动(ABS),包括:

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

  • 一个“无头”(无UI)setRetainInstance(true)片段,它相当于“BaseGameActivity”样本,并执行全部 对GameHelper的调用。

  • GameHelper - 提供的版本不变。

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

顺便说一句,如果有任何游戏(最不像我的那样愚蠢)在我的手机/标签上作为服务运行,我会不高兴 - 但这只是我的意见。我认为一个以其父活动而死的setRetainInstance(true)片段是完全足够的。

我很想听听任何人对此的看法。

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

  

8.2。保留无头片段以处理配置更改无头片段通常用于封装某些状态   配置更改或后台处理任务。为了这   目的是你要设置你的无头片段保留。一个   在配置更改期间不会销毁保留的片段。