游戏SDK(三)架构设计之代码实现1

news/2024/11/23 5:12:07/

前言

上一篇介绍了游戏SDK架构设计的思路,大体的项目结构如下:

- sdk-demo  // sdk 测试 demo 
- sdk-api   // sdk 接口模块
- sdk-manager    // sdk 业务分发管理
- sdk-channel   // 登录支付渠道- sdk-channel-huawei  // 具体的渠道sdk ,这里仅做示例- sdk-channel-xiaomi.... // 其他- sdk-channel-googleplay  // 公司自行开发的Google play渠道sdk 也放在这一层- sdk-channel-guoneiguanwang    // 公司自行开发的国内官网渠道sdk 也放在这一层
- sdk-data    // sdk数据上报- sdk-data-appsflyer   // AF 数据上报 ,这里仅做示例....// 其他
- sdk-plugin   // 游戏sdk插件- sdk-plugin-yuyin   // 语音插件,这里仅做示例...    // 其他
- sdk-core // 游戏SDK公共业务
- sdk-common // 基础库
- buildSrc   // 一些打包编译脚本
- doc  // 文档
...  // 其他分类看项目需要

渠道/数据上报平台/插件管理

配置文件:(以下仅作示例)

AppId=1001
#渠道名称
ChannelName=googleplay 
#需要上传的数据平台
DataChannel=appsflyer,facebook,···
#需要增加的插件
PluginName=twitter,···

以上配置文件是一个 .properties 的文件,定义了当前的应用(AppId)等参数,指定当前需要运行什么渠道(ChannelName),需要的数据平台(DataChannel),需要增加的插件(PluginName),等等还会有其他参数,这里只是列出示例,格式可以根据项目需要自定义。

渠道管理模块根据配置文件制定的渠道名加载对应的渠道实现以及数据平台模块、插件模块。

P.S: 以上配置文件应由游戏SDK后台配置参数,并生成配置文件,配合打包工具打包到对应渠道的.apk中,测试时可以放置一份在 demo 模块中。

代码实现

  1. API 接口设计,定义抽象类,定义对外接口。区分抽象接口和非抽象接口,抽象接口是子类必须实现的,比如渠道的登录、支付,一般渠道都有这些功能。除业务功能接口外,还有Application 和 Activity 的生命周期接口。

    // 接口可以分类定义,便于阅读,比如 IApplicationLifeCycle 是 applicaiton 的生命周期接口
    public abstract class AbstractChannel implements IApplicationLifeCycle, IActivityLifeCycle,IDataMonitor, IActivity, IShare, ISocial, IChat, IPush {/****************************基础接口*******************//*** 设置UserCallBack,初始化及用户登录结果会通过此回调对象通知给游戏** @param userCallBack*/public abstract void setUserCallBack(UserCallBack userCallBack);/*** 登录接口** @param activity     - 当前活跃的Activity* @param customParams - 扩展参数,JSON格式,默认为null*/public abstract void login(Activity activity, String customParams);// ...其他接口
    }// IActivityLifeCycle 的接口
    public interface IActivityLifeCycle {/*** activity 启动** @param activity           -游戏当前活动的页面* @param savedInstanceState -存储的bundle*/void onCreate(Activity activity, Bundle savedInstanceState);/*** activity 销毁时调用到** @param activity -游戏当前活动的页面*/void onDestroy(Activity activity);/*** 活动页面 显示时调用到** @param activity -游戏当前活动的页面*/void onResume(Activity activity);//··· 其他接口类似
    }// ····其他接口类似,再次不一一列举
    
  2. 由于预定义对外的接口定义好了之后一般不会做更改,但有时候不同的渠道确实有不同的功能需要实现,所以预定义一个自定义接口及通用回调,根据传入的方法名称去调用。

    /*** 提供给游戏的动态接口** @param methodName  方法名称* @param methodParam 方法参数,可为null* @param callback    通用回调* @param customInfo  透传字段,回调中回传给游戏* @return 返回, 可为Null*/public abstract Object callMethod(String methodName, Object methodParam, GenericCallBack callback, String customInfo);
    
  3. 游戏研发只接入SDK的 api 层,不需要关心及引用其他的模块,所以这里需要一个反射获取渠道管理类,再由渠道管理类具体实现应该由哪个渠道实现CP调用的接口。

    public class ProjectManager {// 渠道管理类类名private static String CHANNEL_MANAGER_CLASS_NAME = "com.xxx.client.xxx.ChannelManager";/********************* 同步锁双重检测机制实现单例模式(懒加载)********************/private volatile static ProjectManager projectManager;public static ProjectManager init() {if (projectManager == null) {synchronized (ProjectManager.class) {if (projectManager == null) {projectManager = new ProjectManager();}}}return projectManager;}public static ProjectManager getInstance() {return projectManager;}/********************* 同步锁双重检测机制实现单例模式 ********************/private ProjectManager() {}/*** 加载渠道管理类** @return* @throws RuntimeException*/public synchronized AbstractChannel loadChannelManager() throws RuntimeException {AbstractChannel p = null;Class<?> glass = null;if (TextUtils.isEmpty(CHANNEL_MANAGER_CLASS_NAME)) {LogUtil.e(TAG, "loadChannelManager: the class name is empty!");return p;}try {glass = Class.forName(CHANNEL_MANAGER_CLASS_NAME);} catch (ClassNotFoundException e) {LogUtil.i(TAG, "loadChannelManager: " + "do not find " + CHANNEL_MANAGER_CLASS_NAME);}try {//尝试调用getInstanceMethod m = glass.getDeclaredMethod("getInstance");m.setAccessible(true);p = (AbstractChannel) m.invoke(null, new Object[]{});} catch (NoSuchMethodException e1) {//调用getInstance失败后,尝试new其对象try {p = (AbstractChannel) glass.newInstance();} catch (Exception exception) {LogUtil.w(TAG, "loadChannelManager", "glass.newInstance(): " + "do not find " + CHANNEL_MANAGER_CLASS_NAME);}} catch (Exception exception) {LogUtil.e(TAG, "loadChannelManager", "glass.getInstance(): " + "do not find " + CHANNEL_MANAGER_CLASS_NAME, exception.toString());}if (p == null) {LogUtil.w(TAG, CHANNEL_MANAGER_CLASS_NAME + " is empty.");}return p;}
    }
    

    关于Java 反射的内容可以看这篇文章:Java反射机制-框架设计的灵魂

  4. 定义渠道的父类、数据平台的父类、插件父类(如果插件功能差别很大,没有共同点,可以单独使用反射获取),可以在父类里处理渠道接口共同的地方,比如自定义方法的实现。

    以渠道的父类为例:

    public class SdkChannel extends AbstractChannel {@Overridepublic void setUserCallBack(UserCallBack userCallBack) {}@Overridepublic void login(Activity activity, String customParams) {}/**************************** Basic interface ******/@Overridepublic void logout(final Activity activity, final String customParams) {if (mUserCallBack != null) {mUserCallBack.onLogoutFinish(ErrorCode.SUCCESS, "");}}@Overridepublic void pay(Activity activity, PayInfo payInfo, PayCallBack payCallBack) {}// ··· 其他接口类似
    }
    
  5. 在Applicaiton 启动的时候获取配置文件的 ChannelName 的值,判断需要加载的渠道

    // 读取配置文件
    PropertiesUtil.loadFromAssetsConfig(context);
    // 给渠道定义统一规则类名
    public static final String CHANNEL_CLASS_PATTERN = "com.xxx.client.{渠道名}.inner.ChannelImpl";// 读取配置文件中的 ChannelName ,拼接成最后的类名
    // 加载类
    public static <T> T loadClass(ClassLoader loader, String className, Class<T> claz) {if (loader == null) {return null;}try {Class<?> loaded = loader.loadClass(className);if (claz.isAssignableFrom(loaded)) {return (T) loaded.newInstance();}} catch (ClassNotFoundException e) {LogUtil.i("can not find class " + className);} catch (Exception e) {LogUtil.e("can not create instance for class " + className, e);}return null;}
    
  6. 在渠道管理类统一调用接口

    public class ChannelManager extends AbstractChannel {@Overridepublic void login(Activity activity, String customParams) {// 尽量在外层catch 异常,避免因为游戏SDK崩溃try {// 获取渠道类SdkChannel loginChannel = getLoginChannel();if (loginChannel != null) {// 调用渠道的登录接口loginChannel.login(activity, customParams);} else {LogUtil.i(TAG, "can not find  channel implement, please check if your main activity is inherited from SDKSDK activity or call SDKSDK application lifecyle interfaces(such as onCreate, onStart and etc.)");}} catch (Exception e) {LogUtil.e(CATCH_UNEXPECT_EXCEPTION, e);}}}// 渠道实现类集成父类SdkChannel
    public class ChannelImpl extends SdkChannel {@Overridepublic void login(Activity activity, String customParams) {// 渠道的登录实现}
    }
    
  7. 项目回调设计:模块之间难免有互相调用,每个事件的事件流都有结果,这就需要回调机制。

    // 以下的都是示例,其他的回调都是类似// 在API层定义CP可调用的回调
    public interface UserCallBack {/*** 登录成功,游戏需要根据authInfo到SG服务器进行登录验证** @param code* @param authInfo*/void onLoginSuccess(int code, String authInfo);/*** 登录失败, 建议游戏返回账号登录界面** @param code* @param msg* @param channelCode*/void onLoginFail(int code, String msg, String channelCode);
    }// 再在管理类里包装一层,统一处理最后的 用户信息
    public class UserCallBackWrapper implements UserCallBack {@Overridepublic void onLoginSuccess(int code, String authInfo) {try {// 在这里统一处理共同操作,比如上报登录成功数据if (getDataMonitors() != null) {for (DataMonitor dataMonitor : getDataMonitors()) {dataMonitor.onLoginSuccess(code, authInfo);}}} catch (Exception e) {LogUtil.e(UNKOWN_EXCEPTION_MSG, e);}try {LogUtil.i("login success, begin to call game callback");this.gameCallback.onLoginSuccess(code, authInfo);} catch (Exception e) {LogUtil.e(UNKOWN_EXCEPTION_MSG, e);}}
    }
    
  8. 其他就是具体的渠道实现。数据和插件类似。

以上,从CP调用到渠道的实现,再从渠道实现的接口回调给CP的全过程已经完成。其他在之后的篇幅介绍。


http://www.ppmy.cn/news/20815.html

相关文章

JAVA01_30学习总结(Spring入门)

今日内容 1. Spring引入 Spring是用来解耦的专用框架和工具,代替普通new的方式,利用反射! 2. 简单工厂模式 简单工厂模式-静态方法工厂模式该工厂模式的核心思想就是-利用配置文件的键值对特性,利用反射来获取需要的对象,不在使用new的方式,进行解耦!步骤1)构造方法私有化,…

(dfs模板)使用dfs算法时没有对回溯时的状态量进行恢复

模板&#xff1a; vector<int> path; vector<vector<int>> result; void backTracking(......) { if (......) { ...... return; } for (int i 0; i < size; i) { path.emplace_b…

torchvision

tochvision是pytorch中用于图像处理的库。 torchvision由以下部分组成&#xff1a; torchvision.datasets&#xff1a;加载数据集 torchvision.models&#xff1a;提供训练模型 torchvision.transforms&#xff1a;图形变换方法&#xff08;裁剪&#xff0c;转向等&#xf…

压缩包文件如何设置和删除密码

压缩软件除了可以压缩和解压文件&#xff0c;还可以作为加密软件&#xff0c;给压缩的文件设置密码来保护文件。 今天就来看下两个常用的压缩软件是如何设置和删除密码的。 先说说WinRAR这个最常用的压缩软件&#xff0c;它可以根据不同的需求设置单次密码和永久自动加密。 …

uniapp项目

目录 一、HBuilder创建项目 二、引入uView 2.1 npm方式安装 2.2 下载方式安装 三、小程序的分包 三、App.vue中的生命周期 四、工具封装 五、api接口请求封装 六、store 七、加载顺序 八、flex的使用 一、HBuilder创建项目 文件--新建--项目--默认模板--Vue2--创建 …

C++11 入门

作者&#xff1a;小萌新 专栏&#xff1a;C进阶 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍C11的一些背景知识 本篇博客主要是讲解一些关键字 C11前言C11诞生简介列表初始化{}初始化关键字autodecltypenullptr范围forSTL的更…

【网络安全】记一次红队渗透实战项目

前言 【一一帮助安全学习&#xff08;网络安全面试题学习路线视频教程工具&#xff09;一一】 一、信息收集 信息收集非常重要&#xff0c;有了信息才能知道下一步该如何进行&#xff0c;接下来将用nmap来演示信息收集 1、nmap扫描存活IP 由于本项目环境是nat模式需要项目…

IronWebScraper for .NET 2023.1 Crack

用于从 HTML Web 应用程序中提取干净的结构化数据的 C# 框架。 IronWebScraper for .NET 2023 &#xff1a;Adds support for Microsoft .NET 6 and .NET 7.January 27, 2023 - 17:25 New Version &#xff1a;&#xff1a;&#xff1a; Added support for Microsoft .NET 6 an…