简介

根据国家新闻出版署的最新规定,发布中国大陆的所有网络游戏必须接入国家新闻出版署网络游戏防沉迷实名验证系统,并且仅可在周五、周六、周日和法定节假日每日20时至21时向未成年人提供1小时网络游戏服务,其他时间均不得以任何形式向未成年人提供网络游戏服务,并且游戏付费服务需要对未成年人支付充值金额设限。
华为平台已为您实现了整套防沉迷的控制逻辑,您只需编写少量代码即可实现防沉迷。本codelab针对发布中国大陆的游戏,演示如何通过华为帐号登录游戏,并快速高效实现防沉迷功能。

您将建立什么

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

您将会学到什么

通过这个Codelab,您将学到:

硬件要求

软件要求

需要的知识点

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

  1. AppGallery Connect首页点击"我的项目",在项目列表中选择您创建好的项目,在"项目设置"页面中选择"API管理"。
  2. 点击 "华为帐号 "和"游戏服务"后面的开关,开启华为帐号和游戏服务。开关打开后,建议等15分钟以后再测试游戏服务相关功能。

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

  1. AppGallery Connect首页点击"我的项目",在项目下面中点击您的应用。
  2. 在"项目设置"页面中,点击"应用"栏下的"agconnect-services.json"下载配置文件。
  3. 将"agconnect-services.json"文件拷贝到应用级根目录下。

配置HMS Core SDK的Maven仓地址

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

7.0以下版本

7.0版本

7.1及以上版本

添加编译依赖

  1. 打开应用级的"build.gradle"文件。
  2. 在"dependencies"中添加如下编译依赖。
    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工程中创建如下布局页面。其中,Init按钮点击后可触发初始化接口,SignIn按钮点击后可以触发登录接口,GetGamePlayer按钮用来触发获取玩家信息接口。

<!--布局代码--> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:id="@+id/init_btn" android:layout_width="match_parent" android:layout_height="@dimen/btn_height" android:background="@color/btn_background" android:gravity="center" android:text="@string/init_text" android:textColor="@color/text_color" android:textSize="@dimen/button_size"/> <TextView android:id="@+id/login_btn" android:layout_width="match_parent" android:layout_height="@dimen/btn_height" android:background="@color/btn_background" android:layout_marginTop="@dimen/margin_top" android:gravity="center" android:text="@string/login_text" android:textColor="@color/text_color" android:textSize="@dimen/button_size" /> <TextView android:id="@+id/player1_btn" android:layout_width="match_parent" android:layout_height="@dimen/btn_height" android:background="@color/btn_background" android:gravity="center" android:layout_marginTop="@dimen/margin_top" android:text="@string/game_player" android:textColor="@color/text_color" android:textSize="@dimen/button_size" /> </LinearLayout>

如果您希望游戏对接国家版署,您可向当地的新闻出版局申请接入网络游戏防沉迷实名认证系统,并获取"bizID(游戏备案识别码)",再将bizID配置到AppGallery Connect,华为会自动对接国家新闻出版署的实名认证系统并开启强制实名认证,无需您进行额外的开发,具体操作请参见版署实名认证申请

  1. 在Application的onCreate方法中添加注册Activity的回调监听。

    代码如下:

    public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); HuaweiMobileServicesUtil.setApplication(this); } }
  2. 在应用启动的第一个Activity中,调用JosAppsClient的init方法初始化游戏SDK,并在init接口传参中注册AntiAddictionCallback防沉迷回调,当满足以下条件时游戏会将结果回调给您,您需要在回调中实现触发防沉迷后的游戏保存、退出等操作。
    • 已实名的未成年人在非规定游戏时间内登录游戏,游戏服务会弹框提示玩家不允许游戏,玩家点击"确定"。
    • 已实名的未成年人在规定时间内登录游戏,当游戏进行到晚上21时,游戏服务会弹框提示玩家已到游戏时间,玩家点击"知道了"。

代码如下:

public void init() { AccountAuthParams params = AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM_GAME; JosAppsClient appsClient = JosApps.getJosAppsClient(this); // Set the anti-addiction prompt context, this line must be added // 设置防沉迷提示语的Context ResourceLoaderUtil.setmContext(this); Task<Void> initTask = appsClient.init(new AppParams(params, new AntiAddictionCallback() { @Override public void onExit() { // Implement the game addiction prevention function, such as saving the game and invoking the account exit interface. // 在此处实现游戏防沉迷功能,如保存游戏、调用帐号退出接口 showLog("onExit"); } })); initTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { showLog("init success"); playersClient = Games.getPlayersClient(MainActivity.this); // The login interface can be invoked only after the init is successful. Otherwise, error code 7018 is displayed. // 一定要在init成功后,才可以调用登录接口,否则登录会提示7018错误码 // signIn(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { showLog("init failed, " + e.getMessage()); if (e instanceof ApiException) { ApiException apiException = (ApiException) e; int statusCode = apiException.getStatusCode(); // Error code 7401 indicates that the user did not agree to Huawei joint operations privacy agreement // 错误码为7401时表示用户未同意华为联运隐私协议 if (statusCode == JosStatusCodes.JOS_PRIVACY_PROTOCOL_REJECTED) { showLog("has reject the protocol"); // You can exit the game or re-call the init interface. // 在此处实现退出游戏或者重新调用初始化接口 } // Handle other error codes. // 在此处实现其他错误码的处理 } } }); } /** * Log output * 日志输出 */ public void showLog(String logLine) { StringBuffer sbLog = new StringBuffer(); DateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:SS", Locale.ENGLISH); String time = format.format(new Date()); sbLog.append(time).append(":").append(logLine); Log.i(TAG, sbLog.toString()); }
  1. 帐号登录接口必须在init回调成功之后调用。
  2. 调用AccountAuthManager的getService方法初始化AccountAuthService对象,并调用AccountAuthService的silentSignIn方法实现静默登录。如果静默登录失败,调用AccountAuthService的getSignInIntent方法拉起华为帐号授权页面,显式登录结果在onActivityResult里回调。
    /** * 游戏登录需要先调用华为帐号认证登录完成之后,再调用getCurrentPlayer或者getGamePlayer接口获取游戏玩家信息 * 1.先调用华为帐号登录接口的静默登录接口,此接口对于已经授权登录过的应用不会再次拉起登录页面。 * 2.静默登录失败一般是由于登录需要授权,此时在回调中调用显示登录接口拉起登录授权页面进行登录认证。 * 登录结果会在onActivityResult回调中获取,可在此时调用游戏获取玩家信息接口。 */ public void signIn() { Task<AuthAccount> authAccountTask = AccountAuthManager.getService(this, getHuaweiIdParams()).silentSignIn(); authAccountTask.addOnSuccessListener( authAccount -> { Toast.makeText(MainActivity.this, "signIn success", Toast.LENGTH_LONG).show(); showLog("signIn success"); }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { if (e instanceof ApiException) { ApiException apiException = (ApiException) e; Toast.makeText(MainActivity.this, "signIn failed:" + apiException.getStatusCode(), Toast.LENGTH_LONG).show(); showLog("signIn failed:" + apiException.getStatusCode()); signInNewWay(); } } }); } /** * 获取到华为帐号登录授权页面的Intent,并通过调用startActivityForResult(Intent, int)打开华为帐号登录授权页面。 */ public void signInNewWay() { Intent intent = AccountAuthManager.getService(MainActivity.this, getHuaweiIdParams()).getSignInIntent(); startActivityForResult(intent, SIGN_IN_INTENT); } public AccountAuthParams getHuaweiIdParams() { return new AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM_GAME).createParams(); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == SIGN_IN_INTENT) { if (null == data) { showLog("signIn intent is null"); return; } String jsonSignInResult = data.getStringExtra("HUAWEIID_SIGNIN_RESULT"); if (TextUtils.isEmpty(jsonSignInResult)) { showLog("signIn result is empty"); return; } try { HuaweiIdAuthResult signInResult = new HuaweiIdAuthResult().fromJson(jsonSignInResult); if (0 == signInResult.getStatus().getStatusCode()) { showLog("signIn success"); } else { showLog("signIn failed: " + signInResult.getStatus().getStatusCode()); } } catch (JSONException var7) { showLog("Failed to convert json from signInResult."); } } }

在华为帐号登录成功后,游戏调用PlayersClient.getGamePlayer()获取玩家信息。

/** * Obtains user information about gamers. * 获取游戏玩家用户信息 */ public void getGamePlayer() { playersClient = Games.getPlayersClient(this); Task<Player> playerTask = playersClient.getGamePlayer(); playerTask.addOnSuccessListener(new OnSuccessListener<Player>() { @Override public void onSuccess(Player player) { showLog("getPlayerInfo Success"); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { // 获取玩家信息失败 if (e instanceof ApiException) { showLog("getPlayerInfo failed, status: " + ((ApiException) e).getStatusCode()); if (7400 == ((ApiException) e).getStatusCode() || 7018 == ((ApiException) e).getStatusCode()) { // error code 7400 indicates that the user has not agreed to the joint operations privacy agreement // error code 7018 indicates that the init API is not called. // 7400表示用户未签署联运协议,需要继续调用init接口 // 7018表示初始化失败,需要继续调用init接口 init(); }else if (GamesStatusCodes.GAME_STATE_NETWORK_ERROR == ((ApiException) e).getStatusCode()) { //Error code 7002 indicates that the network is abnormal. You can prompt the player to check the network. // 错误码7002表示网络异常,此处您可提示玩家检查网络 showLog( "Network error"); } } } }); }
  1. 测试游戏初始化功能:
    点击Init按钮,控制台查看打印日志,在Verbose日志中搜索关键字"Game_codeLab",打印"init success"即初始化成功。
  2. 测试华为帐号登录功能:
    点击SignIn按钮,华为帐号授权登录成功之后,看到欢迎提示弹窗,如下图;或在Verbose日志中搜索关键字"Game_codeLab,打印"signIn success."即登录成功。
  3. 测试获取玩家信息功能:
    点击getGamePlayer或者getCurrentPlayer按钮,在Verbose日志中搜索关键字"Game_codeLab",打印"getPlayerInfo success",即获取玩家信息成功。
  4. 测试防沉迷功能:
    • 不在周五、周六、周日和法定节假日每日20时至21时时间段,用实名未成年华为帐号,完成上述3步后,弹出提示框,点击页面上确定按钮,在Verbose日志中搜索关键字"Game_codeLab",打印"onExit",说明防沉迷功能生效。
    • 在周五、周六、周日和法定节假日每日20时至21时时间段,用实名未成年华为帐号,完成上述3步后,成功进入游戏,并在超出时间段后弹出如下弹窗,点击弹窗上"知道了"按钮,在Verbose日志中搜索关键字"Game_codeLab",打印"onExit",也说明防沉迷生效。

祝贺您,您已经成功完成了Codelab,并学到了:

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

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

源码下载地址:源码下载

Code copied