java-客户端接入指南


本文档面向 Android 开发者,描述的接入方式适合运营在 4399 平台的独代游戏。

SDK 下载

SDK和相关示例的下载地址可在游戏创建后,进入 「游戏管理-SDK服务」 页面获取。

image

SDK 说明

用户信息与隐私策略

SDK 中使用的用户信息及隐私政策参考:《4399通行证用户服务协议》《隐私政策》

功能描述

为4399独家代理的游戏提供的SDK,提供游戏激活、礼包、更新等服务,和游戏官方消息官方活动、论坛等丰富而优质的游戏内容

若游戏有被静态修改后二次打包的情况,SDK 对此类破解提供了一些解决方案,详见 服务端文档

SDK 组成

客户端 SDK

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

服务端 API

服务端支持额外的功能,参考 SDK 服务端接入文档

集成流程

准备

首次接入 SDK,要在 4399 开放平台 注册应用,主要是提交APK、素材等信息。
完成后,开发者将得到 SDK 的基础参数:game keyGameKey,游戏在 4399 平台的运营标识

引入依赖

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

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:operate:3.8.0"
    
    // 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

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

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

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

abi 适配与 so 库

若游戏不需要支持所有 abi ,可以按需选用。
aar依赖方式,按以下方式配置需要的abijar+res依赖方式需要手动删除不需要abi目录

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

AndroidManifest 额外配置

在游戏项目的AndroidManifest.xml中,需要注册存储权限和渠道标识

<!-- 接入方应在调用接口前,申请好运行时权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
<application ....>       
    <!-- 渠道标记,4399官方渠道为‘4399’,其他渠道可与运营确认 -->
    <meta-data android:name="FTNN_CHANNEL_ID" android:value="4399"/>
</application>

接入方应在调用接口前,申请好运行时权限,可以使用EasyPemissions 第三方类库快速处理

接口调用:游戏用户协议弹窗

游戏可以使用 4399 SDK 提供的协议弹窗接口,此功能需要运营配置。
调用此接口后将弹窗隐私弹窗,用户同意可以调用后续初始化,否则 SDK 将自动退出游戏。 代码示例参考下一个接口:初始化

接口调用:初始化(必须)

SDK 初始化后才能正确调用其他接口,建议在游戏Activity的开始处如 onCreate方法中进行。 此接口内部自动完成“更新、激活、防沉迷”等流程。

private void initSDK() {
        // 初始化 SDK
        // 配置全局属性,如横竖屏配置
        OperateConfig operateConfig = new OperateConfig.Builder(this)
                // 设置调试模式,可选,true时打开,默认false,发布前必须设置为false或删除该行
                .setDebugEnabled(false)
                // 设置游戏运营 key,此参数需要在原创开放平台注册应用后得到
                .setGameKey(GAME_KEY)
                // 设置SDK页面方向,应与游戏方向一致,部分第三方页面需要在AndroidManifest中设置
                .setOrientation(GAME_ORIENTATION)
                // 设置游戏是否兼在高于Android 9.0版本系统容全面屏,true兼容,默认false
                .compatNotch(GAME_COMPACT_NOTCH)
                .build();
        OperateCenter.getInstance().gameProtocol(activity, operateConfig, new OpeResultListener() {
            @Override
            public void onResult(int code, @Nullable String message) {
                // 其中code:0、用户同意协议;160001、没有协议更新;用户不同意,则SDK结束进程,退出游戏
                // 同意后再初始化其他部分,如初始化 4399 SDK
                ExclusiveAgent.init(this, operateConfig, new ExclusiveAgent.OnInitGlobalListener() {           
                    /*
                     * 激活状态回调
                     *
                     * code 激活禁用 1:激活成功 2:已激活过
                     * msg  状态说明
                     */
                    @Override
                    public void onActivationState(int code, @NonNull String msg) {
                    }
        
                    /*
                     * 初始化完成,玩家在此处可以安全进入游戏
                     */
                    @Override
                    public void onInitFinished() {
                    }
                });
            }
        });
}

初始化的几点说明

页面方向

  <!-- 游戏 Activity 配置建议:
       应该使用 android:configChanges="orientation|screenSize|keyboardHidden"
       不应该使用 android:launchMode="singleTask" 启动模式,可以考虑 singleTop
  -->
方向参数 含义
SCREEN_ORIENTATION_LANDSCAPEint, 0 横屏
SCREEN_ORIENTATION_PORTRAIT, int, 1 竖屏
SCREEN_ORIENTATION_SENSOR_LANDSCAPEint, 6 横屏,可180度旋转
SCREEN_ORIENTATION_SENSOR_PORTRAITint, 7 竖屏
<!-- 
    4399 SDK:SDK 中除了以'cn.m4399.'开头的 Activity 都是第三方页面,
        假设游戏为横屏,需要锁定它们的方向为横屏,可以设置 android:screenOrientation,
        此设置应与初始化接口中设置的值保持对应,且若编译有错误,可以进一步设置‘tools:replace’
 -->
<activity 
        android:name="com.cmic.gen.sdk.view.GenLoginAuthActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:launchMode="singleTop"
        android:screenOrientation="landscape"
        android:theme="@style/AppTheme.NoActionBar"
        tools:replace="android:theme, android:screenOrientation" />

接口调用:游戏内容相关(推荐)

初始化之后,可以调用游戏盒跳转接口,这些接口可以大大丰富游戏内容、补充游戏功能。

使用活动码

/*
 使用活动码
 your_activity_id: 活动ID,向运营获取
 your_activity_code: 活动码,用户输入
*/
ExclusiveAgent.useActivityCode("your_activity_id", 
    "your_activity_code", 
    new OpeResultListener() {
    @Override
    public void onResult(int code, @Nullable String message) {
        // code 0:成功 3:失败 5:异常中止
    }
});

使用礼包码

/*
 使用礼包码
 your_gift_id: 礼包ID,向运营获取
 your_gift_code: 礼包码,用户输入
*/
ExclusiveAgent.useGiftCode("your_gift_id", 
    "your_gift_code", 
    new OpeResultListener() {
    @Override
    public void onResult(int code, @Nullable String message) {
        // code 0:成功 3:失败 5:异常中止
    }
});

游戏分享

/*
 游戏分享
*/
OperateCenter.getInstance().share(EaGameActivity.this);

游戏评分

调用comment接口,唤起SDK游戏评分界面

/*
 游戏评分
*/
OperateCenter.getInstance().comment(EaGameActivity.this);

游戏盒跳转

原有的游戏盒跳转,即礼包、活动、论坛、游戏盒详情接口,迁移为通用跳转接口,开发需要向运营获取跳转参数,跳转方式参考下文通用跳转接口

接口调用:通用跳转(推荐)

游戏可能需要多形式、更可变的跳转,对此 SDK 对此进行支持。使用这一特性需要:

// 先判断是否支持key的协议
OperateAction.support(key, new OpeResultListener() {
        @Override
        public void onResult(int code, @Nullable String msg) {
            // 使用key进行跳转,0为支持,其他不支持
            if (code == 0) { 
                OperateAction.perform(key);
            }
        }
    });

极少数跳转需要传递额外参数,如图片分享,此时要使用另一个接口

// 入口 key
// 跳转intent,intent的内部是键值对
OperateAction.perform(key, intent);

云存档

单机类游戏中有些没有自己的服务器,但是又需要跨设备持久化。因此,SDK 提供云存档接口,游戏只需要初始化、读取、保存存档即可。
使用这些接口前,游戏开发者需要:

如此,可以保证存档不丢失、不混乱。以上情况可以参考代码下面初始化 CloudArchive.init 接口

初始化

// publicKey: 公钥,向运营获取公钥
CloudArchive.init(publicKey, new CloudArchive.InitListener() {
    @Override
    public void onSuccess() {
        //初始化成功
    }

    @Override
    public void onFailure(int code, @NonNull String message) {
        //初始化故障
    }

    @Override
    public void onUserChanged(@Nullable CloudArchive previousUserCloudArchive, @Nullable CloudArchive currentUserCloudArchive) {
        /*
          未登录 -> 账号A : previousUserCloudArchive == null && currentUserCloudArchive != null
          账号A -> 账号B : previousUserCloudArchive != null && currentUserCloudArchive != null
          账号A -> 未登录 : previousUserCloudArchive != null && currentUserCloudArchive == null
        */
        //登录用户改变
        if (previousUserCloudArchive != null) {
            //保存前一个登录用户的存档
            previousUserCloudArchive.save(getArchive(), new CloudArchive.SaveListener() {
                @Override
                public void onSuccess() {
                }

                @Override
                public void onFailure(int code, @NonNull String message) {
                }
            });
        }
        reset();//重置游戏
        if (currentUserCloudArchive != null) {
            //获取当前登录用户的存档列表
            currentUserCloudArchive.list(new CloudArchive.ListListener() {
                @Override
                public void onSuccess(@NonNull List<Archive> archiveList) {
                    
                }

                @Override
                public void onFailure(int code, @NonNull String message) {

                }
            });
        }
    }
});

其中Archive——存档定义如下:

public class Archive {
    /**
     * 存档索引,建议使用[1,10]区间的正整数
     */
    public final int index;

    /**
     * 存档标题
     */
    public final String title;

    /**
     * 最后更新时间
     */
    public final int updateTime;

    /**
     * 存档数据
     */
    public final String data;
    ...
}

存档列表

CloudArchive.getCloudArchive().list(new CloudArchive.ListListener() {
    @Override
    public void onSuccess(@NonNull List<Archive> archiveList) {
        //获取存档列表成功
    }

    @Override
    public void onFailure(int code, @NonNull String message) {
        //获取存档列表故障
    }
});

读取存档

//index: 存档索引
CloudArchive.getCloudArchive().get(index, new CloudArchive.GetListener() {
    @Override
    public void onSuccess(@NonNull Archive archive) {
        //读取存档成功
    }

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

保存存档

//archive: 存档
CloudArchive.getCloudArchive().save(archive, new CloudArchive.SaveListener() {
    @Override
    public void onSuccess() {
        //保存存档成功
    }

    @Override
    public void onFailure(int code, @NonNull String message) {
        //保存存档故障
    }
});