本文档面向 Android 开发者,描述的接入方式适合运营在 4399 平台的独代游戏。
SDK和相关示例的下载地址可在游戏创建后,进入 「游戏管理-SDK服务」 页面获取。
SDK 中使用的用户信息及隐私政策参考:《4399通行证用户服务协议》 与《隐私政策》
为4399独家代理的游戏提供的SDK,提供游戏激活、礼包、更新等服务,和游戏官方消息官方活动、论坛等丰富而优质的游戏内容
若游戏有被静态修改后二次打包的情况,SDK 对此类破解提供了一些解决方案,详见 服务端文档
SDK 优先提供在线aar
依赖方式,Demo 结构遵循 Android
Studio(as) 规范,但仍然保留了 jar+res
的依赖方式。
SDK 支持的编译配置 android:minSdkVersion >= 16。
服务端支持额外的功能,参考 SDK 服务端接入文档
首次接入 SDK,要在 4399 开放平台
注册应用,主要是提交APK、素材等信息。
完成后,开发者将得到 SDK
的基础参数:game key
或GameKey
,游戏在 4399
平台的运营标识
根据游戏需要,以下三种方式可选其一
build.gradle
中引入以下内容即可{
repositories {
maven // 4399 SDK 开放仓库:正式
'https://mvn.4399doc.com/repository/maven-releases'
url }
{
maven // 4399 SDK 开放仓库:快照
'https://mvn.4399doc.com/repository/maven-snapshots'
url }
}
{
dependencies fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation
// 运营 SDK:建议使用最新版本,可通过浏览仓库地址或向运营咨询版本
"cn.m4399.sdk:operate:3.8.0"
implementation
// volley 和 support 是 SDK 使用的外部依赖,若接入方已有,可忽略
'com.android.volley:volley:1.2.1'
implementation //noinspection GradleCompatible
"com.android.support:support-v4:28.0.0"
implementation }
注意:若使用7.0+
版本的 gradle 及 android build
插件,仓库地址应配置在settings.gradle
中
operate/libs/volley-v1.2.1.jar
operate/libs/support-v13-23.2.1.jar
Gradle 文件中注意添加本地依赖
{
dependencies fileTree(dir: 'libs', include: ['*.jar','*.aar'])
implementation }
若游戏不需要支持所有 abi
,可以按需选用。
aar
依赖方式,按以下方式配置需要的abi
;jar+res
依赖方式需要手动删除不需要的abi
目录
android {
defaultConfig {
ndk {
// 根据游戏需要选择
abiFilters "armeabi", "armeabi-v7a", "x86", "arm64-v8a"
}
}
}
在游戏项目的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
// 配置全局属性,如横竖屏配置
= new OperateConfig.Builder(this)
OperateConfig operateConfig // 设置调试模式,可选,true时打开,默认false,发布前必须设置为false或删除该行
.setDebugEnabled(false)
// 设置游戏运营 key,此参数需要在原创开放平台注册应用后得到
.setGameKey(GAME_KEY)
// 设置SDK页面方向,应与游戏方向一致,部分第三方页面需要在AndroidManifest中设置
.setOrientation(GAME_ORIENTATION)
// 设置游戏是否兼在高于Android 9.0版本系统容全面屏,true兼容,默认false
.compatNotch(GAME_COMPACT_NOTCH)
.build();
.getInstance().gameProtocol(activity, operateConfig, new OpeResultListener() {
OperateCenter@Override
public void onResult(int code, @Nullable String message) {
// 其中code:0、用户同意协议;160001、没有协议更新;用户不同意,则SDK结束进程,退出游戏
// 同意后再初始化其他部分,如初始化 4399 SDK
.init(this, operateConfig, new ExclusiveAgent.OnInitGlobalListener() {
ExclusiveAgent/*
* 激活状态回调
*
* 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
-->
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 |
竖屏 |
android:screenOrientation
,如下所示<!--
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: 活动码,用户输入
*/
.useActivityCode("your_activity_id",
ExclusiveAgent"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: 礼包码,用户输入
*/
.useGiftCode("your_gift_id",
ExclusiveAgent"your_gift_code",
new OpeResultListener() {
@Override
public void onResult(int code, @Nullable String message) {
// code 0:成功 3:失败 5:异常中止
}
});
/*
游戏分享
*/
.getInstance().share(EaGameActivity.this); OperateCenter
调用comment接口,唤起SDK游戏评分界面
/*
游戏评分
*/
.getInstance().comment(EaGameActivity.this); OperateCenter
原有的游戏盒跳转,即礼包、活动、论坛、游戏盒详情接口,迁移为通用跳转接口,开发需要向运营获取跳转参数,跳转方式参考下文通用跳转接口
游戏可能需要多形式、更可变的跳转,对此 SDK 对此进行支持。使用这一特性需要:
// 先判断是否支持key的协议
.support(key, new OpeResultListener() {
OperateAction@Override
public void onResult(int code, @Nullable String msg) {
// 使用key进行跳转,0为支持,其他不支持
if (code == 0) {
.perform(key);
OperateAction}
}
});
极少数跳转需要传递额外参数,如图片分享,此时要使用另一个接口
// 入口 key
// 跳转intent,intent的内部是键值对
.perform(key, intent); OperateAction
单机类游戏中有些没有自己的服务器,但是又需要跨设备持久化。因此,SDK
提供云存档接口,游戏只需要初始化、读取、保存存档即可。
使用这些接口前,游戏开发者需要:
public key
公钥,这是调用接口的必要参数。如此,可以保证存档不丢失、不混乱。以上情况可以参考代码下面初始化
CloudArchive.init
接口
// publicKey: 公钥,向运营获取公钥
.init(publicKey, new CloudArchive.InitListener() {
CloudArchive@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) {
//保存前一个登录用户的存档
.save(getArchive(), new CloudArchive.SaveListener() {
previousUserCloudArchive@Override
public void onSuccess() {
}
@Override
public void onFailure(int code, @NonNull String message) {
}
});
}
reset();//重置游戏
if (currentUserCloudArchive != null) {
//获取当前登录用户的存档列表
.list(new CloudArchive.ListListener() {
currentUserCloudArchive@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;
...
}
.getCloudArchive().list(new CloudArchive.ListListener() {
CloudArchive@Override
public void onSuccess(@NonNull List<Archive> archiveList) {
//获取存档列表成功
}
@Override
public void onFailure(int code, @NonNull String message) {
//获取存档列表故障
}
});
//index: 存档索引
.getCloudArchive().get(index, new CloudArchive.GetListener() {
CloudArchive@Override
public void onSuccess(@NonNull Archive archive) {
//读取存档成功
}
@Override
public void onFailure(int code, @NonNull String message) {
//读取存档故障
}
});
//archive: 存档
.getCloudArchive().save(archive, new CloudArchive.SaveListener() {
CloudArchive@Override
public void onSuccess() {
//保存存档成功
}
@Override
public void onFailure(int code, @NonNull String message) {
//保存存档故障
}
});