简介

游戏存档是指把玩家的游戏进度存储至华为云空间中,即使用户之前的设备丢失、损毁或换了新设备,只要用户使用相同的华为帐号登录,就可以从华为云空间取得之前的游戏进度继续游戏。
一个存档主要由2部分组成:

属性

说明

存档ID

存档在该游戏中的唯一标识,在提交存档文件后由华为游戏服务器生成。

存档名称

存档保存的文件名,在提交存档文件后由华为游戏服务器生成。

存档描述

存档用于展示给玩家的描述信息,在提交存档文件时由您自行定义,最多1000个字符。

上次修改时间

服务器生成的时间戳(以毫秒为单位),表示上次更新保存的游戏的时间。

游戏时长

您在提交存档文件时提供的玩家在该存档上游戏总时间(以毫秒为单位)。

游戏进度

您在提交存档文件时提供的玩家在该存档上的游戏进度,由您自行定义。比如第几关等,用整数进行识别。

封面图片

您在提交存档文件时提供的存档封面图像,一般来说为游戏存档时的截图,若未提供,则为系统默认图片,最大200KB,支持JPG或PNG格式,长宽比为16:9。

您将建立什么

在本次codelab中,您将建立一个具备简单游戏交互功能的Android应用程序,您的应用程序将包含以下功能:

您将会学到什么

硬件要求

软件要求

需要的知识点

集成HUAWEI HMS Core能力,需要完成以下准备工作:

  1. AppGallery Connect首页,点击"我的项目"->"选择您创建的项目"->"项目设置"->"API 管理",进入服务管理菜单。
  2. 点击 "华为帐号 "和"游戏服务"后面的开关,打开帐号和游戏服务。

添加当前应用的AppGallery Connect配置文件

  1. AppGallery Connect首页,点击"我的项目"->"选择您项目下的应用"->"项目设置"->"常规"->"应用"下的"agconnect-services.json"按钮,下载配置文件。
  2. 将"agconnect-services.json"文件拷贝到应用级根目录下。

配置HMS Core SDK的Maven仓地址

Android Studio的代码库配置在Gradle插件7.0以下版本、7.0版本和7.1及以上版本有所不同。请根据您当前的Gradle插件版本,选择对应的配置过程。

7.0以下版本

7.0版本

7.1及以上版本

7.0以下版本

7.0版本

7.1及以上版本

添加编译依赖

  1. 打开Android Studio应用级build.gradle文件。
  2. 在"dependencies"中添加如下编译依赖。其中:
    • hwid的{version}替换为最新帐号服务版本号,参见版本更新说明
    • game的{version}替换为最新游戏服务版本号,参见版本更新说明
      dependencies { implementation 'com.huawei.hms:hwid:{version}' implementation 'com.huawei.hms:game:{version}' }
  3. 添加agcp插件配置。请根据实际情况选择:
    • 方式一:在文件头部声明的下一行添加如下配置。
      apply plugin: 'com.huawei.agconnect'
    • 方式二:在plugins中添加如下配置。
      plugins { id 'com.android.application' // 添加如下配置 id 'com.huawei.agconnect' }
  4. 点击界面上的"Sync Now"链接下载编译已完成的配置。

配置混淆脚本

编译APK前需要添加混淆配置,避免功能异常。配置步骤如下:

  1. 打开Android Studio工程的混淆配置文件proguard-rules.pro。
  2. 加入混淆配置。
    -ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.hianalytics.android.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;} -keep class com.huawei.gamebox.plugin.gameservice.**{*;} -keep interface com.huawei.hms.analytics.type.HAEventType{*;} -keep interface com.huawei.hms.analytics.type.HAParamType{*;} -keep class com.huawei.hms.analytics.HiAnalyticsInstance{*;} -keep class com.huawei.hms.analytics.HiAnalyticsInstance{*;} -keep class com.huawei.hms.analytics.HiAnalytics{*;}
  3. 当您启用R8资源缩减(项目级"build.gradle"文件中"shrinkResources"属性为"true")和严格引用检查("res/raw/keep.xml"文件中的"shrinkMode"为"strict")时,请您配置"keep.xml"文件手动保留layout资源,确保应用正常通过华为应用市场上架审核。
    <?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/hms_download_progress,@drawable/screen_off,@layout/upsdk*,@drawable/c_buoycircle*,@drawable/hms_game*,@layout/c_buoycircle*,@layout/hms_game*,@strings/hms_game*,@strings/c_buoycircle*" tools:shrinkMode="strict" />

本次Codelab中您可以在Android Studio工程中创建一个主页面,布局参照下图进行UI设计,新增四个Button。"init"点击后调用初始化接口,初始化接口调用成功后自动调用登录接口,"createArchive"点击后调用跳转到创建存档页面,"displayByAppAssistant"点击后跳转到华为应用助手展示存档列表页面, "displayUserSelf"点击后跳转到自定义展示存档列表页面,点击列表条目时,可以实现打开游戏存档的功能,具体页面展示和布局代码在10 开发展示游戏存档列表功能时再详细介绍,此处不展示。

// 布局代码 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btn_init" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:text="init"/> <Button android:id="@+id/btn_createArchive" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:text="createArchive"/> <Button android:id="@+id/btn_displayByAppAssistant" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:text="displayByAppAssistant"/> <Button android:id="@+id/btn_displayUserSelf" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:text="displayUserSelf"/> </LinearLayout>

游戏初始化

  1. 在Application的onCreate方法中添加注册Activity的回调监听。
    public class MyApplication extends Application { @Override public void onCreate(){ super.onCreate(); HuaweiMobileServicesUtil.setApplication(this); } }
  2. 在启动游戏的第一个Activity中,调用JosAppsClient的init方法初始化游戏SDK。以发布中国大陆,含游戏防沉迷回调的代码为例。
    private void init(){ AccountAuthParams params = AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM_GAME; JosAppsClient appsClient = JosApps.getJosAppsClient(this); // 设置防沉迷提示语的context ResourceLoaderUtil.setmContext(this); Task<Void> initTask = appsClient.init(new AppParams(params, new AntiAddictionCallback() { @Override public void onExit() { // System.exit(0); // 该回调会在如下两种情况下返回: // 1.未成年人实名帐号在白天登录游戏,华为会弹框提示玩家不允许游戏,玩家点击"确定",华为返回回调 // 2.未成年实名帐号在国家允许的时间登录游戏,到晚上9点,华为会弹框提示玩家已到时间,玩家点击"知道了",华为返回回调 // 您可在此处实现游戏防沉迷功能,如保存游戏、调用帐号退出接口或直接游戏进程退出(如System.exit(0)) }})); initTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { showLog("init success"); hasInit = true; // 一定要在init成功之后,才可以调用登录接口 signIn(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException) { ApiException apiException = (ApiException) e; int statusCode = apiException.getStatusCode(); if (statusCode == JosStatusCodes.JOS_PRIVACY_PROTOCOL_REJECTED) { // 错误码为7401时表示用户未同意华为联运隐私协议 showLog("has reject the protocol"); // 此处您需禁止玩家进入游戏 } else if (statusCode == GamesStatusCodes.GAME_STATE_NETWORK_ERROR) { // 错误码7002表示网络异常 showLog("network error"); // 此处您可提示玩家检查网络,请不要重复调用init接口,否则断网情况下可能会造成手机高耗电。 } else if (statusCode == 907135003) { // 907135003表示玩家取消HMS Core升级或组件升级 showLog("init statusCode=" + statusCode); init(); } else { // 在此处实现其他错误码的处理 } } } }); }

游戏登录

  1. 游戏登录接口必须在init回调成功之后调用。
  2. 调用AccountAuthManager的getService方法初始化AccountAuthService对象,并调用AccountAuthService的silentSignIn方法实现静默登录。如果静默登录失败,当返回错误码为2002时,调用AccountAuthService的getSignInIntent方法拉起华为帐号登录授权页面,登录结果在onActivityResult里回调。需要注意的是:使用存档相关的功能,在登录时需要调用AccountAuthParamsHelper.setScopeList申请DRIVE_APP_DATA的Scope。
    /** * 登录,返回已登录此应用的华为帐号登录信息(或者错误信息) */ List<Scope> scopes = new ArrayList<>(); scopes.add(GameScopes.DRIVE_APP_DATA); AccountAuthParams mAuthParams = new AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM_GAME).setScopeList(scopes).createParams(); private void signIn(){ showLog("begin login and current hasInit=" + hasInit); // 一定要在init成功后,才可以调用登录接口 Task<AuthAccount> authAccountTask = AccountAuthManager.getService(this, mAuthParams).silentSignIn(); authAccountTask.addOnSuccessListener(new OnSuccessListener<AuthAccount>() { @Override public void onSuccess(AuthAccount authAccount) { showLog("signIn success"); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException) { ApiException apiException = (ApiException) e; int statusCode = apiException.getStatusCode(); if (2002 == statusCode){ showLog("start getSignInIntent"); signInNewWay(); }else { showLog("signIn failed:" + statusCode); } } } }); } /** * 获取到华为帐号登录授权页面的Intent,并通过调用startActivityForResult(Intent, int)打开华为帐号登录授权页面。 */ private void signInNewWay() { Intent intent = AccountAuthManager.getService(MainActivity.this, mAuthParams).getSignInIntent(); startActivityForResult(intent, SIGN_IN_INTENT); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (data == null){ return; } if (SIGN_IN_INTENT == requestCode) { handleSignInResult(data); }else { showLog("unknown requestCode in onActivityResult"); } } /** * 登录授权的结果响应处理方法 * @param data Data */ private void handleSignInResult(Intent data) { String jsonSignInResult = data.getStringExtra("HUAWEIID_SIGNIN_RESULT"); if (TextUtils.isEmpty(jsonSignInResult)) { showLog("SignIn result is empty"); return; } try { AccountAuthResult signInResult = new AccountAuthResult().fromJson(jsonSignInResult); if (0 == signInResult.getStatus().getStatusCode()) { showLog("Sign in success."); } else { showLog("Sign in failed: " + signInResult.getStatus().getStatusCode()); } } catch (JSONException var7) { showLog("Failed to convert json from signInResult."); } } private static final String TAG = "MainActivity"; /** * 打印日志 * @param msg */ private void showLog(String msg){ Log.d(TAG,msg); }

在使用游戏存档接口的能力前都需要先调用Games.getArchiveClient获取ArchivesClient实例。示例代码如下:

ArchivesClient client = Games.getArchiveClient(this);
  1. 新建游戏存档时需要把游戏数据写入存档文件中,开发过程中根据实际情况获取,在本次codelab中,设计了一个简单的页面,数据手动输入,布局显示如下:

字段名称

字段描述

description

存档描述。

playedTime

游戏时长。

progress

游戏进度。

isCache

是否支持本地缓存。选中"YES"表示支持本地缓存,选中"NO"表示不支持本地缓存。

hasImage

是否有封面图面,此处显示的是一个固定的图片,可以根据实际业务需求修改。选中"YES"表示有封面图片,选中"NO"表示没有封面图片。

submit

点击此按钮,调用新建存档接口。

// 布局代码 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <TextView android:id="@+id/tv_des" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:text="description:"/> <EditText android:id="@+id/et_des" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toEndOf="@+id/tv_des" android:layout_toRightOf="@+id/tv_des" android:hint="Please enter the description." android:textSize="16sp" /> <TextView android:id="@+id/tv_playedTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/et_playedTime" android:text="playedTime:" android:textSize="16sp" /> <EditText android:id="@+id/et_playedTime" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/et_des" android:layout_alignLeft="@+id/et_des" android:layout_marginTop="20dp" android:hint="Please enter the playedTime." android:textSize="16sp" /> <TextView android:id="@+id/tv_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/et_progress" android:textSize="16sp" android:text="progress:"/> <EditText android:id="@+id/et_progress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/et_playedTime" android:layout_alignLeft="@+id/et_des" android:layout_marginTop="20dp" android:hint="Please enter the progress." android:textSize="16sp" /> <TextView android:id="@+id/tv_isSupportCache" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/rg_isCache" android:textSize="16sp" android:layout_marginTop="10dp" android:text="isCache:"/> <RadioGroup android:id="@+id/rg_isCache" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/et_des" android:layout_below="@+id/et_progress" android:orientation="horizontal"> <RadioButton android:id="@+id/rb_cache_yes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="YES"/> <RadioButton android:id="@+id/rb_cache_no" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:text="NO"/> </RadioGroup> <TextView android:id="@+id/tv_hasImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/rg_hasImage" android:textSize="16sp" android:layout_marginTop="10dp" android:text="hasImage:"/> <RadioGroup android:id="@+id/rg_hasImage" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/et_des" android:layout_below="@+id/rg_isCache" android:orientation="horizontal"> <RadioButton android:id="@+id/rb_hasImage_yes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="YES"/> <RadioButton android:id="@+id/rb_hasImage_no" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:text="NO"/> </RadioGroup> <ImageView android:id="@+id/iv_cover_pic" android:layout_width="200dp" android:layout_height="200dp" android:layout_below="@+id/rg_hasImage" android:layout_centerHorizontal="true" android:src="@mipmap/cover_picture" /> <Button android:id="@+id/submit" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/iv_cover_pic" android:layout_marginTop="20dp" android:textAllCaps="false" android:text="submit"/> </RelativeLayout> </ScrollView>
  1. 应用需要使用ArchiveDetails.set方法将玩家当前的游戏存档文件写入ArchiveDetails对象。
    String description = etDes.getText().toString(); long playedTime = Long.parseLong(TextUtils.isEmpty(etPlayedTime.getText().toString()) ? "0": etPlayedTime.getText().toString()); long progress = Long.parseLong(TextUtils.isEmpty(etProgress.getText().toString()) ? "0" : etProgress.getText().toString()); ArchiveDetails archiveDetails = new ArchiveDetails.Builder().build(); archiveDetails.set((progress + description + playedTime).getBytes());
  2. 游戏将存档元数据(如存档描述、游戏进度、封面图片等)写入ArchiveSummaryUpdate对象。
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.cover_picture); ArchiveSummaryUpdate.Builder builder = new ArchiveSummaryUpdate.Builder(); builder.setActiveTime(playedTime) .setCurrentProgress(progress) .setDescInfo(description); ArchiveSummaryUpdate archiveSummaryUpdate; if (hasImage){ archiveSummaryUpdate = builder.setThumbnail(bitmap) .setThumbnailMimeType("png").build(); }else { archiveSummaryUpdate = builder.build(); }
  3. 调用ArchivesClient.addArchive(ArchiveDetails details, ArchiveSummaryUpdate update, boolean isSupportCache)方法以异步方式提交存档,存档提交成功后HMS Core SDK将以ArchiveSummary对象方式向应用返回存档成功后的存档元数据。
Task<ArchiveSummary> archiveSummaryTask = client.addArchive(archiveDetails, archiveSummaryUpdate, isCache); archiveSummaryTask.addOnSuccessListener(new OnSuccessListener<ArchiveSummary>() { @Override public void onSuccess(ArchiveSummary archiveSummary) { if (archiveSummary != null){ String fileName = archiveSummary.getFileName(); String archiveId = archiveSummary.getId(); String descInfo = archiveSummary.getDescInfo(); showLog("create archive success descInfo:" + descInfo + ",fileName:" + fileName+ ",archiveId:" + archiveId); finish(); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException){ ApiException apiException = (ApiException) e; int statusCode = apiException.getStatusCode(); if(statusCode == 7216){ Log.e(TAG,"Check whether the format of the archive cover image is correct."); }else if(statusCode == 7218){ Log.e(TAG,"The game service is disabled."); }else if(statusCode == 7013){ // 未登录华为帐号 You have not logged in to your Huawei ID. signIn(); }else if(statusCode == 7213){ //存档个数达到上限 The number of archived files has reached the upper limit. Log.e(TAG,"The number of archived files has reached the upper limit."); }else{ Log.e(TAG,"statusCode:"+ statusCode); } } } }); private static final String TAG = "CreateArchiveActivity"; /** * Print logs * 打印日志 * @param msg */ private void showLog(String msg){ Log.d(TAG,msg); }

玩家可以在存档入口选择浏览存档记录,此时游戏需要展示所有的存档记录供用户选择。存档记录选择界面的展示方法分为2种:

  1. 直接展示应用助手的存档页面:游戏需要获取应用助手存档界面的Intent对象,然后通过startActivityForResult方法拉起存档选择页面,您不需要自己开发存档展示页面。
  2. 自行展示存档列表:获取存档数据,自行开发存档记录页面。

直接展示应用助手的存档列表

  1. 当玩家进入存档入口时,应用调用ArchivesClient.getShowArchiveListIntent方法获取应用助手存档页面对应的Intent对象。当取得存档列表界面对应的Intent对象后,应用可以调用startActivityForResult方法直接拉起华为应用助手的存档列表页面。
    Task<Intent> showArchiveListIntentTask = mClient.getShowArchiveListIntent("游戏存档", true, true, -1); showArchiveListIntentTask.addOnSuccessListener(new OnSuccessListener<Intent>() { @Override public void onSuccess(Intent intent) { if (intent != null){ startActivityForResult(intent,1); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException){ ApiException apiException = (ApiException) e; Log.e(TAG, "statusCode:" + apiException.getStatusCode()); } } });
  2. 如果用户在应用助手存档列表中选择一个存档查看,游戏需要调用ArchivesClient.parseSummary获取选择的存档元数据,Bundle对象使用Intent对象的getParcelableExtra方法获取。
    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (data == null){ return; } if (1 == requestCode){ if (data.hasExtra(ArchiveConstants.ARCHIVE_SELECT)){ Bundle bundle = data.getParcelableExtra(ArchiveConstants.ARCHIVE_SELECT); Task<ArchiveSummary> archiveSummaryTask = mClient.parseSummary(bundle); archiveSummaryTask.addOnSuccessListener(new OnSuccessListener<ArchiveSummary>() { @Override public void onSuccess(ArchiveSummary archiveSummary) { if (archiveSummary != null){ String archiveId = archiveSummary.getId(); String descInfo = archiveSummary.getDescInfo(); String fileName = archiveSummary.getFileName(); showLog("get archive info success archiveId:" + archiveId + ",fileName:" + fileName+ ",descInfo:" + descInfo); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException){ ApiException apiException = (ApiException) e; Log.e(TAG, "statusCode:" + apiException.getStatusCode()); } } }); }else if (data.hasExtra(ArchiveConstants.ARCHIVE_ADD)){ // 调用addArchive添加存档 } }else { showLog("unknown requestCode in onActivityResult"); } } private static final String TAG = "MainActivity"; /** * Print logs * 打印日志 * @param msg */ private void showLog(String msg){ Log.d(TAG,msg); }

自行展示存档列表

  1. 自行展示存档列表页面是由开发者自行设计,这里以展示存档封面图片和存档描述为例,页面展示效果如下:
    // 主界面布局代码 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rcv_archive" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> // 列表中每个条目的布局代码 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingHorizontal="15dp" android:paddingVertical="10dp" android:gravity="center_vertical"> <ImageView android:id="@+id/iv_archive_icon" android:layout_width="50dp" android:layout_height="50dp" android:src="@mipmap/ic_launcher"/> <TextView android:id="@+id/tv_archive_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textColor="@color/black" android:layout_marginLeft="15dp"/> </LinearLayout>
  2. 玩家进入存档页面入口时,游戏可以调用ArchivesClient.getArchiveSummaryList方法获取所有存档列表,支持从本地缓存或华为游戏服务器获取。获取到数据之后,通过自定义的RecyclerView列表适配器,把数据显示到页面上。
    Task<List<ArchiveSummary>> archiveSummaryListTask = mArchivesClient.getArchiveSummaryList(false); archiveSummaryListTask.addOnSuccessListener(new OnSuccessListener<List<ArchiveSummary>>() { @Override public void onSuccess(List<ArchiveSummary> archiveSummaries) { if (archiveSummaries != null){ mSummaries = archiveSummaries; Log.i(TAG, " archiveSummaries size" + archiveSummaries.size()); mArchiveAdapter = new ArchiveAdapter(archiveSummaries,DisplayArchiveUserselfActivity.this,DisplayArchiveUserselfActivity.this); rcv_archive.setAdapter(mArchiveAdapter); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException){ ApiException apiException = (ApiException) e; Log.e(TAG, "statusCode:" + apiException.getStatusCode()); } } }); // 适配器代码截取数据处理部分 ArchiveSummary archiveSummary = summaries.get(position); holder.tv_archive_desc.setText(archiveSummary.getDescInfo()); if (archiveSummary.hasThumbnail()){ Task<Bitmap> thumbnailTask = mClient.getThumbnail(archiveSummary.getId()); thumbnailTask.addOnSuccessListener(new OnSuccessListener<Bitmap>() { @Override public void onSuccess(Bitmap bitmap) { holder.iv_archive_icon.setImageBitmap(bitmap); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException){ ApiException apiException = (ApiException) e; Log.e("ArchiveAdapter","statusCode:" + apiException.getStatusCode()); } } }); }

打开游戏存档是指玩家进入游戏后加载之前提交的存档继续进行游戏。如果您打开华为应用助手展示的存档,华为应用助手会自动加载打开游戏存档功能;如果您打开自行展示的存档,请参考本章节完成打开游戏存档功能。建议调用ArchivesClient.getArchiveSummaryList方法获取的存档,不要打开本地缓存的存档。
在游戏存档时可能存在同一个帐号在多个设备中同时存档的情况,如果设备因网络问题存档延时,则可能会发生数据冲突。当更新存档或打开存档时发现两份存档的存档属性对比存在不一致时,华为游戏服务器会返回对应的冲突结果给您,并允许您选择使用其中某份存档作为最终使用的存档,或者也允许您自主混合存档数据作为最终使用的存档。

  1. 玩家选择一个存档加载时,应用调用ArchivesClient的loadArchiveDetails方法加载存档文件,有以下几种方式选择。
  2. 如果结果中的public boolean isDifference()返回true,表示加载存档时发生数据冲突,您需要进行数据冲突处理,详细步骤参见冲突处理,本codelab暂不做详细说明。
  3. 当存档加载成功后,应用可以使用OperationResult.getArchive获取包含存档元数据和存档文件内容的Archive对象,再使用Archive.getDetails方法获取包含存档文件内容的ArchiveDetails对象。
    点击存档列表条目时,加载游戏存档数据。示例代码如下:
    mArchiveAdapter.setOnItemClickListener(new ArchiveAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { ArchiveSummary archiveSummary = mSummaries.get(position); Task<OperationResult> operationResultTask = mArchivesClient.loadArchiveDetails(archiveSummary, ArchiveConstants.STRATEGY_TOTAL_PROGRESS); operationResultTask.addOnSuccessListener(new OnSuccessListener<OperationResult>() { @Override public void onSuccess(OperationResult operationResult) { Log.i(TAG, "isDifference:" + ((operationResult == null) ? "" : operationResult.isDifference())); if (operationResult != null && !operationResult.isDifference()) { Archive archive = operationResult.getArchive(); if (archive != null && archive.getSummary() != null) { ArchiveSummary summary = archive.getSummary(); String archiveId = summary.getId(); String descInfo = summary.getDescInfo(); String fileName = summary.getFileName(); Log.i(TAG, "get archive info success archiveId:" + archiveId + ",fileName:" + fileName+ ",descInfo:" + descInfo); } } else { //处理冲突 Log.i(TAG, "Handling Conflicts"); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException) { ApiException apiException = (ApiException) e; Log.e(TAG, "statusCode:" + apiException.getStatusCode()); } } }); } });
  4. 获取存档文件内容后,应用可以使用ArchiveDetails.get方法读取存档文件中的二进制游戏数据,读取完成后将游戏数据加载给用户。
    archive.getDetails().get();

恭喜您,您已经成功完成了Codelab并学到了:

您可以阅读下面链接,了解更多相关的信息。

本Codelab中所用API请参考游戏服务的存档相关API介绍

游戏存档官方文档介绍,请参考存档

相关错误码说明,请参考错误码

源码下载地址:源码下载

Code copied