目录

1. Use Case

Concepts

HUAWEI Game Service allows your game to save your players' game progress to HUAWEI Mobile Cloud and then retrieve the saved data so that your players can continue the game from the last save point from any device as long as they use HUAWEI IDs to sign in to the game. In this way, players do not need to start from the beginning even if their device is lost, damaged, or changed.

A saved game consists of the following parts:

Attribute

Description

ID

Unique ID of an archive, which is   generated by Huawei game server.

Name

Name of an archive, which is generated by   Huawei game server.

Description

Description about an archive, which is   defined by you for players to view. The description can contain up to 1000   characters.

Last update time

Timestamp when a game is last saved. The   value is generated by Huawei game server and is accurate to milliseconds.

Played time

Total played time of an archive, in   milliseconds. Your game needs to provide the value when updating an archive.

Game progress

Progress of a player on an archive. You   can define how to measure the progress. For example, the progress can be   represented by the current level reached by a player, for which you can   define an integer.

Cover image

Cover image of an archive, which is   usually the game screenshot taken at the save point and provided by your   game. This attribute is optional. If you do not set the attribute, the   default image is used. A JPG or PNG image with the aspect ratio 16:9 and a   size no larger than 200 KB is recommended.

 Offline synchronization

Conflict resolution

Your game may encounter conflicts when using the saved game function. These conflicts occur when a player plays your game on different devices using the same HUAWEI ID at the same time and these devices perform reads and writes on the same archive in poor network conditions. If two archives conflict with each other, Huawei game server will notify your game of the conflict reason and allow your game to use one archive as the final archive or properly combine the two archives into the archive to use.

Operation

Conflict Occurs When

Archive updating

Archive A is written; while archive B already   exists on the server.

  • The played time of archive B   is longer.

  • The progress of archive B is   faster.

  • The last update time of   archive B is later.

  • The description or cover   image of archive A is different from that of archive B.

Archive reading

Archive A is cached locally, but archive   B is retrieved from the server.

  • The played time of archive A   is longer.

  • The progress of archive A is   faster.

  • The last update time of   archive A is later.

  • The description or cover   image of archive A is different from that of archive B.

 

There are two ways of resolving conflicts:

2. Before You Start

Note:
You are advised to provide the function for users who have enabled Game Services to disable it. CallGamesClient.cancelGameService to implement the function.

3. Initializing the ArchivesClient Instance

1. To use the saved game function, call HuaweiIdAuthParamsHelper.setScopeList when a player signs in to your game to apply for the permission scope of DRIVE_APP_DATA. The sample code is as follows:

  1. List<Scope> scopes = new ArrayList<>();
  2. scopes.add(GameScopes.DRIVE_APP_DATA);
  3. HuaweiIdAuthParams huaweiIdAuthParams = new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM_GAME).setScopeList(scopes).createParams();

2. Before using archive API capabilities, call Games.getArchiveClient to obtain the ArchivesClient instance. The sample code is as follows:

  1. ArchivesClient client = Games.getArchiveClient(this, mAuthHuaweiId);

The value of mAuthHuaweiId passed in the API is the AuthHuaweiId object obtained when a user signs in to the game. If the sign-in session has expired, call the HuaweiIdAuthService.getSignInIntent method to sign the user in to the game again or call the HuaweiIdAuthService.silentSignIn method to implement silent sign-in and obtain AuthHuaweiId.

4. Creating a Saved Game

When a player saves a game for the first time, Huawei game server creates an archive file and archive metadata for the saved game.

1.  When a player saves the game during gameplay, your game needs to query Huawei game server's restrictions on related archive files.

  1. Task<Integer> thumbnailSizeTask = Games.getArchiveClient(this, mAuthHuaweiId) .getLimitThumbnailSize();
  2. thumbnailSizeTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
  3.    @Override
  4.    public void onSuccess(Integer integer) {
  5.        int maxThumbnailSize = integer;
  6.    }
  7. }).addOnFailureListener(new OnFailureListener() {
  8.    @Override
  9.    public void onFailure(Exception e) {
  10.        if (e instanceof ApiException) {
  11.            ApiException apiException = (ApiException) e;
  12.            Log.e("archive","statusCode:" + apiException.getStatusCode());
  13.        }
  14.    }
  15. });
  16. Task<Integer> detailSizeTask = Games.getArchiveClient(this, signInHuaweiId) .getLimitDetailsSize();
  17. detailSizeTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
  18.    @Override
  19.    public void onSuccess(Integer integer) {
  20.        int detailSize = integer;
  21.    }
  22. }).addOnFailureListener(new OnFailureListener() {
  23.    @Override
  24.    public void onFailure(Exception e) {
  25.        if (e instanceof ApiException) {
  26.            ApiException apiException = (ApiException) e;
  27.            Log.e("archive","statusCode:" + apiException.getStatusCode());
  28.        }
  29.    }
  30. });

2.   Call the ArchiveDetails.set method to write the current archive file of the player to the ArchiveDetails object.

3.  The game writes the archive metadata (such as the archivee description, progress, and cover image) to the ArchiveSummaryUpdate object.

  1. ArchiveSummaryUpdate archiveSummaryUpdate =
  2.        new ArchiveSummaryUpdate.Builder().setActiveTime(playedTime)
  3.                .setCurrentProgress(progress)
  4.                .setDescInfo(description)
  5.                .setThumbnail(bitmap)
  6.                .setThumbnailMimeType(imageType)
  7.                .build();

4.  Call the ArchivesClient.addArchive method to submit the archive. After the game is successfully saved, the HMS SDK returns the archive metadata to your game as an ArchiveSummary object.

Note:
In this mode, data is cached locally when the device is offline and then submitted to the server when the network recovers. However, data may be lost due to manual operations.
  1. ArchiveDetails archiveDetails = new ArchiveDetails.Builder().build();
  2. archiveDetails.set(content);
  3. Task<ArchiveSummary> addArchiveTask = Games.getArchiveClient(this, signInHuaweiId).addArchive(archiveDetails, archiveSummaryUpdate, isSupportCache);
  4. addArchiveTask.addOnSuccessListener(new OnSuccessListener<ArchiveSummary>() {
  5.    @Override
  6.    public void onSuccess(ArchiveSummary archiveSummary) {
  7.        if (archiveSummary != null) {
  8.            String fileName = archiveSummary.getFileName();
  9.            String archiveId = archiveSummary.getId();
  10.        }
  11.    }
  12. }).addOnFailureListener(new OnFailureListener() {
  13.    @Override
  14.    public void onFailure(Exception e) {
  15.        if (e instanceof ApiException) {
  16.            ApiException apiException = (ApiException) e;
  17.            Log.e("archive","statusCode:" + apiException.getStatusCode());
  18.        }
  19.    }
  20. });

5. Displaying the Saved Game List

When a player requests to browse saved game data, your game needs to display all saved games of the player for the player to choose in either way:

Notice:
HUAWEI AppAssistant displays saved games only for released games. For games in testing, saved games will not be displayed on HUAWEI AppAssistant.

Displaying the saved game list screen of HUAWEI AppAssistant

1.  When a player triggers saved game browsing, the game obtains the Intent object of the saved game list screen of HUAWEI AppAssistant by calling the ArchivesClient.getShowArchiveListIntent method.

2.  Call the startActivityForResult method to open the saved game list screen of HUAWEI AppAssistant.

3.  If a user selects a saved game from the saved game list screen of HUAWEI AppAssistant, your game needs to call ArchivesClient.parseSummary to obtain the corresponding archive metadata. The Bundle object is obtained using the getParcelableExtra method of the Intent object.

  1. Task<Intent> task = archivesClient.getshowArchiveListIntent(title, allowAddBtn, allowDeleteBtn, maxArchive);
  2. task.addOnSuccessListener(new OnSuccessListener<Intent>() {
  3.    @Override
  4.    public void onSuccess(Intent intent) {
  5.        if (intent != null) {
  6.            startActivityForResult(intent, 1);
  7.        }
  8.    }
  9. }).addOnFailureListener(new OnFailureListener() {
  10.    @Override
  11.    public void onFailure(Exception e) {
  12.        if (e instanceof ApiException) {
  13.            ApiException apiException = (ApiException) e;
  14.            Log.e("archive", "statusCode:" + apiException.getStatusCode());
  15.        }
  16.    }
  17. });  
  18. …  
  19. @Override  
  20. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  21.    super.onActivityResult(requestCode, resultCode, data);
  22.    if (requestCode == 1) {
  23.        if (data == null) {
  24.            return;
  25.        }
  26.        if (data.hasExtra(ArchiveConstants.ARCHIVE_SELECT)) {
  27.            Bundle bundle = data.getParcelableExtra(ArchiveConstants.ARCHIVE_SELECT);
  28.            Task<ArchiveSummary> task = getArchivesClient().parseSummary(bundle);
  29.            task.addOnSuccessListener(new OnSuccessListener<ArchiveSummary>() {
  30.                @Override
  31.                public void onSuccess(ArchiveSummary archiveSummary) {
  32.                    if (archiveSummary != null) {
  33.                        // TODO  
  34.                    }
  35.                }
  36.            }).addOnFailureListener(new OnFailureListener() {
  37.                @Override
  38.                public void onFailure(Exception e) {
  39.                    if (e instanceof ApiException) {
  40.                        ApiException apiException = (ApiException) e;
  41.                        Log.e("archive", "statusCode:" + apiException.getStatusCode());
  42.        }
  43.                }
  44.            });
  45.        } else if (data.hasExtra(ArchiveConstants.ARCHIVE_ADD)) {
  46.            // TODO  call addArchive api to add a new Archive
  47.        }
  48.    }
  49. }

Developing your own saved game list screen

When a player triggers saved game browsing, call the ArchivesClient.getArchiveSummaryList method to obtain all archive data from Huawei game server or a local position.

  1. Task<List<ArchiveSummary>> task = archivesClient.getArchiveSummaryList(isRealTime);  
  2. task.addOnSuccessListener(new OnSuccessListener<List<ArchiveSummary>>() {
  3.    @Override
  4.    public void onSuccess(List<ArchiveSummary> list) {
  5.        if (list != null) {
  6.             Log.i("TAG"," list size" + list.size());
  7. }
  8.    }
  9. }).addOnFailureListener(new OnFailureListener() {
  10.    @Override
  11.    public void onFailure(Exception e) {
  12.        if (e instanceof ApiException) {
  13.            ApiException apiException = (ApiException) e;
  14.            Log.e("archive", "statusCode:" + apiException.getStatusCode());
  15.        }
  16.    }
  17. });

To obtain the archive cover, call ArchiveSummary.hasThumbnail to check whether there is a cover thumbnail. If so, call ArchivesClient.getThumbnail to obtain the thumbnail.

  1. private void loadThumbnail(Archive archive){
  2.    if (archive.getSummary().hasThumbnail()) {
  3.        Task<Bitmap> coverImageTask = getArchivesClient().getThumbnail(archive.getSummary().getId());
  4.        coverImageTask.addOnSuccessListener(new OnSuccessListener<Bitmap>() {
  5.            @Override
  6.            public void onSuccess(Bitmap bitmap) {
  7.                Glide.with(getApplicationContext()).load(bitmap).into(archiveImage);
  8.            }
  9.        }).addOnFailureListener(new OnFailureListener() {
  10.            @Override
  11.            public void onFailure(Exception e) {
  12.                if (e instanceof ApiException) {
  13.                    Log.e("archive","load image failed"+ ((ApiException) e).getStatusCode());
  14.                }
  15.            }
  16.        });
  17.    }
  18. }


6. Updating a Saved Game

When a player saves a game again, Huawei game server can update the existing archive file and metadata instead of generating new files to save the storage space.

Note:
Of course, you can determine whether to use the capability based on your needs.

1.  When a player saves a game again, call the ArchivesClient.getArchiveSummaryList method to obtain the archive list of the current player.

2. Obtain the ArchiveSummary metadata from the existing archive data and parse the archive ID.

3. Call the ArchiveDetails.set method to write the current game archive file of the player to the ArchiveDetails object and write the archive metadata to be saved to the ArchiveSummaryUpdate object.

4.  Call the ArchivesClient.updateArchive method to update the archive.

5.  If OperationResult.isDifference returns true during archive reading, a data conflict has occurred. To resolve the conflict, go to Resolving Archive Conflicts.

  1. String description = editTextDescription.getText().toString();
  2. long playedTime = String2Long(editTextPlayedTime.getText().toString());
  3. long progress = String2Long(editTextProgress.getText().toString());
  4. ArchiveSummaryUpdate archiveMetadataChange = builder.build();
  5. ArchiveDetails archiveContents = new ArchiveDetails.Builder().build();
  6. archiveContents.set((progress + description + playedTime).getBytes());
  7. Task<OperationResult> task = getArchivesClient().updateArchive(archiveId, archiveMetadataChange, archiveContents);
  8.    task.addOnSuccessListener(new OnSuccessListener<OperationResult>() {
  9.         @Override
  10.         public void onSuccess(OperationResult archiveDataOrConflict) {
  11.             showLog("isDifference:" + ((archiveDataOrConflict == null) ? "" : archiveDataOrConflict.isDifference()));
  12.         }
  13.    }

7. Loading a Saved Game

A user can choose to load the game from a specified archive.

1.  When a player selects a saved game, call the loadArchiveDetails method of ArchivesClient to read the archive depending on the following situations:

2.  If OperationResult.isDifference returns true during archive reading, a data conflict has occurred. To resolve the conflict, go to Resolving Archive Conflicts.

3.  After the archive is successfully read, call OperationResult.getArchive to obtain the Archive object that contains the archive metadata and file, and then call Archive.getDetails to obtain the ArchiveDetails object that contains the saved game file content.

The sample code is as follows:

  1. int conflictPolicy = getConflictPolicy();  //Specify the conflict resolution policy.
  2. Task<OperationResult> task;
  3. if (conflictPolicy == -1) {
  4.    task = Games.getArchiveClient(this, authHuaweiId).loadArchiveDetails(archiveId);
  5. } else {
  6.    task = Games.getArchiveClient(this, authHuaweiId).loadArchiveDetails(archiveId, conflictPolicy);
  7. }
  8. task.addOnSuccessListener(new OnSuccessListener<OperationResult>() {
  9.    @Override
  10.    public void onSuccess(OperationResult archiveDataOrConflict) {
  11.        Log.i("TAG", "isDifference:" + ((archiveDataOrConflict == null) ? "" : archiveDataOrConflict.isDifference()));
  12.        if (archiveDataOrConflict != null && !archiveDataOrConflict.isDifference()) {
  13.            Archive archive = archiveDataOrConflict.getArchive();
  14.            if (archive != null && archive.getSummary() != null) {
  15.                Log.i("TAG", "ArchiveId:" + archive.getSummary().getId());
  16.            }
  17.        } else {
  18.            //Conflict handling
  19.            handleDifference(archiveDataOrConflict);
  20.        }
  21.    }
  22. }).addOnFailureListener(new OnFailureListener() {
  23.    @Override
  24.    public void onFailure(Exception e) {
  25.        if (e instanceof ApiException) {
  26.            ApiException apiException = (ApiException) e;
  27.            Log.e("archive", "statusCode:" + apiException.getStatusCode());
  28.        }
  29.    }
  30. });

4.  Call the ArchiveDetails.get method to read the binary game data from the archive file and load the data in your game.

  1. archive.getArchiveDetails().get();

8. Resolving Archive Conflicts

When archive data read from the local cache conflicts with that retrieved from Huawei game server during archive reading or updating, Huawei game server returns the conflict details to your game. You can select either of the following conflict resolution mechanisms based on your requirements:

1.  If OperationResult.isDifference returns true during archive reading, a data conflict has occurred. Call OperationResult.getDifference to obtain conflict details.

2.  Your game can select or prompt the user to select one of the conflicting archives as the final archive.

3.  Generate the final archive based on the conflict resolution result.

4.  Call the updateArchive method of ArchivesClient to resolve the conflict.

5. Call the OperationResult.isDifference method to check whether there is still a conflict. If so, repeat the preceding steps until OperationResult.isDifference returns false.

The sample code is as follows:

  1. private void handleDifference(OperationResult operationResult) {
  2.    if (operationResult != null) {
  3.        OperationResult.Difference archiveDifference = operationResult.getDifference();
  4.        Archive openedArchive = archiveDifference.getRecentArchive();
  5.        Archive serverArchive = archiveDifference.getServerArchive();
  6.        // Sample use the server archive to solve diffrence
  7.        if (serverArchive == null) {
  8.            return;
  9.        }
  10.        Task<OperationResult> task = Games.getArchiveClient(this, signInHuaweiId).updateArchive(serverArchive);
  11.        task.addOnSuccessListener(new OnSuccessListener<OperationResult>() {
  12.            @Override
  13.            public void onSuccess(OperationResult operationResult) {
  14.                Log.i("archive","isDifference:"
  15.                        + ((operationResult == null) ? "" : operationResult.isDifference()));
  16.                if (operationResult != null && !operationResult.isDifference()) {
  17.                    Archive archive = operationResult.getRecentArchive();
  18.                    if (archive != null && archive.getSummary() != null) {
  19.                        Log.i("archive","ArchiveId:" + archive.getSummary().getId());
  20.                    }
  21.                } else {
  22.                    handleDifference(operationResult);
  23.                }
  24.            }
  25.        }).addOnFailureListener(new OnFailureListener() {
  26.            @Override
  27.            public void onFailure(Exception e) {
  28.                ApiException apiException = (ApiException) e;
  29.                Log.e("archive" , "resolve result:" + apiException.getStatusCode());
  30.            }
  31.        });
  32.    }
  33. }

9. Deleting a Saved Game

When a player deletes a saved game, call the ArchivesClient.removeArchive method with the archive metadata object in the request.

  1. Task<String> removeArchiveTask = Games.getArchiveClient(this, signInHuaweiId).removeArchive(archiveSummary);
  2. removeArchiveTask.addOnSuccessListener(new OnSuccessListener<String>() {
  3.    @Override
  4.    public void onSuccess(String s) {
  5.        String deletedArchiveId = s;
  6.    }
  7. }).addOnFailureListener(new OnFailureListener() {
  8.    @Override
  9.    public void onFailure(Exception e) {
  10.        if (e instanceof ApiException) {
  11.            ApiException apiException = (ApiException) e;
  12.            Log.e("archive" ,"statusCode:" + apiException.getStatusCode());
  13.        }
  14.    }
  15. });

If a saved game is deleted successfully, the archive ID is returned. If the saved game fails to be deleted, a result code is returned. For details, please refer to Game API Result Codes.