跳转至

Android(GooglePlay)手游SDK客户端对接文档

本文档是小雪球发行商将游戏发行到 GooglePlay 的客户端的对接文档,需要游戏开发商对接和使用。SDK中不仅包含玩家登录和内购支付等功能,还包括其他比如个人中心、角色信息上报等方法。该文为Android客户端接入本SDK的使用教程,只涉及SDK的使用方法,默认读者已经熟悉IDE的基本使用方法(本文以AndroidStudio为例),以及具有相应的编程知识基础等。


1. 准备阶段

请先阅读《SDK接入相关文件及参数介绍》和《SDK接入数据交互流程图》,之后将SDK需要游戏服务端提供的接口地址以及游戏内购商品列表提供给小雪球后,方可开始游戏与SDK的对接与联调。

2. SDK导入与配置

2.1 依赖包导入

小雪球SDK直接导入

SDK下载界面中直接下载Android(GooglePlay)的SDK包压缩文件。

压缩包
  | --- SDK/
  | ------snowball_game_gp-x.x.x.aar
  ...

将其中的snowball_game_gp-x.x.x.aar引入到游戏的Android Studio项目中。

注意

Gradle的版本必须大于或者等于3.2.1

在项目级build.gradle文件中配置

buildscript {
    repositories {
        google()
    }

    dependencies {
        // ...

        // Google Services plugin
        classpath 'com.google.gms:google-services:4.3.13'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}

在应用级build.gradle文件中配置

apply plugin: 'com.android.application'
// Google Services plugin
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

dependencies {
    ...

    implementation 'com.google.android.gms:play-services-auth:20.3.0'
    implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
    implementation 'com.facebook.android:facebook-login:latest.release'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.android.billingclient:billing:5.0.0'
    implementation 'com.squareup.okhttp3:okhttp:3.12.12'
    implementation 'com.squareup.okio:okio:1.15.0'
    implementation 'com.appsflyer:af-android-sdk:6.8.2'
    implementation 'com.android.installreferrer:installreferrer:2.2'
    // google firebase
    implementation platform('com.google.firebase:firebase-bom:30.3.1')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-crashlytics'
    implementation 'com.google.firebase:firebase-messaging'
}

2.2 strings.xml配置

打开您的/app/res/values/strings.xml文件,增加Google和Facebook相关的配置信息

<string name="server_client_id">{GOOGLE_CLIENT_ID}</string>
<string name="google_license_key">{GOOGLE_LICENSE_KEY}</string>
<string name="facebook_app_id">{FACEBOOK_APP_ID}</string>
<string name="fb_login_protocol_scheme">fb{FACEBOOK_APP_ID}</string>
<string name="facebook_client_token">{FACEBOOK_CLIENT_TOKEN}</string>

注意

将小雪球提供的配置参数信息表中的值替换进上面{xxx}中,具体参数对应的说明请参考参数配置

2.3 AndroidManifest.xml配置

权限配置

manifest节点中添加以下权限申请

<uses-permission android:name="android.permission.INTERNET" />
<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" />
<!-- Devices >= 3 have version of Google Play that supports licensing. -->
<uses-sdk android:minSdkVersion="3" />
<!-- Required permission to check licensing. -->
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

小雪球SDK参数配置

application节点中添加以下配置

<!-- snowball -->
<meta-data android:name="SNB_INSTANCE_KEY" android:value="{SNB_INSTANCE_KEY}" />
<meta-data android:name="SNB_API_PREFIX" android:value="{SNB_API_PREFIX}" />
<meta-data android:name="SNB_PAGE_PRIVACY_URL" android:value="{SNB_PAGE_PRIVACY_URL}" tools:node="replace" />
<meta-data android:name="SNB_PAGE_USERAGREEMENT_URL" android:value="{SNB_PAGE_USERAGREEMENT_URL}" tools:node="replace" />
<meta-data android:name="SNB_PAGE_HELP_URL" android:value="{SNB_PAGE_HELP_URL}" />
<meta-data android:name="SNB_PAGE_FB_HOME_URL" android:value="{SNB_PAGE_FB_HOME_URL}" />
<meta-data android:name="SNB_GAME_MAIN_ACTIVITY_CLASSNAME" android:value="游戏主Activity的包名+类名" />
<!-- appsflyer -->
<meta-data android:name="APPSFLYER_DEV_KEY" android:value="{APPSFLYER_DEV_KEY}" />
<!-- facebook -->
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>

注意

  • 请将以上配置信息中的android:value值替换成发行商提供的,具体参数对应的说明请参考参数配置
  • 小雪球SDK提供了前置隐私政策和用户协议的Activity,用户在该界面中同意之后,方可跳转到游戏的主Activity,这里SNB_GAME_MAIN_ACTIVITY_CLASSNAMEmeta-data值就是需要配置游戏主Activity的地方

设置包名

AndroidManifest.xml文件中manifest节点中的package属性值设置成发行商提供的包名

增加Activity界面声明

<!-- snowball -->
<activity
    android:name="com.smallsnowball.gamesdk.ui.SnowballPrivacyActivity" 
    android:configChanges="orientation|keyboard|keyboardHidden|screenLayout|screenSize" 
    android:exported="true"
    android:screenOrientation="portrait" 
    android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity
    android:name="com.smallsnowball.gamesdk.ui.SnowballWebActivity"
    android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
<!-- facebook -->
<activity
    android:name="com.facebook.FacebookActivity" 
    android:configChanges= "keyboard|keyboardHidden|screenLayout|screenSize|orientation" 
    android:label="@string/app_name" />
<activity
    android:name="com.facebook.CustomTabActivity" 
    android:exported="true">
    <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="@string/fb_login_protocol_scheme" />
    </intent-filter>
</activity>

注意

  • SnowballPrivacyActivity为此app的默认启动Activity,请不要更改
  • SnowballPrivacyActivity的activity节点下的android:screenOrientation属性值,请根据游戏的界面的横竖屏来设置,横屏使用landscape,竖屏使用portrait

增加Provider的声明

<provider
    android:authorities="com.facebook.app.FacebookContentProvider{FACEBOOK_APP_ID}" 
    android:name="com.facebook.FacebookContentProvider" 
    android:exported="true"/>

注意

请将以上配置信息中的{FACEBOOK_APP_ID}值替换成发行商提供的,具体参数对应的说明请参考参数配置

增加Receiver的声明

<receiver android:name="com.appsflyer.SingleInstallBroadcastReceiver" android:exported="true">
    <intent-filter>
        <action android:name="com.android.vending.INSTALL_REFERRER" />
    </intent-filter>
</receiver>

增加Service的声明

<service
    android:name="com.smallsnowball.gamesdk.service.SnowballFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

其他注意事项

  • 项目minSdkVersion必须设置为23
  • 项目targetSdkVersion必须设置为33
  • Unity3D游戏工程关闭自动权限申请功能
    <application>
        <meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
    </application>
    
  • 删除隐私敏感权限:android.permission.READ_PHONE_STATE(GooglePlay禁止获取imei)
  • 文件操作请在应用私有目录中进行,避免在SD卡公共目录中进行文件读写操作(可以避免申请外部存储权限)

2.4 google-services.json文件

将Firebase的配置文件google-services.json移到应用级的目录中。

2.5 资源配置

图标icon配置

配置游戏各像素icon图标,目录与图片尺寸对应关系如下:

目录 图片尺寸
drawable-hdpi 72×72
drawable-ldpi 36×36
drawable-mdpi 48×48
drawable-xhdpi 96×96
drawable-xxhdpi 144×144
drawable-xxxhdpi 192×192

注意

drawable目录中不添加游戏icon

隐私协议界面默认背景图配置

游戏必须提供隐私协议界面的默认背景图,改名并保存到res/drawable/snowball_privacy_bg.png,尺寸为1920×1080像素

3. SDK接入

3.1 Application初始化

在Android应用创建的时候,必须继承SDK默认的Application并调用Application初始化方法

Application初始化代码
import com.smallsnowball.gamesdk.SnowBallSDK;
import com.smallsnowball.gamesdk.app.SnowballApplication;

public class MyApplication extends SnowballApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        // 应用初始化方法调用
        SnowBallSDK.getInstance().applicationInit(this);
    }
}

注意

  • 以下所有接口都必须在游戏单独唯一的Activity中去调用
  • 以下所有接口使用前,必须引入SDK的类
    import com.smallsnowball.gamesdk.SnowBallSDK;
    

3.2 SDK初始化

注意

  • SDK初始化后才能正确调用其他接口
  • 建议在游戏主Activity的开始处如onCreate方法中进行
  • 下面代码中activity参数的值可以使用MainActivity.this替换,直接将当前Activity的传入
SDK初始化代码
/**
 初始化
 参数:
     - activity: 类型android.app.Activity
     - canPrintLog: 类型boolean。是否在logcat中打印详细日志。建议在正式出包后改值设置为false
 返回: 无
*/
SnowBallSDK.getInstance().init(activity, canPrintLog);

3.3 玩家登录

玩家登录代码
/**
 玩家登录
 参数: 无
 返回: 无
*/
SnowBallSDK.getInstance().login();

3.4 玩家登出/切换账户

注意

  • 本接口必须在账户登录成功后才能调用
玩家登出代码
/**
 玩家登出
 参数: 无
 返回: 无
*/
SnowBallSDK.getInstance().logout();

3.5 游戏内购物品支付

注意

  • 本接口中sign签名请参考游戏内购订单验证过程和发送接口中的游戏创建内购订单部分。为了保障支付的安全性,签名计算请在游戏服务端中进行,保证instanceSecret不被非法破解获取。
  • 本接口必须在账户登录成功后才能调用
游戏内购物品支付代码
import com.smallsnowball.gamesdk.entity.SnowBallIapRequest;

//...

SnowBallIapRequest iapReq = new SnowBallIapRequest();
iapReq.setInstanceKey('SDK应用实例key');
iapReq.setUid('账户uid');
iapReq.setToken('账户登录后的token');
iapReq.setProductId('内购商品id');
iapReq.setProductName('内购商品名称');
iapReq.setRoleId('角色id');
iapReq.setRoleName('角色名称');
iapReq.setServerId('游戏服id');
iapReq.setServerName('游戏服名称');
iapReq.setAmount('商品金额比如0.99');
iapReq.setCurrency('货币类型比如USD');
iapReq.setGameOrderId('游戏生成的订单号');
iapReq.setExtra('透传参数订单成功后原样返回'); //选填
iapReq.setNotifyUrl('游戏内购物品发送通知地址'); //选填
iapReq.setSign('游戏生成的sign');

/**
 游戏内购物品支付
 参数: 
     - iapReq: 类型SnowBallIapRequest
 返回: 无
*/
SnowBallSDK.getInstance().inAppPay(iapReq);

3.6 退出游戏

注意

  • 该接口为选接
  • 当玩家安卓手机上点击回退按钮的时候,需要弹出窗口让玩家确认是否退出游戏。请在onKeyDown默认方法里进行调用
  • 如果游戏自带退出游戏确认窗口,可以不调用本接口
退出游戏代码
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
        // 如果游戏有自己的退出游戏确认窗口,则以下应使用游戏的逻辑,不需要使用以下exitGame方法
        SnowBallSDK.getInstance().exitGame();
        return true;
    }

    return super.onKeyDown(keyCode, event);
}

3.7 上报角色信息

以下3种情况下上报

  • 创造角色
  • 角色进入游戏
  • 角色升级
上报角色信息代码
import com.smallsnowball.gamesdk.entity.SnowBallRoleInfo;

//...

SnowBallRoleInfo roleInfo = new SnowBallRoleInfo();
roleInfo.setRoleId('角色id');
roleInfo.setRoleName('角色名');
roleInfo.setRoleLevel('角色等级');
roleInfo.setVipLevel('角色VIP等级可为空');
roleInfo.setServerId('游戏服id');
roleInfo.setServerName('游戏服名');
// 上报枚举类型:
//        - SnowBallRoleInfo.InfoType.CREATE: 创角色
//        - SnowBallRoleInfo.InfoType.ENTER: 进入游戏
//        - SnowBallRoleInfo.InfoType.UPGRADE: 升级
roleInfo.setType(SnowBallRoleInfo.InfoType.CREATE);

/**
 上报角色信息
 参数: 
    - roleInfo: 类型SnowBallRoleInfo
 返回: 无
*/
SnowBallSDK.getInstance().reportRoleInfo(roleInfo);

3.8 打开用户中心

注意

  • 游戏设置界面中,需要加入用户中心菜单,点击后调用该接口
打开用户中心代码
/**
 打开用户中心
 参数: 无
 返回: 无
*/
SnowBallSDK.getInstance().openUserCenter();

3.9 打开其他H5窗口

注意

  • 该接口为选接
  • 游戏中如果需要调用以下接口,需要在游戏中实现该功能的菜单按钮
打开其他H5窗口代码
import com.smallsnowball.gamesdk.entity.H5Page;

//...

// 打开隐私政策页面
SnowBallSDK.getInstance().openExtraPage(H5Page.PRIVACY_POLICY);

// 打开用户协议页面
SnowBallSDK.getInstance().openExtraPage(H5Page.USER_AGREEMENT);

// 打开帮助中心页面
SnowBallSDK.getInstance().openExtraPage(H5Page.HELP_CENTER);

// 打开Facebook社区页面
SnowBallSDK.getInstance().openExtraPage(H5Page.FACEBOOK_HOMEPAGE);

3.10 全局事件监听

注意

  • 监听定义请放置在与SDK初始化同级代码下
  • 以上接口涉及到初始化、登录、登出、支付完成的结果都在此处返回
  • 登录成功后获得的相关信息,需要传到游戏服务端进行sign验证,具体说明请参考游戏登录信息验证
  • 支付成功后获得的相关信息,需要传到游戏服务端进行sign验证,具体说明请参考游戏内购订单验证过程和发送接口中的客户端返回SDK订单信息验证方式部分
全局事件监听代码
import com.smallsnowball.gamesdk.listener.ISnowBallGlobalListener;
import com.smallsnowball.gamesdk.entity.SnowBallCallbackCode;
import org.json.JSONException;
import org.json.JSONObject;

//...

SnowBallSDK.getInstance().setGlobalListener(new ISnowBallGlobalListener) {

    // 初始化回调
    @Override
    public void SnowBallInitCallback(int code, String msg) {
        switch (code) {
            case SnowBallCallbackCode.INIT_SUCCESS:
                // 初始化成功。初始化成功后才可调用登陆接口
                break;
            case SnowBallCallbackCode.INIT_FAIL:
                // 初始化失败
                break;
            default:
                break;
        }
    }

    // 登录回调
    @Override
    public void SnowBallLoginCallback(int code, String msg, String userInfo) {
        switch (code) {
            case SnowBallCallbackCode.LOGIN_SUCCESS:
                // 登录成功,将以下信息传入游戏服务端进行sign验证
                try {
                    JSONObject userObj = new JSONObject(userInfo);
                    String uid = userObj.getString("uid"); //SDK账户uid
                    String userType = userObj.getString("userType"); //用户类型
                    String instanceKey = userObj.getString("instanceKey"); //SDK应用实例key
                    String displayName = userObj.getString("displayName"); //用户显示名称
                    String token = userObj.getString("token"); //用户登录token
                    int expire = userObj.getInt("expire"); //用户登录失效时间戳
                    String avatarUrl = userObj.getString("avatarUrl"); //用户头像地址
                    int firstLogin = userObj.getInt("firstLogin"); //是否新用户;1-是,0-否
                    int preRegister = userObj.getInt("preRegister"); //是否预注册用户;0-非,1-官网预注册,2-市场预注册
                    String sessionId = userObj.getString("sessionId"); //登录session
                    String sign = userObj.getString("sign"); //签名
                    // TODO 传入到服务端验证
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                break;
            case SnowBallCallbackCode.LOGIN_FAIL:
                // 登录失败
                break;
            case SnowBallCallbackCode.LOGIN_CANCEL:
                // 登录取消
                break;
            case SnowBallCallbackCode.LOGIN_TIMEOUT:
                // 登录超时
                break;
            default:
                break;
        }
    }

    // 支付回调
    @Override
    public void SnowBallPayCallback(int code, String msg, String iapOrder) {
        switch (code) {
            case SnowBallCallbackCode.PAY_SUCCESS:
                // 支付成功,将以下信息传入游戏服务端进行sign验证,验证成功,给玩家发货
                try {
                    JSONObject orderObj = new JSONObject(iapOrder);
                    String orderType = orderObj.getString("orderType"); //订单类型
                    String orderId = orderObj.getString("orderId"); //SDK订单号
                    String instanceKey = orderObj.getString("instanceKey"); //SDK应用实例key
                    int sandbox = orderObj.getInt("sandbox"); //是否沙盒支付;1-是,0-否
                    String productId = orderObj.getString("productId"); //内购商品id
                    String realPrice = orderObj.getString("realPrice"); //实付金额
                    String realCurrency = orderObj.getString("realCurrency"); //实付使用货币
                    int ts = orderObj.getInt("ts"); //订单时间戳
                    String gameOrderId = orderObj.getString("gameOrderId"); //游戏生成的订单号
                    String sign = orderObj.getString("sign"); //签名
                    // TODO 传入到服务端验证
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                break;
            case SnowBallCallbackCode.PAY_FAIL:
                // 支付失败
                break;
            case SnowBallCallbackCode.PAY_CANCEL:
                // 支付取消
                break;
            default:
                break;
        }
    }

    // 登出回调
    @Override
    public void SnowBallLogoutCallback(int code, String msg) {
        switch (code) {
            case SnowBallCallbackCode.LOGOUT_SUCCESS:
                // 登出成功,通知游戏玩家下线或切换账户
                break;
            case SnowBallCallbackCode.LOGOUT_KICKOUT:
                // SDK后台踢玩家下线
                break;
            case SnowBallCallbackCode.LOGOUT_FAIL:
                // 登出失败
                break;
            default:
                break;
        }
    }
}

3.11 Activity生命周期

游戏主窗体中直接重写一下父类方法

Activity生命周期代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    SnowBallSDK.getInstance().onCreate();
    super.onCreate(savedInstanceState);
}

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

@Override
protected void onResume() {
    SnowBallSDK.getInstance().onResume();
    super.onResume();
}

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

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

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

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

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

3.12 全局属性获取

在游戏对接过程中,需要使用到SDK提供的各种配置和参数值。

全局属性获取代码
// 获取SDK的应用实例key
String instanceKey = SnowBallSDK.getInstance().getInstanceKey();

// 获取当前游戏是否是提审版本(初始化成功后才能获取)
// true: 正在提审
// false: 非提审
boolean reviewStatus = SnowBallSDK.getInstance().getReviewStatus();

// 获取SDK账户唯一uid(登录成功后才能获取)
String uid = SnowBallSDK.getInstance().getUid();

4. 其他事项

4.1 代码混淆

小雪球SDK是以aar包提供给研发商,游戏出包时确保小雪球SDK和使用的三方SDK不会被随意混淆,而影响程序的正常运行。以下是proguard-rules.pro文件需要添加的内容

proguard-rules.pro
#---基础---
-dontshrink
# 保留内部类
-keepattributes Exceptions,InnerClasses
#抛出异常时保留代码行数
-keepattributes SourceFile,LineNumberTable

-keep class android.** {*;}
-keep class androidx.** {*;}

-keep public class * extends android.**
-keep public class * extends androidx.**

-keep interface android.** {*;}
-keep interface androidx.** {*;}

# 不混淆androidx配置的类和方法
-dontwarn androidx.annotation.Keep

#不混淆实现android.os.Parcelable的类
-keep class * implements android.os.Parcelable

# 保留枚举类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保留注解,如果不添加改行会导致我们的@Keep注解失效
-keepattributes *Annotation*
-keep @androidx.annotation.Keep class ** {
    @androidx.annotation.Keep <fields>;
    @androidx.annotation.Keep <methods>;
}

#------

#---SnowBall---
#保持SDK包下不被混淆
-keep class com.smallsnowball.gamesdk.** {*;}
#------

#---Appsflyer ---
-keep class com.appsflyer.** { *; }
-keep public class com.android.installreferrer.** { *; }
#------

4.2 构建出包