# GHOME SDK 开发手册 for Android

更新日期:2023.04.20

# 1. 前言

本文用于指导游戏开发商接入SDK,文中包含客户端的接入说明,以及服务器接口的介绍;

# 2. SDK接入流程概述

  • 搭建SDK开发环境

  • 完成隐私政策展示逻辑(请务必在完成隐私的展示逻辑之后再进行调用初始化 以及敏感权限申请等操作)(游戏使用sdk隐私政策弹框请接接口4; 游戏自己实现隐私政策弹框请接接口5接口6

  • 初始化SDK

  • 初始化成功,调用账号功能相关接口

  • 用户在进行购买时,调用支付功能相关接口

  • 其他功能接口,可根据业务需要适时调用

  • 接入第三方渠道的个性化功能时,可调用SDK扩展接口实现

  • 销毁SDK

  • 游戏在启动游戏时需对设备网络进行判断,若无网络的情况下,游戏需实现对用户的弹框提示。

  • GHome sdk中已占用的申请系统权限的code: 157, 159( 游戏请勿使用这些code进行权限申请,以免冲突)

# 3. 开发环境要求

# 3.1. 集成SDK文件到游戏工程

  • 将压缩包中的assets目录下的所有文件和文件夹拷贝到游戏工程的assets目录下。
  • 将压缩包中的libs目录下的所有文件和文件夹拷贝到游戏工程的libs目录下。
  • 将压缩包中的res目录下的所有文件和文件夹拷贝到游戏工程的res目录下。
  • 将压缩包中的混淆文件拷贝到游戏安卓工程的混淆文件中(如果游戏开启混淆的话,否则无需关注)。

# 3.2. 配置AndroidManifest.xml文件

注意:
1、如果文档和SDK中的AndroidManifest.xml文件存在差异,请以SDK中的文件为准
2、targetSdkVersion值必须大于等于26
3、必须有versionCode设置项,且该项必须有赋值
4、游戏主activity启动模式不能设置为singleTask (如果游戏有自身需求需要设置为该模式, 则需要重点回归测试下第三方支付在支付中回到桌面再切换回游戏等场景是否会有卡住的问题)
5、需要在app 的build.gradle内增加一下一行配置manifestPlaceholders.put("APPLOG_SCHEME","rangersapplog.byAx6uYt".toLowerCase()), 示例:


apply plugin: 'com.android.application'

android {
    compileSdkVersion 29

    defaultConfig {
        applicationId "com.ghome.sdk.demo"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "8.3.3.0"

        manifestPlaceholders.put("APPLOG_SCHEME","rangersapplog.byAx6uYt".toLowerCase())
    }
    ......
    ......
    ......
 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 请在游戏工程的AndroidManifest.xml文件中增加以下权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- 二维码所需权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.VIBRATE"/>

<!-- 叨鱼登录 begin -->
<uses-permission android:name="com.sdo.sdaccountkey.openapi.provider.READ_PERMISSION" />
<uses-permission android:name="com.sdo.sdaccountkey.openapi.provider.WRITE_PERMISSION" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<!-- 叨鱼登录 end -->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 修改游戏工程的AndroidManifest.xml文件中Application节点:

修改Application类为“com.ghome.sdk.GHomeApplication”,

<application
    android:name="com.ghome.sdk.GHomeApplication"
    android:usesCleartextTraffic="true"
    android:requestLegacyExternalStorage="true">
1
2
3
4

如果游戏已经有自己相应的Application(比如GameApplication),那么需要改为继承GHomeApplication类,并且需要在子类里重写一下onCreate(有些渠道需求特殊导致需要这样重写一下):

<application
    android:name="com.ghome.sdk.GameApplication"
    android:usesCleartextTraffic="true"
    android:requestLegacyExternalStorage="true">


public class GameApplication extends GHomeApplication {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 请在游戏工程的AndroidManifest.xml文件中增加以下Activity等信息(若lint提示错误信息,请关闭lint检查功能):
<!-- QQ sdk 里的网络请求代码未及时适配安卓新系统 需要加入这条配置规避问题-->
<uses-library android:name="org.apache.http.legacy" android:required="false" />

<activity
    android:name="你的包名.wxapi.WXPayEntryActivity"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:exported="true"
    android:launchMode="singleTop"
    android:screenOrientation="portrait"/>

<activity
    android:name="com.sina.weibo.sdk.web.WeiboSdkWebActivity"
    android:configChanges="keyboardHidden|orientation"
    android:exported="false"
    android:windowSoftInputMode="adjustResize" >
</activity>

<!-- QQ登录,如不支持QQ可以删除 begin    -->
<activity
    android:name="com.tencent.tauth.AuthActivity"
    android:noHistory="true"
    android:launchMode="singleTask" >
    <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="tencent+游戏再的QQ平台申请的ID" />
    </intent-filter>
</activity>

<activity android:name="com.tencent.connect.common.AssistActivity"
        android:theme="@android:style/Theme.Translucent.NoTitleBar"
        android:configChanges="orientation|keyboardHidden|screenSize"
/>
<!-- QQ登录,如不支持QQ可以删除 end -->

<activity
android:name="com.ghomesdk.gameplus.pay.PayActivity"
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:screenOrientation="behind"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:windowSoftInputMode="adjustResize"></activity>

<!-- 二维码扫码 -->
<activity
    android:name="com.ghomesdk.gameplus.qrcode.QRCodeScanActivity"
    android:screenOrientation="portrait"
    android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
<activity
    android:name="com.gsk.TouchMatrixActivity"
    android:screenOrientation="portrait"
    android:theme="@android:style/Theme.NoTitleBar.Fullscreen"></activity>
<!-- alipay sdk begin -->
<activity
    android:name="com.alipay.sdk.app.H5PayActivity"
    android:configChanges="orientation|keyboardHidden|navigation|screenSize"
    android:exported="false"
    android:theme="@android:style/Theme.NoTitleBar" >
</activity>
<activity
    android:name="com.alipay.sdk.app.H5AuthActivity"
    android:configChanges="orientation|keyboardHidden|navigation|screenSize"
    android:exported="false"
    android:theme="@android:style/Theme.NoTitleBar" >
</activity>
<activity
    android:name="com.alipay.sdk.app.PayResultActivity"
    android:configChanges="orientation|keyboardHidden|navigation|screenSize"
    android:exported="true"
    android:launchMode="singleInstance"
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >
</activity>
<activity
    android:name="com.alipay.sdk.app.AlipayResultActivity"
    android:exported="true"
    android:launchMode="singleTask"
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >
</activity>
<activity
    android:name="com.alipay.sdk.app.H5OpenAuthActivity"
    android:configChanges="orientation|keyboardHidden|navigation|screenSize"
    android:exported="false"
    android:screenOrientation="behind"
    android:windowSoftInputMode="adjustResize|stateHidden" >
</activity>
<activity
    android:name="com.alipay.sdk.app.APayEntranceActivity"
    android:configChanges="orientation|keyboardHidden|navigation|screenSize"
    android:exported="false"
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >
 </activity>
<!-- alipay sdk end -->

<!--快手渠道必须加入 start-->
            <!-- 放在游戏主Activity节点内部 -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.BROWSABLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <!--  scheme定义规则 ks_ghome_[游戏名称缩写] -->
                <data
                    android:host="home"
                    android:scheme="ks_ghome_qyn" />
            </intent-filter>
            <!--快手渠道必须加入 end-->

<!-- weibo sdk start-->
        <activity
            android:name="com.sina.weibo.sdk.web.WebActivity"
            android:configChanges="orientation|screenSize"
            android:exported="false"
            android:windowSoftInputMode="adjustResize"
            tools:replace="configChanges" />
        <activity
            android:name="com.sina.weibo.sdk.share.ShareTransActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:exported="true"
            android:launchMode="singleTask"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            tools:replace="configChanges" >
            <intent-filter>
                <action android:name="com.sina.weibo.sdk.action.ACTION_SDK_REQ_ACTIVITY" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

<!-- weibo sdk end-->


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
  • (一般无需关注):AndroidManifest.xml中可配置 是否对 获取手机状态权限 进行申请:
<!-- 是否需要申请手机状态权限 ,ghome sdk默认true 无需修改-->
        <meta-data android:name="NEED_READ_PHONE_STATE" android:value="true"/>

1
2
3

# 3.3. 增加Proguard配置信息

  • 若您使用Proguard混淆游戏代码,请加入以下配置:
-dontwarn com.ghome.**
-keep class com.ghome.** {*;}
-dontwarn com.ghomesdk.gameplus.**
-keep class com.ghomesdk.gameplus.** {*;}
-dontwarn com.alipay.android.**
-keep class com.alipay.android.** {*;}
-dontwarn com.gsk.**
-keep class com.gsk.** {*;}


-dontwarn com.tencent.smtt.sdk.WebView
-dontwarn com.tencent.smtt.sdk.WebChromeClient

-dontwarn androidx.annotation.Nullable
-dontwarn androidx.annotation.NonNull
-dontwarn com.google.android.gms.ads.identifier.AdvertisingIdClient
-dontwarn com.google.android.gms.ads.identifier.AdvertisingIdClient$Info
-dontwarn androidx.appcompat.app.AlertDialog
-dontwarn androidx.appcompat.view.menu.ListMenuItemView
-dontwarn androidx.recyclerview.widget.RecyclerView
-dontwarn androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-dontwarn androidx.viewpager.widget.ViewPager
-dontwarn androidx.recyclerview.widget.RecyclerView
-dontwarn androidx.annotation.RequiresApi
-dontwarn androidx.fragment.app.FragmentActivity
-dontwarn androidx.fragment.app.Fragment
-dontwarn androidx.annotation.AnyThread
-dontwarn androidx.annotation.WorkerThread

-keep class com.bytedance.applog.picker.DomSender { public *; }

-keep class com.bytedance.dr.VivoIdentifier {*;}
-keep class com.bytedance.dr.VivoIdentifier$* {*;}

-dontwarn org.bouncycastle.**
-keep class org.bouncycastle.** {*;}
-keep class XI.vs.K0.**{*;}
-keep class XI.CA.XI.**{*;}
-keep class XI.K0.XI.**{*;}
-keep class XI.XI.K0.**{*;}
-keep class XI.xo.XI.XI.**{*;}
-keep class com.asus.msa.SupplementaryDID.**{*;}
-keep class com.asus.msa.sdid.**{*;}
-keep class com.bun.lib.**{*;}
-keep class com.bun.miitmdid.**{*;}
-keep class com.huawei.hms.ads.identifier.**{*;}
-keep class com.samsung.android.deviceidservice.**{*;}
-keep class com.zui.opendeviceidlibrary.**{*;}
-keep class org.json.**{*;}
-keep public class com.netease.nis.sdkwrapper.Utils {public <methods>;}
-dontwarn com.reyun.tracking.**
-keep class com.reyun.tracking.** {*;}

-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}
-keep class com.alipay.sdk.app.H5PayCallback {
    <fields>;
    <methods>;
}
-keep class com.alipay.android.phone.mrpc.core.** { *; }
-keep class com.alipay.apmobilesecuritysdk.** { *; }
-keep class com.alipay.mobile.framework.service.annotation.** { *; }
-keep class com.alipay.mobilesecuritysdk.face.** { *; }
-keep class com.alipay.tscenter.biz.rpc.** { *; }
-keep class org.json.alipay.** { *; }
-keep class com.alipay.tscenter.** { *; }
-keep class com.ta.utdid2.** { *;}
-keep class com.ut.device.** { *;}

-keep class com.tencent.mm.opensdk.** { *;}
-keep class com.tencent.wxop..** { *;}

-dontwarn com.ta.utdid2.**
-dontwarn com.ut.device.**

-dontwarn com.alipay.mobilesecuritysdk.**
-dontwarn com.alipay.security.**

-dontwarn android.net.SSLCertificateSocketFactory
-dontoptimize
-dontusemixedcaseclassnames
-keep public class *
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-verbose

# 不混淆open sdk, 避免有些调用(如js)找不到类或方法
-keep class com.tencent.connect.** {*;}
-keep class com.tencent.open.** {*;}
-keep class com.tencent.tauth.** {*;}

# 叨鱼登录相关start
-keep class com.shengqu.daoyu.opensdk.model.** { *; }
-keep class com.shengqu.daoyu.opensdk.openapi.DYAPI{ *; }
-keep class com.shengqu.daoyu.opensdk.openapi.DYAPIEventHandler{ *; }
-keep class com.shengqu.daoyu.opensdk.openapi.DYAPIFactory{ *; }
-keep class * implements com.shengqu.daoyu.opensdk.openapi.DYAPI { *; }
# 叨鱼登录相关end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

# 3.4. 接口回调相关code常量定义

常量类所在包路径 com.ghome.sdk.common.Constants :

public static final int ERROR_CODE_SUCCESS = 0;
public static final int ERROR_CODE_LOGIN_CANCEL = -100;
public static final int ERROR_PAY_FAILED = -10869604;
public static final int ERROR_USER_NOT_LOGIN = -10869607;
public static final int ERROR_LOGIN_FAILED = -10869608;
public static final int ERROR_PAY_CANCEL = -10869609;
public static final int ERROR_INIT_FAILED = -10869610;
public static final int ERROR_OPERATION_NOT_SUPPORTED = -10869613;

public static final int ERROR_CODE_QR_LOGIN_SUCCESS = -10869637;
public static final int ERROR_CODE_QR_PAY_SUCCESS = -10869638;
public static final int ERROR_CODE_QR_RESULT = -10869639;
public static final int ERROR_CODE_QR_CANCELED = -10869640;

public static final int EVENT_ACCOUNT_LOGOUT = 1;
public static final int EVENT_GAME_FORCE_CLOSE = 2;
public static final int EVENT_ACCOUNT_CHANGED = 3;
public static final int EVENT_ACCOUNT_LOGOUT_ONLY = 4;
public static final int EVENT_EXIT_GAME = 5;


常量类所在包路径 com.ghome.sdk.common.IGHomeApi :

public static final int EXTEND_COMMAND_SUBMIT_EXTEND_DATA = 1003;
public static final int EXTEND_COMMAND_EXIT_PAGE = 1005;

public static class FloatIconPosition{
public static final int LeftTop = 0;    // 悬浮窗显示在左上角
public static final int LeftMiddle = 1;   // 悬浮窗显示在左边中间
public static final int LeftBottom = 2;   // 悬浮窗显示在左下角
public static final int RightTop = 3;   // 悬浮窗显示在右上角
public static final int RightMiddle = 4;  // 悬浮窗显示在右边中间
public static final int RightBottom = 5;  // 悬浮窗显示在右下角
public static final int MiddleTop = 6;    // 悬浮窗显示在上边中间
public static final int MiddleBottom = 7; // 悬浮窗显示在下边中间
}

文档接口里使用的Callback所在包路径:
com.ghome.sdk.common.IGHomeApi.Callback;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 4. 调用sdk的隐私协议展示功能

注意(重要):

游戏需使用sdk的隐私政策功能的话则必接,否则不要接

游戏可调用该接口展示用户协议(隐私协议相关逻辑都交给sdk处理, 游戏只需在启动游戏调用该接口并等待同意与否的回调即可)

  • 接口:

    doPrivacyAgreement(final Activity activity, final String appId, final Callback callback);

  • 参数说明:

    activity: Activity对象。

    appId:游戏在手游直通车平台上申请得到的ID。

    callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    如游戏是自己实现隐私协议相关弹框展示功能则无需调用该接口, 否则必接

  • 代码示例:

GHome.getInstance().doPrivacyAgreement(mContext, "791000008", new IGHomeApi.Callback() {
            @Override
            public void callback(int code, String message, Map<String, String> data) {
                if (code == Constants.ERROR_CODE_SUCCESS) {
                    //调用了sdk的隐私政策功能并得到同意,可继续游戏后续运行逻辑
                } else {
                    //玩家不同意隐私政策,游戏可根据自身需求 做直接退出游戏等操作
                    //finish();
                }
            }
        });

1
2
3
4
5
6
7
8
9
10
11
12

# 5. 游戏调用该接口获取是否需要展示隐私协议弹框(游戏使用自己的隐私弹框逻辑的话则必接,否则不要接)

  • 说明: 游戏在两种场景之前需要调用该接口获取是否显示隐私协议弹框。 场景1: 启动游戏 场景2: 调用GHome的login接口之前

  • 接口:

    getAgreementStatus(Activity activity, String appId, boolean isForLogin, Callback callback);

  • 参数说明:

    activity: Activity对象。

    appId:游戏在手游直通车平台上申请得到的ID。

    isForLogin: 是否是在去调用login接口之前的查询(true),(对应于启动游戏的查询-false)

    callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    如游戏是自己实现隐私协议相关弹框展示功能则必接,否则请勿调用该接口; 并且在同意与否的操作时 需要调用反馈接口

  • 代码示例:

//启动游戏 false
GHome.getInstance().getAgreementStatus(mContext, mContext.gameId, false, new IGHomeApi.Callback() {
            @Override
            public void callback(int code, String message, Map<String, String> data) {
                if (code == 0 && data != null && data.get("status").equals("1")) {
                    //游戏方自己处理隐私政策展示功能
                    AlertDialog agreeDialog = new AlertDialog.Builder(mContext).setMessage(data.get("privacyContent")).setNegativeButton("不同意", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            GHome.getInstance().onUserAgreement(mContext, true, false);
                            //TODO exit game
                        }
                    }).setPositiveButton("同意并继续", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            GHome.getInstance().onUserAgreement(mContext, true, true);
                            //TODO 继续初始化或者登录行为
                        }
                    }).create();
                    agreeDialog.setCancelable(false);
                    agreeDialog.show();
                }else{
                     mGHome.login(mContext, mContext.loginCallback);
                }
            }
        });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 6. 隐私协议结果反馈接口(游戏自己实现的隐私政策则必接,否则不接)

游戏需要在隐私协议弹窗后,根据用户选择同意与否调用该接口。

  • 接口:

    onUserAgreement(Activity activity, boolean isUserAction, boolean isAgreed);

  • 参数说明:

    activity: Activity对象。

    isUserAction:是否用户主动同意隐私协议(主动选择true,其他选择或者选第一次选择之后后面默认选项false)

    isAgreed: 是否同意隐私协议

  • 注意事项: 如游戏没有隐私协议弹窗,则需要在后台配置参数,弹出SDK自带隐私协议弹窗。

  • 代码示例:

//第一次用户选择弹框同意
GHome.getInstance().onUserAgreement(getActivity(), true, true);
//之后用户默认同意
GHome.getInstance().onUserAgreement(getActivity(), false, true);

1
2
3
4
5

# 7. 基础功能(必接)

# 7.1. SDK初始化接口(需要在用户同意隐私政策之后调用)

游戏客户端需要在游戏启动的第一个Activity的onCreate()方法中调用该接口(注意在super.onCreate(); 和 GHome.getInstance().onCreate(this); 之后),在调用GHome SDK其他API前需先调用此接口对SDK进行初始化。否则可能无法正常使用后续的接口。如果游戏在自身初始化的时候有动态权限申请,务必在权限申请完成后再调用G家初始化接口。

  • 接口:

    initialize(final Activity activity, final String gameId, final Callback callback);

  • 参数说明:

activity: Activity对象。

gameId: 游戏在手游直通车平台上申请得到的ID。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    游戏在启动游戏时需对设备网络进行判断,若无网络的情况下,游戏需实现对用户的弹框提示。

    如果没有特别说明,在初始化接口成功之前调用其他接口都有可能出现错误,请确保初始化接口成功之后再调用其他接口。

  • 代码示例:

GHome.getInstance().initialize(this, "791000008", new Callback(){

  @Override
  public void callback(int code, String message, Map<String, String> data) {
    // success
    if(code == Constants.ERROR_CODE_SUCCESS){
      //初始化成功
    }  else if(code == Constants.ERROR_INIT_FAILED){
      //初始化失败 无需处理
    } else if(code == Constants.EVENT_ACCOUNT_LOGOUT){
      // 这里已经注销账号了,游戏需要在这里处理自己注销账号的逻辑(可在注销之后调用登录接口拉登录界面)
    } else if(code == Constants.EVENT_ACCOUNT_CHANGED){
      // 此处切换账号已经成功,游戏可以使用此处获得的userid和ticket去游戏服务端做登陆数据验证
      // 如果游戏需要,可以在登录成功后,通过data获取到以下信息。
      // userid是该玩家在我们平台的用户id号;
      // ticket是本次登录得到的票据,用来给游戏服务器向平台服务器验证本次登陆是否合法;
      System.out.println("userid=" + data.get("userid"));
      System.out.println("ticket=" + data.get("ticket"));
    } else if(code == Constants.EVENT_ACCOUNT_LOGOUT_ONLY){
      // 这里已经注销账号了,游戏需要在这里处理自己注销账号的逻辑(注销即可,不调登录)
    } else if(code == Constants.EVENT_EXIT_GAME){
      //当通过渠道悬浮窗点退出游戏,悬浮窗已退出通知游戏做退出游戏操作。游戏不需要弹退出确认框
    }
  }
  
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 7.2. SDK踢下线通知接口

踢下线注销SDK账号接口(注:需要游戏客户端在接收到被踢下线消息时调用该接口)

  • 接口:

    kickOff(final Activity activity)

  • 参数说明:

activity: Activity对象。

  • 注意事项:

    建议在用户被踢下线时调用该接口

  • 代码示例:

GHome.getInstance().kickOff(activity);
1

# 8. 账号功能(必接)

# 8.1. 账号体系说明

  • 注册&登录原理

    1. 在游戏登录界面打开SDK注册&登录界面;

    2. 用户完成注册&登录之后,SDK把用户id和ticket票据返回游戏;

    3. 游戏如果有服务器,需要把客户端获取到ticket票据发送到“登录票据验证接口”(参考5.4节)进行验证,然后把返回的用户信息(用户id和账号)保存到游戏服务器;

  • 注册&登录时线图
    Android3

# 8.2. 客户端登录

# 8.2.1 客户端登录接口

游戏客户端可以调用登录接口为玩家提供登录功能。若用户第一次登录,将显示登录和注册的界面。若用户上次使用账号成功登录过,则会用该账号自动登录。 (重要:如果游戏使用自己的隐私政策弹框功能, 则需要在调用login之前调用查询是否需要弹隐私政策的接口,查询接口说明请参考 接口5

  • 接口:

    login(final Activity activity, final Callback callback);

  • 参数说明:

activity: Activity对象。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    通常情况游戏不需要提示用户登录结果,无论是登录失败还是登录成功,SDK都有Toast提示。

  • 代码示例:

GHome.getInstance().login(this, new Callback() {

  @Override
  public void callback(int code, String message, Map<String, String> data) {
    // 登陆结果的回调。
    // 开发者可以仅处理登陆成功的情况。
    if (code == Constants.ERROR_CODE_SUCCESS) {
      // 如果游戏需要,可以在登录成功后,通过data获取到以下信息。
      // userid是该玩家在我们平台的用户id号;
      // ticket是本次登录得到的票据,用来给游戏服务器向平台服务器验证本次登陆是否合法;
      // isGuest如果有返回并且字符串是"true"表示是游客,不返回或其他表示非游客。
      System.out.println("userid=" + data.get("userid"));
      System.out.println("ticket=" + data.get("ticket"));
      System.out.println("isGuest=" + "true".equal(data.get("isGuest")==null?"":data.get("isGuest")));

      // 登录成功,进入游戏界面,游戏开发者补充逻辑。
    }else if(code == Constants.ERROR_INIT_NOT_FINISHED){
      //可在此处再次调用初始化接口

      //mContext.loginEnter = true;
      //mContext.gameId = "791000008";
      //mGHome.initialize(mContext, mContext.gameId, mContext.initCallback);
    }else if (code == Constants.ERROR_CODE_LOGIN_CANCEL) {
      // 游戏客户端需要在这里处理返回至游戏界面,玩家点击屏幕或者登录按键时再次调用login接口弹出登录框
    }else{
      //登录失败无需处理
    }
  }

});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 8.2.2 登录接口额外配置(微信 和 叨鱼登录需要)

1、AndroidManifest下必须添加

    <activity
    <!-- 这里为游戏微信登录的配置,游戏需要修改为自己的包名加.wxapi.WXEntryActivity -->
        android:name="com.ghome.sdk.demo.wxapi.WXEntryActivity"
        android:exported="true"
        android:label="WXEntryActivity"
        android:launchMode="singleTop"
        android:theme="@android:style/Theme.Translucent.NoTitleBar"
        android:taskAffinity="com.ghome.sdk.demo">
        <!-- 这里为游戏微信登录的配置,游戏需要修改为自己的包名 -->
    </activity>
1
2
3
4
5
6
7
8
9
10

2、在工程下新建一个package,命名为工程的包名+wxapi;
3、在这个package下面新建一个类,命名为WXEntryActivity;
4、这个类必须继承GHomeWXEntryActivity。

  • 代码示例:
public class WXEntryActivity extends GHomeWXEntryActivity{
}
1
2

5、若接入的GHome SDK 版本为7.0.0.0 及以上需要继续完成以下 6,7,8,9步骤;
6、AndroidManifest.xml中添加如下配置.

  • 代码示例:
<activity
    <!-- 这里为游戏叨鱼登录的配置,游戏需要修改为自己的包名加.dyapi.DYEntryActivity -->
        android:name="com.ghome.sdk.demo.dyapi.DYEntryActivity"
        android:configChanges="keyboardHidden|orientation|screenSize"
        android:exported="true"
        android:launchMode="singleTask"
        android:theme="@android:style/Theme.Translucent.NoTitleBar" />
1
2
3
4
5
6
7

7、在工程下新建一个package,命名为工程的包名+dyapi;
8、在这个package下面新建一个类,命名为DYEntryActivity;
9、这个类必须继承DaoYuHandlerActivity.

  • 代码示例:
public class DYEntryActivity extends DaoYuHandlerActivity {
    @Override
    public void onResp(OpenApiResp openApiResp) {
        DaoyuLogin.onDaoyuAuthCallback(this, openApiResp);
    }
}
1
2
3
4
5
6

# 8.3. 客户端注销接口

游戏客户端可以在游戏内需要注销账号的地方调用该接口,执行成功将删除自动登录的记录信息,玩家下次登录后将不再自动登录,而是显示出登录界面。

  • 接口:

    logout(final Activity activity, final Callback callback);

  • 参数说明:

activity: Activity对象。

  • 注意事项:

暂无

  • 代码示例:
GHome.getInstance().logout(this, new Callback() {
  @Override
  public void callback(int code, String message, Map<String, String> data) {
    if (code == Constants.ERROR_CODE_SUCCESS) {
      // 注销成功
    } else {
      // 注销失败
    }
  }
});
1
2
3
4
5
6
7
8
9
10

# 8.4. 服务器登录票据验证接口

  • 用法说明

    为了保证游戏服务器的安全性,游戏服务器在登记用户登录信息之前需要到G家平台服务器做一个验证

  • 接口地址

    https://mservice.sdo.com/v1/open/ticket

  • 传值方式

    GET

  • 参数列表

    appid: 游戏对应的APPID

    timestamp: 精确到秒的unix时间戳

    sequence: 不重复的请求序列号(全局唯一)

    ticket_id: 从客户端登录接口返回的ticket字符串;

    sign: 签名串(参考:“附录A:服务器端签名算法”,签名原始串示例:appid=xxx&sequence=xxx&ticket_id=xxx & timestamp=xxxappsecretkey)

  • 返回结果

    返回结果按json编码,数据格式为:

{
  code:0, #返回状态,0为成功,1为失败,2  签名错误7 osap商户不存在,3001 票据不存在或超过有效期或被反复验证,1003 票据申请的appId和验证的APPID不一致
  msg:"ok", #错误信息
  data:{
    userid:123456, #平台用户id,纯数字,无类型。
    token:"40E00263-16A7-8CFA-D0E8-C951D683EA24", #平台TOKEN,36位字符串,暂时不用
    phone:"+86-139****6893" #手机帐号,带国际区号,可用于显示帐号
    invitation_code:"34343"  #邀请码
    thirdId:'34343'  #第三方账号ID
    companyId:"172" #第三方公司ID
    "userAttribute": "6" #6表示游客,其他值表示非游客
    adult_flag:2  #0未实名,1未成年,2成年
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 8.5. 切换账号接口

游戏里有切换账号入口的情况应调用这个接口来实现切换账号功能。

  • 接口:

    switchAccount(final Activity activity, final Callback agreementCallback, final Callback callback);

  • 参数说明:

activity: Activity对象。

agreementCallback: 用于接收隐私协议弹框的通知,收到该回调时游戏根据status状态弹出自己的隐私弹框,切记如果游戏方使用的sdk的隐私弹框功能则该参数传null对象 或 不传)

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项: 无

  • 代码示例:

//游戏使用sdk的隐私弹框功能, 第二个参数则传 null: GHome.getInstance().switchAccount(mContext, null, callback);

//游戏使用自己实现的隐私弹框功能,第二个参数传入agreementCallback对象, 示例如下:
GHome.getInstance().switchAccount(mContext, new IGHomeApi.Callback() {
            @Override
            public void callback(int code, String message, Map<String, String> data) {
                if (code == 0) {
                    //弹游戏自己的隐私政策弹框(通过data.get("privacyContent") 取内容)
                    if (data != null && data.get("status") != null && data.get("status").equals("1")) {
                        //模拟游戏方自己处理隐私政策展示功能 start
                        GameAgreementOperationDialog agreementDialog = new GameAgreementOperationDialog(getContext());
                        agreementDialog1.setContentUrl(data.get("privacyContent"));
                        agreementDialog1.setOperationListener(new GameAgreementOperationDialog.GameAgreementOperationListener() {
                            @Override
                            public void onOperateCallback(boolean isAgree) {
                                GHome.getInstance().onUserAgreement(mContext, true, isAgree);
                            }
                        });
                        agreementDialog.show();
                        //模拟游戏方自己处理隐私政策展示功能 end
                    } else {
                        GHome.getInstance().onUserAgreement(mContext, false, true);
                    }
                } else {
                }
            }
        }, new Callback() {
            @Override
            public void callback(int code, String msg, Map<String, String> data) {
                // 切换账号结果的回调。
                // 切换账号失败会有Toast提示,所以开发者可以不处理切换账号失败的情况。
                if (code == Constants.ERROR_CODE_SUCCESS) {
                    // 切换账号成功,通过data返回账号信息(获取用户信息同登录接口)。
                    // userid是该玩家在我们平台的用户id号;
                    // ticket是本次切换账号得到的票据,用来给游戏服务器向平台服务器验证本次登陆是否合法;
                    // isGuest如果有返回并且字符串是"true"表示是游客,不返回或其他表示非游客。
                    System.out.println("userid=" + data.get("userid"));
                    System.out.println("ticket=" + data.get("ticket"));
                    System.out.println("isGuest=" + "true".equal(data.get("isGuest") == null ? "" : data.get("isGuest")));
                } else if (code == Constants.ERROR_CODE_LOGIN_CANCEL) {
                    // 代表切换账号取消,但是此时已经是登出状态了
                    // 游戏客户端需要在这里做退出游戏还是重新打开登陆界面的处理
                } else {
                    //默认是切换账号失败,无需处理
                }
            }
        });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 8.6. 客户端注销删除账号接口

游戏客户端可以在游戏内提供注销账号的场所调用该接口,需要在收到注销回调成功之后退出回到登录。

  • 接口:

    writtenOff(final Activity activity, final Callback writtenOffCallback);

  • 参数说明:

activity: Activity对象。

  • 注意事项:

暂无

  • 代码示例:
GHome.getInstance().writtenOff(this, new Callback() {
  @Override
  public void callback(int code, String message, Map<String, String> data) {
    if (code == Constants.ERROR_CODE_SUCCESS) {
      // 注销删除账号成功,游戏这里退出游戏登录态回到登录页即可,(同logout处理
    } else {
      // 注销删除账号取消或者失败 无需处理
    }
  }
});
1
2
3
4
5
6
7
8
9
10

# 9. 支付功能(必接)

# 9.1. 支付原理说明

  • 支付原理

    1. 游戏客户端发起支付请求;

    2. SDK服务器收到请求,并发送至第三方支付;

    3. 第三方支付成功收费以后通知SDK服务器,再由SDK服务器通知游戏服务器发货至游戏,需要游戏提供“订单通知接口”进行接收(参考6.3节);

  • 支付时线图

Android4

# 9.2. 客户端支付接口

游戏客户端可以在购买道具的地方调用该接口。这将打开支付界面,引导玩家支付该订单。用户结束支付操作后,SDK会回调游戏客户端,告知支付结果是否已经成功。

  • 接口:

    pay(final Activity activity, final String groupId, final String orderId, final String areaId, final String productId, final String extend, final Callback callback);

  • 参数说明:

activity: Activity对象。

groupId: 必传,组ID。没有则传空串

orderId: 必传,游戏订单号。如果游戏需要记录订单号,可以传入唯一的字符串来标识此订单。

areaId: 必传,在手游直通车平台上设置的区服的ID 区服ID不可为0

productId: 必传,在手游直通车平台上设置的商品的ID

extend: 可选,扩展参数,游戏可用于自行扩展,平台发货通知时将原样返回给游戏服务器。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    由于可能发生网络延迟等等原因,有时候客户端返回的支付结果可能与实际情况有出入,所以最终订单是否支付成功,建议以平台服务器端的通知为准,客户端的通知结果仅作为参考。

  • 代码示例:

GHome.getInstance().pay(this, "111", "game order id", "1", "p001", "", new Callback() {
  @Override
  public void callback(int code, String message, Map<String, String> data) {
    if (code == Constants.ERROR_CODE_SUCCESS) {
      // 支付成功,游戏客户端可以在这里查询游戏服务器是否收到发货通知
      // 如果游戏服务器已经收到发货通知可以向用户发放道具
    } else {
      // 支付暂未成功,这种状态可能是用户未完成支付,也可能是尚未收到渠道的发货通知
      // 游戏客户可以不做处理,用户下次登录时再做查询
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12

# 9.3. 微信支付接入步骤

1、在工程下新建一个package,命名为工程的包名+wxapi;
2、在这个新建的package下面新建一个类,命名为WXPayEntryActivity;
3、这个类必须继承GhomeWXPayActivity。

  • 代码示例:
public class WXPayEntryActivity extends GHomeWXPayActivity{

}
1
2
3

# 9.4. 服务器订单通知接口

  • 用法说明

    用于将用户支付结果通知给游戏。

  • 接口地址

    接口地址需要游戏提供,可以在开发商后台“我的游戏 > 区服管理”中配置。

  • 传值方式

    POST

  • 参数列表

    orderNo: 服务器的订单号

    userId:用户ID

    gameOrderNo: 游戏的订单号

    product: 商品ID(在开发商后台“我的游戏 > 充值管理”中配置)当游戏服务器发货是按照游戏自己记录的商品id发货时,请保证这里返回的商品id是否与游戏自己记录的商品id一致,方可发货;如不一致,游戏可以拒绝发货!

    extend:游戏发送过来的扩展信息,原格式返回

    sign:签名串(参考“附录A:服务器端签名算法”)

    time:到账时间

  • 参数示例

orderNo=791000012PP016140210105937000001&userId=18178&gameOrderNo=NONE&product=com.winggod.jingzhuan&extend=NONE&time=1392004960&sign=ad08d9e2d7b7df6603bc431392d1c707
1
  • 返回结果

    返回字符串:success,表示成功

    返回字符串:refund,表示需要退款,目前支持的退款渠道有:官方支付宝、官方微信

    其他表示失败。

# 10. 扩展功能(可选)

# 10.1. 客户端获取游戏区服列表接口

游戏客户端可以调用该接口获取游戏在G家平台登记的区服配置。

  • 接口:

    getAreaConfig(final Activity activity, final Callback callback);

  • 参数说明:

activity: Activity对象。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

暂无

  • 代码示例:
GHome.getInstance().getAreaConfig(this, new Callback() {
  @Override
  public void callback(int code, String message, Map<String, String> data) {
    if (code == 0) {
      String json = data.get("data"));
    }
  }
});
1
2
3
4
5
6
7
8
  • JSON格式说明:
{
  "message":[
  {
    "area_code":"1",  // 开发商后台配置的区服ID
    "name":"area1"    // 开发商后台配置的区服名称
    "group_id":"1"    // 开发商后台配置的区组ID
    "group_name":"gourp1"    // 开发商后台配置的区组名称
  },
  ...
  ]
}
1
2
3
4
5
6
7
8
9
10
11

# 10.2. 客户端获取游戏商品列表接口

游戏客户端可以调用该接口获取游戏在G家平台登记的商品配置。

  • 接口:

    getProductConfig(final Activity activity, final Callback callback);

  • 参数说明:

activity: Activity对象。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

暂无

  • 代码示例:
GHome.getInstance().getProductConfig(this, new Callback() {
  @Override
  public void callback(int code, String message, Map<String, String> data) {
    if (code == 0) {
      String json = data.get("data"));
    }
  }
});
1
2
3
4
5
6
7
8
  • JSON格式说明:
{
  "message":[
  {
    "product_code":"com.gold100",  // 开发商后台配置的产品ID
    "item_name":"100 Gold",       // 开发商后台配置的产品名称
    "money":"10.00",          // 开发商后台配置的产品金额
    "type":0              // 0:Android游戏,1:IOS游戏,2:Android和IOS游戏
  },
  ...
  ]
}
1
2
3
4
5
6
7
8
9
10
11

# 10.3. 客户端获取一次性登录票据接口

游戏客户端在接入其他需要登录G家平台的应用时(如:客服、论坛等。),可以调用该接口获取一个一次性小票提供个应用完成登录。

  • 接口:

    getTicket(final Activity activity, final String appId, final String areaId, final Callback callback);

  • 参数说明:

activity: Activity对象。

appId: 需要使用该票据完成登录的应用ID。

areaId: 需要使用该票据完成登录的应用区号,如果该应用没有区的概念可以传"0"。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    必须在登录成功之后才可以使用该接口。

  • 代码示例:


GHome.getInstance().getTicket(mContext, appId, areaId, new Callback() {
                @Override
                public void callback(int code, String msg, Map<String, String> data) {
                    if (code == Constants.ERROR_CODE_SUCCESS) {
                        if (data != null) {
                            final String ticket = (String) data.get("ticket");
                            //获取成功  继续游戏验证登录逻辑。
                        } else {
                            DemoToastUtil.showToast(mContext, msg);
                        }
                    } else if(code == Constants.ERROR_NETWORK_TIMEOUT){
                        // 票据获取失败,但可以重试(包含网络问题)
                        // 游戏停留当前UI,并提示用户返回的 msg
                        // 用户点击msg关闭信息窗口后,可以点击“进入游戏”继续(重新走getticket流程)
                        DemoToastUtil.showToast(mContext, msg);
                    }else {
                        // 票据获取失败,但重试也可能回复(服务端有明确的特别错误码)
                        // 游戏停留当前UI,并提示用户返回的 msg
                        // 用户点击msg关闭信息窗口后,建议游戏实现App关闭(也可以关闭并重启)
                        DemoToastUtil.showToast(mContext, "code:" + code + " msg:" + msg);
                    }
                }
            });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 10.4. 打开二维码扫码

启动二维码扫码界面。(使用场景: 游戏PC端二维码登录/支付)

  • 接口:

    scanQRCode(final Activity activity, final String extend, final Callback callback);

  • 参数说明:

activity: Activity对象。

extend: 扩展字段,该字段会在PC端扫码成功之后原样返回。

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    必须在登录成功之后才可以使用该接口。

  • 代码示例:

GHome.getInstance().scanQRCode(this, "randomInfo", new Callback() {
  @Override
  public void callback(int code, String message, Map<String, String> data) {
    if (code == Constants.ERROR_CODE_QR_LOGIN_SUCCESS) {
      // 扫码登录成功
    }
    else if (code == Constants.ERROR_CODE_QR_PAY_SUCCESS) {
      // 扫码支付成功
    }
    else if (code == Constants.ERROR_CODE_QR_RESULT) {
      //扫码授权结果信息,可以取data的qrdata 具体信息显示
      System.out.println("qrdata=" + data != null ? data.get("qrdata") : message);
      ToastUtil.showMessage(getActivity(), data != null ? data.get("qrdata") : message);
    }
    else if (code == Constants.ERROR_CODE_QR_CANCELED) {
      System.out.println("QR scan canceled"));
    }else if (code == Constants.ERROR_PAY_FAILED) {
      Log.debug(TAG, "QRCODE_DEBUG扫码支付失败!!! ");
    }else if (code == Constants.ERROR_USER_NOT_LOGIN) {
      Log.debug(TAG, "QRCODE_DEBUG未登录!!! ");//游戏收到后可以弹窗提示 和 拉起登录让用户重登
    } 
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 11. 渠道接口(必接)

# 11.1. 客户端上报游戏区接口

游戏客户端在用户选择区时需要调用该接口,否则数据报表将无法按区提供。

  • 接口:

    loginArea(final Activity activity, final String areaId, final String groupId);

  • 参数说明:

activity: Activity对象。

areaId: 用户进入的区号。

groupId: 必传,组号, 没有可传空串

  • 注意事项:

暂无

  • 代码示例:
GHome.getInstance().loginArea(this, "1", "99");
1

# 11.2. 客户端主Activity中需要接入的生命周期以及权限回调接口

  • 代码示例:
@Override
protected void onCreate() {
  super.onCreate();
  GHome.getInstance().onCreate(this);
}

@Override
protected void onResume() {
  super.onResume();
  GHome.getInstance().onResume(this);

@Override
protected void onStart() {
  super.onStart();
  GHome.getInstance().onStart(this);
}

@Override
protected void onRestart() {
  super.onRestart();
  GHome.getInstance().onRestart(this);
}

@Override
protected void onPause() {
  super.onPause();
  GHome.getInstance().onPause(this);
}

@Override
protected void onStop() {
  super.onStop();
  GHome.getInstance().onStop(this);
}

@Override
protected void onDestroy() {
  super.onDestroy();
  GHome.getInstance().onDestroy(this);
}

@Override
protected void onNewIntent(Intent intent) {
  super.onNewIntent(intent);
  GHome.getInstance().onNewIntent(this, intent);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
  super.onActivityResult(requestCode, resultCode, intent);
  GHome.getInstance().onActivityResult(this, requestCode, resultCode, intent);
}

@Override
public void onBackPressed() {
  super.onBackPressed();
  GHome.getInstance().onBackPressed(this);
}

//申请权限回调接口
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  GHome.getInstance().onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}

@Override
    public void onWindowFocusChanged(boolean hasFocus){
        super.onWindowFocusChanged(hasFocus);
        GHome.getInstance().onWindowFocusChanged(this, hasFocus);
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# 11.3. 客户端显示/隐藏悬浮窗接口

游戏客户端需要在游戏登录成功之后显示悬浮窗,注销之后隐藏悬浮窗,否则可能无法通过渠道上线审核。

  • 接口:

    showFloatIcon(final Activity activity, final boolean show, final int position);

  • 参数说明:

activity: Activity对象。

show: true显示悬浮窗、false隐藏悬浮窗。

position: 悬浮窗显示位置(部分渠道不支持悬浮窗位置设置,指定该参数可能没有效果)。

  • 注意事项:

  • 代码示例:

// 在登成功之后调用
GHome.getInstance().showFloatIcon(this, true, IGHomeApi.FloatIconPosition.LeftTop);

// 在账号注销之后调用
GHome.getInstance().showFloatIcon(this, false, IGHomeApi.FloatIconPosition.LeftTop);
1
2
3
4
5

# 11.4. 第三方渠道SDK扩展接口

游戏客户端,请依据文档,在各个对应场景实现如下接口。

  • 接口:

    doExtend(final Activity activity, final int command, final Map<String, String> params, final Callback callback);

  • 参数说明:

activity: Activity对象。

command: 扩展接口编号(如: IGHomeApi.EXTEND_COMMAND_SUBMIT_EXTEND_DATA)

params: 请求参数,类型为Map<String, String>

callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 注意事项:

    请严格按照文档要求在游戏内各个节点接入该接口。

  • 可从callback的data中获取到调用时候对应传入的"command"值,代码示例:

Callback callback = new Callback() {

  @Override
  public void callback(int code, String message, Map<String, String> data){
    //从data中获取到"command", 该方式适用于游戏使用同一个callback来调用doExtend接口的不同功能逻辑
     int command = Integer.parseInt(data.get("doExtendRequestCommand"));

     //游戏可根据回调code按需处理后续逻辑

  }
}
1
2
3
4
5
6
7
8
9
10
11
# 11.4.1 提交游戏扩展数据

渠道要求游戏在运行过程中提交一些用于运营需要的扩展数据,以便双方对游戏内容进行更深度的运营。游戏在接入时请将代码示例中的玩家数据改为真实数据。本接口需在以下情况下调用:

  1. 玩家登陆成功后角色信息确定之后调用。
  2. 角色等级发生变化的时候调用。
  3. 创建新角色时调用。
  • 参数说明:

    "data"=>真实运营数据(json字符串)

    //该接口在"submitType"的值分别为“createRole”,"selectServer","enterServer","levelUp","exitServer"时务必分别调用多次

  • 注意事项:

    "submitType" 请务必根据场景正确传值

  • 代码示例:

JSONObject jsonExData = new JSONObject(); 
try{
  jsonExData.put("roleId", "R0010");        // 当前登录的玩家角色ID              
  jsonExData.put("roleName", "令狐一冲");     // 当前登录的玩家角色名
  jsonExData.put("roleLevel", "99");        // 当前登录的玩家角色等级
  jsonExData.put("roleCreateTime", "1488264008");        // 角色创建时间,格式为unix时间戳,该时间为服务器时间,务必传一个数字
  jsonExData.put("roleLevelUpdateTime", "1488264008");        // 角色等级更新时间,格式为unix时间戳,该时间为服务器时间,务必传一个数字
  jsonExData.put("zoneId", 192825);       // 当前登录的游戏区服ID
  jsonExData.put("zoneName", "游戏一区-逍遥谷"); // 当前登录的游戏区服名称
  jsonExData.put("submitType", "createRole"); // 上报场景:  "createRole", "selectServer"(选择服务器,账号登录成功 ,角色进入游戏前), "enterServer"(选择服务器进入游戏后), "levelUp"(角色升级) ,"exitServer"(退出服务器,退出游戏)
  jsonExData.put("roleBalance", "游戏币余额");// 务必传一个数字,若无,传"0"
  jsonExData.put("vipLevel", "vip等级");// 务必传一个数字,若无,传入"0"
  jsonExData.put("partyName", "帮派名称");//若没有就传"无"
  jsonExData.put("professionid", "当前登录玩家的职业ID");//没有就传"0"
  jsonExData.put("Private", "当前登录玩家的职业名称");//没有就传"无"
  jsonExData.put("gender", "当前登录玩家的性别");//不能为空,不能为null,可传参数"男"、"女"、"无"
  jsonExData.put("payTotal", "0");//累计充值金额 元为单位,文不能为空,必须为数字,若没有就传"0"
  jsonExData.put("remainCoinNum", "0");//1级货币余额,例如此玩家钱袋里还有5000个元宝,不能为空,必须为数字,若无,传入"0"
  jsonExData.put("professionroleid", "职业称号ID");//不能为空,不能为null,没有就传 "0"
  jsonExData.put("professionrolename", "职业称号");//不能为空,不能为null,没有就传"无"
  jsonExData.put("power", "战力值");//不能为空,必须为数字,不能为null,没有就传“0”
  jsonExData.put("experience", "经验值");//不能为空,用户当前经验值,必须为数字,不能为null,若没有就传"0"
  jsonExData.put("balance", "帐号余额");//若没有则传 "0"

  jsonExData.put("partyid", "当前用户所属帮派帮派ID");//不能为空,必须为数字,不能为null,若无,传入 “0”
  jsonExData.put("partyroleid", "帮派称号ID");//帮派会长/帮主必传1,其他可自定义,不能为空,不能为null,若无,传入 “0”
  jsonExData.put("partyrolename", "帮派称号名称");//不能为空,不能为null,若无,传入“无”
} catch (JSONException e) {
   //处理异常
}

Map<String, String> params = new HashMap<String, String>();
params.put("data", jsonExData.toString());

GHome.getInstance().doExtend(this, IGHomeApi.EXTEND_COMMAND_SUBMIT_EXTEND_DATA, params, new Callback() {

  @Override
  public void callback(int code, String message, Map<String, String> data){
    //通常游戏可以忽略该接口的执行结果
     if(code == Constants.ERROR_CODE_SUCCESS || 
      code == Constants.ERROR_OPERATION_NOT_SUPPORTED){
      //调用成功
      //code=ERROR_OPERATION_NOT_SUPPORTED表示渠道不支持该操作,不过为了让所有渠道的接入代码一致,游戏可将该错误码当做成功来处理
     } 
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 11.4.2 游戏退出页接口

游戏退出时调用该接口(通常是玩家点击返回键等要触发游戏退出弹框的场景),等待该接口返回来确定是否需要弹出游戏自己的退出弹框

  • 参数说明:

  • 代码示例:

GHome.getInstance().doExtend(this, IGHomeApi.EXTEND_COMMAND_EXIT_PAGE, new HashMap<String, String>(), new Callback() {

  @Override
  public void callback(int code, String message, Map<String, String> data){
    if(code == Constants.ERROR_OPERATION_NOT_SUPPORTED){
      //渠道无退出弹框,游戏需要显示自己的退出确认界面
    } else if(code == Constants.ERROR_CODE_SUCCESS) {
      //已显示渠道的退出游戏弹框并且玩家点击了确定退出,游戏在这里直接做退出游戏处理
    } else{
      //无需处理
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 12. 其他接口(非必接)

# 12.1. 获取渠道号

# 12.1.1. 获取一级渠道号

游戏可调用以下接口来获取一级渠道号

方式一:

  • 接口:

    getApplicationSuperChannel();

  • 参数说明:

  • 注意事项:

    必须在初始化完成后调用。

  • 代码示例:

GHome.getInstance().getApplicationSuperChannel();
1

方式二:

  • 接口:

    getApplicationSuperChannel(Context context);

  • 参数说明:

context 上下文(可通过getApplicationContext()获取)

  • 注意事项:

    在初始化完成前或完成后调用都可以。

  • 代码示例:

GHome.getInstance().getApplicationSuperChannel(getApplicationContext());
1
# 12.1.2. 获取二级渠道号

游戏可调用以下接口来获取二级渠道号

方式一:

  • 接口:

    getCPSChannelCode(Context context);

  • 参数说明:

    context 上下文(可通过getApplicationContext()获取)

  • 代码示例:

GHome.getInstance().getCPSChannelCode(getApplicationContext());
1
# 12.1.3. 获取G家设备id

G家使用的设备id的字符串

  • 接口:

    getGHomeDeviceId(final Activity activity)

  • 参数说明:

    activity: Activity对象。

  • 注意事项:

  • 代码示例:

GHome.getInstance().getGHomeDeviceId(this)
1

# 13. 获取用户条款和隐私政策(选接)

游戏获取用户条款和隐私政策页面的URL / html页面内容代码 (接入接口时根据自身需求二选一)

  • 接口:

    getAgreement(final Activity activity, final Callback callback); //页面的URL

    getAgreementContent(final Activity activity, final String appId, final Callback callback); //html页面内容代码

  • 参数说明:

    activity: Activity对象。

    appId: 游戏在手游直通车平台上申请得到的ID。

    callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 代码示例:

  //获取页面url
  GHome.getInstance().getAgreement(mContext, "791000008", new Callback() {
    @Override
    public void callback(int code, String message, Map<String, String> data) {
    if(data != null){
    //获取到的用户条款页面的url
    String serviceAgreementUrl = data.get("serviceAgreementUrl");
    //获取到的隐私政策页面的url
    String privacyPolicyUrl = data.get("privacyPolicyUrl");
    String versionNew = data.get("version");
    }
    }
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
  //获取页面内容
  GHome.getInstance().getAgreementContent(mContext, "791000008", new Callback() {
    @Override
    public void callback(int code, String message, Map<String, String> data) {
    if(data != null){
    //获取到的用户条款html内容
    String serviceAgreement = data.get("servicerAgreement");
    //获取到的隐私政策html内容
    String privacyPolicy = data.get("privacyPolicy");
    String versionNew = data.get("version");
    }
    }
  });
1
2
3
4
5
6
7
8
9
10
11
12
13

# 14. 第三方账号登录账号更换绑定手机号(选接)

使用第三方账号(微博,QQ,微信等)登录进游戏之后的换绑手机号功能接口

  • 接口:

    changeThirdBindPhone(Activity activity, String appId, Callback callback);

  • 参数说明:

    activity: Activity对象。

    appId: 游戏在手游直通车平台上申请得到的ID。

    callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 代码示例:

  GHome.getInstance().changeThirdBindPhone(mContext, "791000008", new Callback() {
    @Override
    public void callback(int code, String message, Map<String, String> data) {
    if (code == 0){
      Toast.makeText(mContext, "关联成功", Toast.LENGTH_SHORT).show();
    }else{
      Toast.makeText(mContext, "关联失败", Toast.LENGTH_SHORT).show();
    }
    }
  });
1
2
3
4
5
6
7
8
9
10

# 15. 敏感词过滤接口(非必接,请看注意事项)

【注意(重要):】

  1. 该接口仅仅是一个interface空接口,如果游戏需要在某些渠道使用渠道的敏感词接口(例如wegame),则需要调用这个接口。
  2. 在 ghome sdk 或其他渠道,不会有任何作用,“原文入参,原文出参”。游戏在这些场景需要自行实现所有敏感词过滤功能(或者使用其他第三方sdk)
  3. 一般建议使用该接口的游戏,可以单独实现母包(和官方登录或者其他渠道分开),以便在聊天、昵称设定(等所有用户可输入信息地方)实现渠道使用的敏感词过滤(目前仅仅wegame渠道支持)。
  4. 基于第三点仅仅是一种游戏的实现方案建议,如果游戏有其他方便实现的方案,也可以操作。但原则上是需要完成敏感词过滤,并了解 ghome sdk 仅仅是一个空接口(功能代理)。
  5. 另外,如果游戏在wegame渠道的母包中实现了一次自己的敏感词,则wegame渠道包则会:用户输入->游戏母包的敏感词过滤->ghome sdk 接口代理->wegame 敏感词->返回结果。
  6. 该接口我们不会记录任何调用信息,不会有任何日志,因为属于用户隐私的敏感信息。所以请上线前使用该接口的游戏都需要经过测试(所有场景,例如聊天、昵称设置、等等),线上是不会对用户记录任何有关的行为数据。
  • 接口:

    filterSensitiveWord(final Activity activity, final String inputWord, final Callback filterCallback);

  • 参数说明:

    activity: Activity对象。

    inputWord: 用户输入的文本信息。

    callback: 回调对象,用来回调执行结果,详情请参考代码事例。

  • 代码示例:

   GHome.getInstance().filterSensitiveWord(mContext, etSensitiveW.getText().toString(), new IGHomeApi.Callback(){
   @Override
   public void callback(int code, String msg, Map<String, String> data){
   Toast.makeText(mContext, "敏感词接口过滤结果: " + data != null ? data.get("resultWord") : "", Toast.LENGTH_SHORT).show();
   }
   });
1
2
3
4
5
6

# 16. GShare(分享功能,按需选接)

# 16.1. 配置分享相关的AndroidManifest.xml 配置项:

<!-- 权限 gshare (不使用录屏功能则不添加)start -->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 权限 gshare (不使用录屏功能则不添加)end -->




<!-- GShare start -->
        <!-- provider 方式分享 authorities 配置为: 包名.gshare.fileprovider-->
        <provider
            android:name="com.ghome.gshare.GShareProvider"
            android:authorities="${applicationId}.gshare.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true" >
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/share_file_provider_paths" />
        </provider>
        <!-- provider 方式分享 -->

        <!--录屏功能所需的service-->
        <service android:name="com.ghome.gshare.record.RecordService"
            android:enabled="true"
            android:exported="true"
            android:foregroundServiceType="mediaProjection">
        </service>
        <!--录屏功能所需的service-->

<!-- 微博provider 方式分享 authorities 配置为: 包名.fileprovider-->
        <provider
            android:name="com.sina.weibo.sdk.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true" >
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/wbfilepaths" />
        </provider>

<!-- GShare end -->


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 16.2. 分享相关生命周期接口(游戏主activity中接入)


@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        //gshare
        GShareInterface.onActivityResult(requestCode, resultCode, data);
    }

 @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        //gshare
        GShareInterface.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

@Override
    protected void onDestroy() {
        super.onDestroy();
        
        //gshare
        GShareInterface.free();
    }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 16.3. 分享接口入参说明

分享类型:


GSharePlatformType sharePlatform = GSharePlatformType.WEIXIN_PLATFORM;

public enum GSharePlatformType
{
    WEIXIN_PLATFORM,//微信
    WEIXIN_CIRCLE_PLATFORM,//微信朋友圈
    QQ_PLATFORM,//QQ
    QZONE_PLATFORM,//QQ空间
    SINA_WB_PLATFORM,//新浪微博
    SDK_CHOOSE_PLATFORM;//弹出选择第三方平台UI
}

1
2
3
4
5
6
7
8
9
10
11
12
13

分享监听(系统分享调用即成功,所以这个回调是调用接口即返回):


MyShareListener shareListener;
// 分享监听
    public class MyShareListener implements GShareListener {
        @Override
        public void onComplete(GSharePlatformType platformType) {
            Toast.makeText(mContext, platformType + "share onComplete", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onError(GSharePlatformType platformType, String strErrMsg) {
            Toast.makeText(mContext, platformType + " share onError:" + strErrMsg, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCancel(GSharePlatformType platformType) {
            Toast.makeText(mContext, platformType + " share onCancel", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onOpen(GSharePlatformType platformType) {
            Toast.makeText(mContext, platformType + " app onOpen", Toast.LENGTH_SHORT).show();
        }
    }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 16.4. 分享文字 (sharePlatform:微信, QQ, QQ空间,新浪微博,平台自动选择)

GShareTextMedia shareMedia = new GShareTextMedia();
((GShareTextMedia) shareMedia).setText("分享文字测试");

GShareInterface.getInstance(mContext).doShareWithSystemPrimal(mContext, sharePlatform, shareMedia, shareListener);

1
2
3
4
5

# 16.5. 分享图片 (sharePlatform:微信, 微信朋友圈,QQ, QQ空间,新浪微博,平台自动选择)

shareMedia = new GShareSystemPrimalImageMedia();
((GShareSystemPrimalImageMedia) shareMedia).setDescription("图片分享");
((GShareSystemPrimalImageMedia) shareMedia).addMediaPath(mContext.getExternalFilesDir(null).toString() + "/" + "gshare1.jpg");

GShareInterface.getInstance(mContext).doShareWithSystemPrimal(mContext, sharePlatform, shareMedia, shareListener);



1
2
3
4
5
6
7
8

# 16.6. 分享视频 (sharePlatform: 微信,QQ,平台自动选择)

shareMedia = new GShareSystemPrimalVideoMedia();
((GShareSystemPrimalVideoMedia) shareMedia).setDescription("可爱的狗子");
((GShareSystemPrimalVideoMedia) shareMedia).addMediaPath(mContext.getExternalFilesDir(null).toString() + "/"  + "gshare1.mp4");

GShareInterface.getInstance(mContext).doShareWithSystemPrimal(mContext, sharePlatform, shareMedia, shareListener);



1
2
3
4
5
6
7
8

# 16.7. 分享音乐 (sharePlatform:QQ,平台自动选择)


shareMedia = new GShareSystemPrimalMusicMedia();
((GShareSystemPrimalMusicMedia) shareMedia).setDescription("音乐分享.");
((GShareSystemPrimalMusicMedia) shareMedia).addMediaPath(mContext.getExternalFilesDir(null).toString() + "/" + "gshare1.mp3");

GShareInterface.getInstance(mContext).doShareWithSystemPrimal(mContext, sharePlatform, shareMedia, shareListener);



1
2
3
4
5
6
7
8
9

# 16.8. 分享录屏


// 初始化视频录制服务
        GShareInterface.initRecordService(mContext);

        MyShareListener shareListener = new MyShareListener();

        GShareRecordListener myListener = new GShareRecordListener() {
            @Override
            public void onStartRecordSuccess() {
                ((Button) view).setText("结束并分享");
            }

            @Override
            public void onStartRecordFailed(String message) {
                Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onEndRecordSuccess(final String videoPath) {
                Toast.makeText(mContext, "录屏成功" + videoPath, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onEndRecordFailed(String message) {
                Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
            }
        };

        if (((Button) view).getText().toString().equals("录屏开始")) {
            GShareInterface.startRecord(myListener);
        } else {
            ((Button) view).setText("录屏开始");
            GShareInterface.stopRecordAndShare(mContext, shareListener);
        }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 17. 附录A:服务器端签名算法

  • 参数说明:

    1. timestamp为调用接口时的unix时间戳(精确到秒)

    2. sequence为请求序列号(游戏服务器端需要保证两次调用接口生成的sequence不重复)

  • 签名算法:

    1. 调用方首先需要将请求的参数根据参数的key(ASCII码值)进行升序排序

    2. 将排序好的接口请求参数和参数值按key=val&key2=val2…这样得格式拼装成一个字符串,并在最后加上与APPID对应的APPKEY

    3. 对上述拼接好的字符串进行md5编码,获得最终的签名串

  • 代码示例(PHP):

public function sign ($params, $secret_key) { // $params数组必须包含timestamp
  ksort($params);
  $pair = array();
  foreach($in as $k => $v){
    $pair[] = $k. '=' .$v;
  }
  $str = implode('&', $pair); // 拼接字符串
  $str = $str.$secret_key; // 把APPKEY补充到最后
  return md5($str);
}
1
2
3
4
5
6
7
8
9
10

# 18. 附录B:特殊说明

  • 订单重发机制:

    平台对所有发货状态非“success”的订单,采取统一的重发机制。具体的重发策略为:每60秒重发1次,最大重发次数为60次。

    对尝试重发后,最终失败的订单,我们会隔天通过报表机制发现,由平台方补发货,游戏方不需要参与。

  • 发货重复处理机制:

    基于订单重发机制,可能导致游戏方重复收到相同orderNo的发货通知,此时要求游戏检查orderNo实际到货情况,避免重复向玩家发放游戏币等虚拟货品。建议对一个月内的orderNo做唯一性检查。

    针对已发货成功的订单,要求游戏方收到重复通知时,请务必返回success,状态为“success”的订单,将不会继续触发重发机制。

  • SDK常用类所在包:

import com.ghome.sdk.GHome;
import com.ghome.sdk.GHomeApplication;
import com.ghome.sdk.common.Constants;
import com.ghome.sdk.common.IGHomeApi;
import com.ghome.sdk.common.IGHomeApi.Callback;
1
2
3
4
5

# 19. 附录C:常用错误码以及回调指令

错误码 描述
0 操作成功
-10869615 用户拒绝隐私政策
-10869610 初始化失败
-100 登录取消
-10869608 登录失败
-10869607 用户未登录
-10869609 支付取消
-10869604 支付失败
-10869613 不被SDK支持的调用操作

# 20. FAQ

  • unity游戏相关:

    1. 游戏拉起登录等弹框后回到后台 或从三方登录取消登录 再切回游戏 游戏黑屏问题解决办法

      https://cloud.tencent.com/developer/article/1761950

  • gshare的录屏功能配置项:

    • 在低版本的android工程中编译时有报错:attribute android:foregroundServiceType not found
      • 如果游戏使用的targetVersion 版本<=28 则不用设置该项属性
      • 如果游戏使用的targetVersion 版本>=29 则修改 compileSdkVersion 版本>=29即可
    • 出包时报错重复库:Duplicate class androidx.XXX found in modules XXX.jar (androidx.XXX) and XXX(android-support-v4.jar)
      • gradle.properties中关闭androidX : android:enabled="false" android:exported="false"

Last Updated: 3/21/2024, 3:27:30 AM