# 版署sdk客户端接入指南

---

本文档面向 Android 开发者。

| SDK版本    | 变更时间           | 变更内容                   |
|----------|----------------|------------------------|
| `v1.0.0` | **2023.03.17** | 发布第一个版本                |
| `v2.0.0` | **2023.05.22** | 添加 Activity 配置，code表更新 |
| `v2.1.0` | **2023.08.01** | 添加云存档读档接口，code表更新      |
| `v2.2.0` | **2023.09.20** | 优化防沉迷弹窗文案布局            |
| `v2.3.0` | **2024.06.19** | 添加账号互踢功能，登录页/注册页标签文案更改 |
| `v2.4.0` | **2024.09.24** | 未达到游戏年龄弹窗支持退出游戏        |
| `v2.4.1` | **2024.11.04** | 输入法兼容模拟器               |
| `v2.4.4` | **2026.05.29** | 防沉迷弹窗样式调整              |

## SDK 说明

### 功能描述

版署 SDK（以下简称 SDK），为接入的游戏提供注册、登录、充值、帐号退出、云存档读档等功能。

### SDK 组成

#### 客户端 SDK

SDK 优先提供在线`aar`依赖方式，其 Demo 结构遵循 Android Studio（as） 规范，但仍然保留了 `jar+res` 的依赖方式。  
SDK 支持的编译配置 **android:minSdkVersion >= 16**。

#### 服务端 API

需要充值功能，参考 [SDK 服务端接入文档](https://sdkftp.4399doc.com/external/operateBZ/2.4.4/server_api_1.html)

## 集成流程

### 引入依赖

根据游戏需要，以下三种方式可选其一

- *在线aar*  
  若使用*在线aar*，开发者只要在使用 SDK 的 module 的 `build.gradle`中引入以下内容即可

```groovy
repositories {
    maven {
        // 4399 SDK 开放仓库：正式
        url 'https://mvn.4399doc.com/repository/maven-releases'
    }
    maven {
        // 4399 SDK 开放仓库：快照
        url 'https://mvn.4399doc.com/repository/maven-snapshots'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])

    // 版署 SDK：建议使用最新版本，可通过浏览仓库地址或向运营咨询版本
    implementation "cn.m4399.sdk:operateBZ:2.4.4"

    // volley 和 support 是 SDK 使用的外部依赖，若接入方已有，可忽略
    implementation 'com.android.volley:volley:1.2.1'
    // noinspection GradleCompatible
    implementation "com.android.support:support-v4:28.0.0"
}
```

*注意：若使用`7.0+`版本的 gradle 及 android build 插件，仓库地址应配置在`settings.gradle`中*

- *本地aar*  
  若使用*本地aar*，则要下载 [在线aar](https://mvn.4399doc.com/repository/maven-releases/cn/m4399/sdk/operateBZ/2.4.4/operateBZ-2.4.4.aar)和 [jar+res](https://sdkftp.4399doc.com/external/operateBZ/2.4.4/operateBZ-2.4.4+40.zip) 到本地，并将`aar`和`jar`包中的以下文件 拷贝到游戏模块的`libs`目录下

```
operate/libs/volley-v1.2.1.jar
operate/libs/support-v13-23.2.1.jar
```

Gradle 文件中注意添加本地依赖

```groovy
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
```

- *jar+res*  
  [jar+res 依赖引入](https://sdkftp.4399doc.com/external/operateBZ/2.4.4/jar_res_import.html) 比较繁琐，但是完成后，后续接口调用仍然是一样的。

#### abi 适配与 so 库

若游戏不需要支持所有 [abi](https://developer.android.google.cn/ndk/guides/abis?hl=zh-cn) ，可以按需选用。  
`aar`依赖方式，按以下方式配置需要的`abi`；`jar+res`依赖方式需要手动删除**不需要**的`abi`目录

```
android {
    defaultConfig {
        ndk {
            // 根据游戏需要选择
            abiFilters "armeabi", "armeabi-v7a", "x86", "arm64-v8a"
        }
    }
}
```

### 特别注意：添加`AndroidManifest.xml`必需配置

本次更新重要内容：`AndroidManifest.xml`中的<span style="color:red">**acyivity需要添加`android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|navigation|smallestScreenSize|fontScale"`配置，入口activity必须添加，否则导致闪退**</span>。  
详细方式看以下示例

```xml
<application>
    <!-- 游戏 Activity 配置建议
        应该使用 android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|navigation|smallestScreenSize|fontScale" 
        不应该使用 android:launchMode="singleTask" 启动模式，可以考虑 singleTop
    -->
    <activity
        android:name="cn.m4399.operateBZ.account.LoginActivity"
        android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|navigation|smallestScreenSize|fontScale"
        android:screenOrientation="behind"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
</application>
```

### 接口调用：初始化（必须）

<span style="color:red">**SDK 初始化后才能正确调用其他接口**</span>，建议在游戏主 Activity 的开始处如 `onCreate`方法中进行。

```java
private void initSDK(){
    mOpeCenter=OperateCenter.getInstance();
    OperateConfig opeConfig = new OperateConfig.Builder(MainActivity.this)
        // 设置 SDK 页面方向，必须，应与游戏方向一致，可取值见下文说明
        .setOrientation(GAME_ORIENTATION)
        // 设置游戏运营 key，必须
        .setGameKey(GAME_KEY)
        // 设置调试模式，可选，默认false,发布前应设置为false或删除调用
        .setDebugEnabled(false)
        // 设置是否在 Android 9.+系统上兼容全面屏，true兼容，默认false
        .compatNotch(GAME_COMPACT_NOTCH)
        .build();
    mOpeCenter.setConfig(config);

    // 注意：初始化完成后，其他接口才可用        
    mOpeCenter.init(MainActivity.this,new OperateCenter.OnInitGlobalListener() {
        /*
         * 初始化回调
         * 只有在init之后， isLogin()返回的状态才可靠
         */
        @Override
        public void onInitFinished(boolean isLogin,User userInfo) {
            // 初始化后处理
        }
        
        /*
         * 注销帐号的回调
         * @param fromUserCenter 标识区分是否从个人中心中注销的，若是则为true，不是为false
         */
        @Override
        public void onUserAccountLogout(boolean fromUserCenter) {
            // 用户登出，游戏应回到自身登录页面
        }
        
        /*
         * 切换账号回调
         */
        @Override
        public void onSwitchUserAccountFinished(boolean fromUserCenter,User userInfo){
            // 用户切换，游戏应回到选服页面
        }
    });
}
```

初始化的几点说明

#### 页面方向

- *游戏页面* 应配置方向变化，否则系统强制方向变化时（一般发生是打开外部应用，如支付宝、微信）可能会发生异常

```xml
  <!-- 游戏 Activity 配置建议：
       应该使用 android:configChanges="orientation|screenSize|keyboardHidden"
       不应该使用 android:launchMode="singleTask" 启动模式，可以考虑 singleTop
  -->
```

- *SDK 页面*，**方向支持横屏或竖屏，但不支持横竖屏切换**  
  通过`OperateConfig.setOrientation()`设置 SDK 页面方向，值是系统类`android.content.pm.ActivityInfo`定义的常量

| 方向参数                                           | 含义         |
|------------------------------------------------|------------|
| `SCREEN_ORIENTATION_LANDSCAPE`，`int`, 0        | 横屏         |
| `SCREEN_ORIENTATION_PORTRAIT`, `int`, 1        | 竖屏         |
| `SCREEN_ORIENTATION_SENSOR_LANDSCAPE`，`int`, 6 | 横屏，可180度旋转 |
| `SCREEN_ORIENTATION_SENSOR_PORTRAIT`，`int`, 7  | 竖屏         |

### 接口调用：账号相关

#### 账号登录（必须）

需要登录时调用此接口，如果 SDK 已存在有效登录状态，则直接回调，否则将打开登录页面登录。

```java
OperateCenter mOpeCenter = OperateCenter.getInstance();
mOpeCenter.login(MainActivity.this, new OperateCenter.OnLoginFinishedListener() {
    @Override
    public void onLoginFinished(boolean success, int resultCode, User user) {
          // 登录结束后的游戏逻辑
    }
});
```

**最佳实践**：SDK 初始化时会检查已有登录状态有效，若有效则初始化接口返回用户信息`User`，  
游戏直接通过`OperateCenter.getCurrentAccount()`获取用户信息接口即可，而不需要发起登录调用

在登录成功回调的用户信息，即`User`对象（参考 API 详情），包含`state`字段

#### 查询是否有已登录账号

```java
boolean isLogin = mOpeCenter.isLogin();
```

#### 获取当前登录帐号信息

在 SDK 处于登录状态时，可通过该接口获取当前用户的信息。
<span style="color:red">注意：User 中的uid是 32 位整数字符串；若要转换为数值，注意溢出的可能性</span>

```java
User user = mOpeCenter.getCurrentAccount();
```

#### 账号切换

账号切换是由于 SDK 内部发起的，切换成功后 SDK 调用`OnInitGlobalListener.onSwitchUserAccountFinished()`通知游戏

#### 账号登出

当需要登出账号时调用，SDK 立即清除本地用户状态，并调用`OnInitGlobalListener.onUserAccountLogout()`通知游戏

```java
mOpeCenter.logout();
```

### 接口调用：充值

- 调用充值接口

```java
/*
 * 充值，使用订单类，新版接口
 *
 * 与直接传递参数几乎一样，只是可以对单笔订单设置是否支持超出金额
 */
mOpeCenter.recharge(MainActivity.this,
    // 充值金额，整数，单位元
    new Order(money,mark)
    // 是否支持超出金额，默认不支持
    .supportExcess(false)
    // 商品名(可选，不传时认为商品名是道具）
    .commodity(COMMODITY_NAME),
    new OperateCenter.OnRechargeFinishedListener() {
        @Override
        public void onRechargeFinished(boolean success, int resultCode, String msg) {
            if (success) {
                // 充值操作成功，根据服务端回调决定是否道具
                // 注意：操作成功包含了订单成功、订单处理中两种情况
            } else {
               // 充值失败逻辑，resultCode查看文档结尾
           }
        }
});
```

> **关于超出金额模式（超额）**  
> 由于卡类等充值渠道有金额限制，可能出现*充值金额*无法匹配任何金额的情况；  
> 如果游戏能处理超出*传入金额* 部分的货币，即将多余部分兑换为游戏币发放给用户，则可以设置`true`；  
> 否则，应设置为`false`或不设置，SDK将直接隐藏无匹配金额的充值渠道。

### 接口调用：云存档(可选)

存档，是为了在版署测试时，提供不同等级/关卡的账号的。当游戏满足以下两个条件时，可以不必使用存档接口：
- 游戏已有存档机制，且更换接口成本较高
- 可按运营要求生成指定存档

存档可提供给运营，由运营添加在后台，或可调用上传存档接口上传。
- 调用上传存档接口

```java
String archiveStr = "4399测试存档";
OperateCenter.getInstance().setArchiveStringSimple(archiveStr, new CloudArchive.SimpleStrSetListener() {
    @Override
    public void onSuccess() {
        //上传存档成功
    }

    @Override
    public void onFailure(int code, @NonNull String message) {
        //上传存档失败
    }
});
```
当游戏启动、账号变更时：
- 调用读档接口

```java
OperateCenter.getInstance().getArchiveStringSimple(new CloudArchive.SimpleStrGetListener() {
    @Override
    public void onSuccess(@NonNull String archive) {
        //读取存档成功
    }

    @Override
    public void onFailure(int code, @NonNull String message) {
        //读取存档失败
    }
});
```

### 接口调用：其他

#### 查看SDK版本号

```java
mOpeCenter.getVersion();
```

## API 详情

### User 类方法

`User`类中是一系列`getter`方法

```java
public final class User {
    /** uid */
    public String getUid();

    /** 用户名 */
    public String getName();

    /** 登录状态 */
    public String getState();
}
```

### 接口 code 列表

| code  | 含义                   |
|-------|----------------------|
| `18`  | 取消操作                 |
| `21`  | sdk未初始化              |
| `200` | 成功                   |
| `401` | 账号格式不对               |
| `402` | 该账号已经被注册             |
| `403` | 密码格式不对               |
| `406` | 系统出错，注册失败            |
| `407` | 请输入正确帐号              |
| `408` | 请输入正确密码              |
| `409` | 更新登录信息错误             |
| `410` | 帐号存档为空或已删除           |
| `411` | 上传的存档为空              |
| `412` | 存档超出大小限制             |
| `413` | 存档出错了                |
| `415` | 充值失败                 |
| `601` | 参数错误                 |
| `604` | 游戏信息错误（一般是gameKey错了） |
| `606` | 登录信息已过期，请重新登录后再尝试    |
| `607` | 账号在其他设备登录            |
