手游SDK —第三篇(架构代码实现篇)
各位看官,上文 手游SDK— 第二篇(架构设计篇)已经介绍过了架构实现的基本思路,那废话就不多说了,进入正题,看下代码的具体实现。
PS:不具备实际项目用途,仅做框架Demo展示
项目结构搭建
首先咱们先整体搭建下项目工程的框架模块,根据之前的项目架构设计搭建的,这里也比较简单,我简单附上一张图。
项目结构.pngPS:相关的模块管理可参考
项目代码实现
第一部分:基础库
基础库涉及的功能库比较多,大概抽离部分模块大体讲解下,主要分两部分:涉及SDK架构的代码实现和部分三方框架的封装思路讲解。
项目/插件/渠道管理设计
项目的框架主体骨架
配置文件格式:Project_config.txt / Plugin_config.txt / Channel_config.txt
{
"project": [
{
"project_name": "project",
"class_name": "com.bzai.gamesdk.project.juhe.JuHeProject",
"description": "聚合SDK项目",
"version": "1.0.0"
}
]
}
{
"plugin": [
{
"plugin_name": "plugin_wechat",
"class_name": "com.bzai.gamesdk.plugin.wechat.WechatPlugin",
"description": "微信功能插件",
"version": "5.1.4"
},
{
"plugin_name": "plugin_alipay",
"class_name": "com.bzai.gamesdk.plugin.alipay.AlipayPlugin",
"description": "支付宝功能插件",
"version": "15.5.5"
}
]
}
{
"channel": [
{
"channel_name": "channel",
"class_name": "com.bzai.gamesdk.channel.test.TestChannelSDK",
"description": "测试渠道SDK",
"version": "1.0.0"
}
]
}
比较简单就不细说,是Json格式的数据,大家可以根据需求进行数据拓展。
PS:备注说明下,其实是可以通过一个配置文件就能配置完的,但是将项目、插件、渠道分别配置加载的目的是方便快速的分别替换项目、插件、渠道配置。一个项目Project可以对应多个渠道、多个插件。后续可以在多渠道、多插件上进行快速的插拔和后台开关切换渠道,不过正常的需求都是一个项目对应零个或一个渠道、零个或多个功能插件。
代码实现
抽象类解析
1、抽象的项目Project类,主要是面向SDKAPI接口设计的,预定义对外的接口及Activity生命周期接口。但是通常对外的接口一般设计好后非必要就很少修改,为了对外接口设计进行扩展,预定义通用的拓展接口extendFunction(Activity activity, int functionType, Object object, CallBackListener callBackListener),通过不同的funtionType进行扩展。
public abstract class Project implements ProguardInterface{
/***************************** Project 加载接口 **********************************/
public ProjectBeanList.ProjectBean projectBean;
private boolean hasInited;
protected synchronized void initProject() {
if (hasInited) {
return;
}
hasInited = true;
}
@Override
public String toString() {
return "Project{" + "projectBean=" + projectBean + ", hasInited=" + hasInited + '}';
}
/***************************** 顶层 Project 功能接口:初始化、登陆、支付、退出 **********************************/
/**
* 初始化
*/
public void init(Activity activity, String gameid, String gamekey, CallBackListener callBackListener) {}
/**
* 登录
*/
public void login(Activity activity, HashMap<String,Object> loginParams) {}
/**
* 支付
*/
public void pay(Activity activity, HashMap<String,Object> payParams, CallBackListener callBackListener) {}
/**
* 切换账号
*/
public void switchAccount(Activity activity){}
/**
* 登出
*/
public void logout(Activity activity) {}
/**
* 退出
*/
public void exit(Activity activity, CallBackListener callBackListener) {}
/**
* 上报数据
*/
public void reportData(Context context, HashMap<String,Object> dataMap){}
/**
* 设置SDK账号监听
*/
public void setAccountCallBackLister(CallBackListener callBackLister){}
/**
* 显示SDK悬浮窗,将登录、支付等信息回调
*/
public void showFloatView(Activity activity){}
/**
* 关闭SDK悬浮窗
*/
public void dismissFloatView(Activity activity){}
/**
* 拓展接口,处理渠道的定制接口
*/
public void extendFunction(Activity activity, int functionType, Object object, CallBackListener callBackListener){}
/**
* 获取渠道ID
* @return
*/
public String getChannelID(){
return null;
}
/******************************* 顶层 Project 生命周期接口 (目前实现各插件的生命周期)******************************/
public void onCreate(Activity activity, Bundle savedInstanceState) {
PluginManager.getInstance().onCreate(activity, savedInstanceState);
}
public void onStart(Activity activity) {
PluginManager.getInstance().onStart(activity);
}
public void onResume(Activity activity) {
PluginManager.getInstance().onResume(activity);
}
public void onPause(Activity activity) {
PluginManager.getInstance().onPause(activity);
}
public void onStop(Activity activity) {
PluginManager.getInstance().onStop(activity);
}
public void onRestart(Activity activity) {
PluginManager.getInstance().onRestart(activity);
}
public void onDestroy(Activity activity) {
PluginManager.getInstance().onDestroy(activity);
}
public void onNewIntent(Activity activity, Intent intent) {
PluginManager.getInstance().onNewIntent(activity,intent);
}
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
PluginManager.getInstance().onActivityResult(activity, requestCode, requestCode, data);
}
public void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) {
PluginManager.getInstance().onRequestPermissionsResult(activity,requestCode,permissions,grantResults);
}
}
2、抽象的功能插件Plugin类,主要是面向第三方SDK设计的,预定义生命周期方法。具体功能实现接口根据功能设计。
public class Plugin implements LifeCycleInterface, ProguardInterface {
public PluginBeanList.PluginBean pluginBean;
private boolean hasInited;
protected synchronized void initPlugin() {
if (hasInited) {
return;
}
hasInited = true;
}
@Override
public String toString() {
return "Plugin{" + "pluginMessage=" + pluginBean + ", hasInited=" + hasInited + '}';
}
/****************************************生命周期方法*********************************************/
public void onCreate(Context context, Bundle savedInstanceState) {}
public void onStart(Context context) {}
public void onResume(Context context) {}
public void onPause(Context context) {}
public void onStop(Context context) {}
public void onRestart(Context context) {}
public void onDestroy(Context context) {}
public void onNewIntent(Context context, Intent intent){}
public void onActivityResult(Context context, int requestCode, int resultCode, Intent data) {}
public void onRequestPermissionsResult(Context context, int requestCode, String[] permissions, int[] grantResults) {}
}
3、抽象的渠道Channel类,主要是面向渠道SDK设计的,主要分为业务必须接口和非业务必须接口。必须业务接口设计为抽象,子类必须实现的。
/**
* 用于描述渠道SDK的顶层接口
*/
public abstract class Channel extends ChannelListenerImpl implements LifeCycleInterface, ProguardInterface {
/***************************** Channel 加载必须接口 **********************************/
/**
* 实例渠道插件对象,必须实现
*/
protected abstract void initChannel();
public ChannelBeanList.ChannelBean channelBean;
@Override
public String toString() {
return "Channel{" + "channelBean=" + channelBean +'}';
}
/****************************** 必须业务逻辑接口 ****************************/
public static final String PARAMS_OAUTH_TYPE = "PARAMS_OAUTH_TYPE";
public static final String PARAMS_OAUTH_URL = "PARAMS_OAUTH_URL";
/**
* 返回渠道的ID(用于识别渠道)
*/
public abstract String getChannelID();
/**
* 由于个别渠道只简单实现登录、支付接口,
* 对外提供该接口给CP判断该接口是否已实现
* @param FuncType
* @return
*/
public abstract boolean isSupport(int FuncType);
/**
* 渠道SDK初始化
*/
public abstract void init(Context context, HashMap<String,Object> initMap, CallBackListener initCallBackListener);
/**
* 渠道SDK登录
*/
public abstract void login(Context context, HashMap<String,Object> loginMap, CallBackListener loginCallBackListener);
/**
* 渠道切换账号
*/
public abstract void switchAccount(Context context, CallBackListener changeAccountCallBackLister);
/**
* 渠道SDK注销账号
*/
public abstract void logout(Context context, CallBackListener logoutCallBackLister);
/**
* 渠道SDK支付
*/
public abstract void pay(Context context, HashMap<String,Object> payMap, CallBackListener payCallBackListener);
/**
* 渠道SDK退出
*/
public abstract void exit(Context context, CallBackListener exitCallBackLister);
/****************************** 非必须业务逻辑接口 ****************************/
/**
* 返回渠道版本号
*/
public String getChannelVersion(){
return null;
}
/**
* 渠道SDK个人中心
*/
public void enterPlatform(Context context, CallBackListener enterPlatformCallBackLister){}
/**
* 显示渠道SDK悬浮窗
*/
public void showFloatView(Context context){}
/**
* 关闭渠道SDK悬浮窗
*/
public void dismissFloatView(Context context){}
/**
* 渠道SDK上报数据
*/
public void reportData(Context context, HashMap<String,Object> dataMap){}
/**
* 横竖屏
* @return true为横屏,false为竖屏
*/
public boolean getOrientation(Context context){
boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
return isLandscape;
}
/****************************** 非业务逻辑 生命周期接口 ****************************/
@Override
public void onCreate(Context context, Bundle savedInstanceState) {}
@Override
public void onStart(Context context) {}
@Override
public void onResume(Context context) {}
@Override
public void onPause(Context context) {}
@Override
public void onStop(Context context) {}
@Override
public void onRestart(Context context) {}
@Override
public void onDestroy(Context context) {}
@Override
public void onNewIntent(Context context, Intent intent) {}
@Override
public void onActivityResult(Context context, int requestCode, int resultCode, Intent data) {}
@Override
public void onRequestPermissionsResult(Context context, int requestCode, String[] permissions, int[] grantResults) {}
}
配置文件的bean类
配置文件的bean类,通过反射加载Project / Plugin / Channel具体实现对象。示例展示Project,其他两个是类似的,详看项目Demo。
public class ProjectBeanList extends ProguardObject{
private List<ProjectBean> project;//注意解析的名字要跟文件一致,不然会导致解析错误
public List<ProjectBean> getProject() {
return project;
}
public void setProject(List<ProjectBean> project) {
this.project = project;
}
public static class ProjectBean extends ProguardObject{
private static final String TAG = "ProjectBean";
/**
* 反射插件的单例模式方法
* 返回的插件可能为空
* @return
*/
public Project invokeGetInstance() {
Project p = null;
Class<?> glass = null;
if (TextUtils.isEmpty(class_name)) {
LogUtils.debug_w(TAG, "invokeGetInstance: the class_name is blank");
return p;
}
try {
glass = Class.forName(class_name);
} catch (ClassNotFoundException e) {
LogUtils.debug_w(TAG, "invokeGetInstance: " + "do not find " + class_name);
}
try {
//尝试调用getInstance
Method m = glass.getDeclaredMethod("getInstance", new Class<?>[]{});
m.setAccessible(true);
p = (Project) m.invoke(null, new Object[]{});
} catch (NoSuchMethodException e1) {
//调用getInstance失败后,尝试new其对象
try {
p = (Project) glass.newInstance();
} catch (Exception exception) {
LogUtils.debug_w(TAG, "glass.newInstance(): " + "do not find " + class_name);
}
} catch (Exception exception) {
LogUtils.debug_w(TAG, "glass.getInstance(): " + "do not find " + class_name);
}
if (p == null) {
LogUtils.debug_w(TAG, class_name + " is empty.");
} else {
p.projectBean = this;
}
return p;
}
/**
* project_name : 项目名称
* class_name : 项目入口类
* description : 项目描述
* version : 版本信息
*/
private String project_name;
private String class_name;
private String description;
private String version;
public String getProject_name() {
return project_name;
}
public void setProject_name(String plugin_name) {
this.project_name = plugin_name;
}
public String getClass_name() {
return class_name;
}
public void setClass_name(String class_name) {
this.class_name = class_name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
@Override
public String toString() {
return "ProjectBean{" +
" project_name='" + project_name + '\'' +
", class_name='" + class_name + '\'' +
", description='" + description + '\'' +
", version='" + version + '\'' +
'}';
}
}
}
配置管理类
主要加载配置文件及对外提供加载接口和实现部分逻辑控制。(示例展示Project,其他两个是类似的,详看项目Demo。注意:Plugin需遍历生命周期加载N个功能插件的生命周期。)
public class ProjectManager {
private static final String TAG = "ProjectManager";
private static String PROJECT_CONFIG = "Project_config.txt";
private static Project project;
private HashMap<String, ProjectBeanList.ProjectBean> projectBeans = new HashMap<>();
/********************* 同步锁双重检测机制实现单例模式(懒加载)********************/
private volatile static ProjectManager projectManager;
public static ProjectManager init(Context context) {
if (projectManager == null) {
synchronized (ProjectManager.class) {
if (projectManager == null) {
projectManager = new ProjectManager(context);
}
}
}
return projectManager;
}
public static ProjectManager getInstance() {
return projectManager;
}
/********************* 同步锁双重检测机制实现单例模式 ********************/
private ProjectManager(Context context) {
parse(context, PROJECT_CONFIG);
}
private void parse(Context context, String pluginFilePath) {
//从配置文件中,读取插件配置
StringBuilder projectContent = FileUtils.readAssetsFile(context, pluginFilePath);
String strProjectContent = String.valueOf(projectContent);
//进行解析
Gson gson = new Gson();
if (!TextUtils.isEmpty(strProjectContent)) {
try {
ProjectBeanList projectBeanList = gson.fromJson(strProjectContent, ProjectBeanList.class);
if (projectBeanList.getProject() != null && projectBeanList.getProject().size() != 0) {
//如果解析结果无误,载入到listPluginBean中去
for (ProjectBeanList.ProjectBean projectBean : projectBeanList.getProject()) {
projectBeans.put(projectBean.getProject_name(), projectBean);
}
//打印解析结果
LogUtils.debug_i(TAG, PROJECT_CONFIG +" parse: \n" + projectBeans.toString());
} else {
//解析结果出错
LogUtils.e(TAG, PROJECT_CONFIG + " parse error.");
}
} catch (Exception e) {
//解析结果出错
LogUtils.e(TAG, PROJECT_CONFIG + " parse exception.");
e.printStackTrace();
}
}else {
LogUtils.e(TAG, PROJECT_CONFIG + " parse is blank.");
}
}
private boolean hasLoaded;
private static HashMap<String, Project> ProjectLists = new HashMap<String, Project>();
/**
* 加载所有的Project,可能存在多个项目
*/
public synchronized void loadAllProjects() {
if (hasLoaded) {
return;
}
HashMap<String, ProjectBeanList.ProjectBean> entries = projectBeans;
Set<String> set = entries.keySet();
for (String key : set) {
loadProject(key);
}
LogUtils.debug_i(TAG, "loadAllProjects:" + ProjectLists.toString());
hasLoaded = true;
}
/**
* 加载一个项目,返回的Project可能为空
*
* @param projectName
* @return
* @throws RuntimeException
*/
private Project loadProject(String projectName) throws RuntimeException {
// 1.查看从配置文件中读取的插件列表,是否存在此插件
HashMap<String, ProjectBeanList.ProjectBean> entries = projectBeans;
ProjectBeanList.ProjectBean projectBean = entries.get(projectName);
if (projectBean == null) {
LogUtils.debug_i(TAG, "The project [" + projectName + "] does not exists in " + PROJECT_CONFIG);
return null;
}
Project project = null;
// 2.调用其单例模式方法
project = projectBean.invokeGetInstance();
if (project != null) {
// 3.反射初始化插件
project.initProject();
// 4.将已加载好的插件,添加到插件列表中去
ProjectLists.put(projectName, project);
}
return project;
}
/**
* 获取特定项目
* 可能为空
*
* @param projectName
* @return
*/
public Project getProject(String projectName) {
if (!hasLoaded) {
LogUtils.debug_i(TAG, "getProject: " + projectName + "Project not loaded yet");
return null;
}
Project project = null;
HashMap<String, Project> entries = ProjectLists;
project = entries.get(projectName);
return project;
}
}
项目回调设计
任何一个项目都会存在模块之间的调用,任何事件的事件流都会有事件结果回调。都会涉及到回调机制。
相关原理可参考下这篇文章: Java回调机制解读简单介绍下项目的回调设计思路:
定义基类的回调接口,定义成功和失败回调,失败回调可以通过错误码和描述信息来区分错误事件的详细信息。这里有个小技巧就是定义返回错误码,CP就可以根据错误码做相关的界面UI提示,这里就可以偷懒尽量避免做多语言和多UI处理啦。
public interface CallBackListener<T> {
/**
* 成功回调
* @param t 详细信息
*/
void onSuccess(T t);
/**
* 失败回调
*
* @param code 错误码
* @param msg 错误详细描述信息
*/
void onFailure(int code, String msg);
}
项目数据设计
一般项目都会涉及到数据的交互与数据存储,数据存储一般常用的数据存储方式SharedPreferences存储、文件存储、SQLite数据库存储、ContentProvider存储、网络存储等混合使用。相关的存储方式及使用这里就不介绍了。由于游戏SDK涉及到的数据量不大,主要以内存存储和SharedPreferences存储和文件存储为主。
PS:如果数据设计到多进程数据交互,建议使用ContentProvider存储方式。简单介绍下内存缓存的思路:
定义全局的集合,通过单例来管理相关的数据存储和读取接口。
public class BaseCache {
private Application mAppContext;
public Context getApplication() {
return mAppContext;
}
/********************* 同步锁双重检测机制实现单例模式(懒加载)********************/
private volatile static BaseCache sCache;
private BaseCache(Application appContext) {
mAppContext = appContext;
}
public static BaseCache getInstance() {
if (sCache == null) {
throw new RuntimeException("get(Context) never called");
}
return sCache;
}
public static BaseCache init(Application cxt) {
if (sCache == null) {
synchronized (BaseCache.class) {
if (sCache == null) {
sCache = new BaseCache(cxt);
}
}
}
return sCache;
}
/********************* 同步锁双重检测机制实现单例模式(懒加载)********************/
/**
* hashMap是线程不安全的,做全局缓存时,用锁来保证存储值
*/
private HashMap<String, Object> mConfigs = new HashMap<>();
private ReentrantLock mLock = new ReentrantLock();
public void put(String key, Object value){
mLock.lock();
mConfigs.put(key,value);
mLock.unlock();
}
public Object get(String key){
mLock.lock();
Object object = mConfigs.get(key);
mLock.unlock();
return object;
}
项目域名设计
因为SDK涉及到多个项目,每个项目肯定是会有不同的域名的,并且同一个项目也存在测试环境、沙盒环境、正式环境的域名区分,域名设计的主要的作用是统一管理域名及方便后续不同的项目来回切换不同的域名地址,这是比较重要的。
简单说下域名的设计思路:
一般修改域名不涉及到代码的修改,对外的包如何动态的修改域名设置呢?
1、通过后台配置预设域名,但是后台的设置会影响到线上环境。慎重。
2、通过包体的配置文件来读取域名,开发人员可以通过反编译包体修改域名调试代码。
PS:开发人员根据需求设计,但是切记要统一管理,方便维护。
public class UrlConfig {
private static final String TAG = "UrlConfig";
private static String Project_SDKUrl; //url
//项目的基础ip域名地址
private static String Project_BaseApi = "https://www.baidu.com/";
public static String getSdkUrl(){
return Project_SDKUrl;
}
public static void initUrl(){
//通过配置读取域名
String SDK_Base_Url = BaseCache.getInstance().getSdkUrl();
if (!TextUtils.isEmpty(SDK_Base_Url)){//通过配置文件来修改域名
Project_BaseApi = SDK_Base_Url;
}
LogUtils.debug_d(TAG,Project_SDKUrl);
}
/**
* 预设设置修改当前网络的请求域名接口
* 思考特殊的场景:
* 多个项目交叉调用时,域名地址不一样,可通过修改临时域名做请求。
*/
public static String getReSetUrl(String sdk_type, String channelId){
String tempUrl = "";
return tempUrl; //返回临时的域名
}
}
项目混淆设计
SDK是对外提供功能的,而且从安全性考虑的话,对外提供的包体都应该是经常各种混淆和加密处理的。不然被攻击的可能性就很高了。
项目的设计是定义顶层的类或接口,具体的实现类继承该类或者实现该接口就不混淆了。关于相关的混淆规则及配置可参考: Android混淆打包那些事儿
/**
* 定义基础混淆接口
*/
public interface ProguardInterface {
}
/**
* 定义基础混淆对象
*/
public class ProguardObject {
}
项目三方库的封装设计
在实际开发中,会使用到大量的三方库,避免重复造轮子,特别是网络库、图片加载图等。但是,开源库过几年就可能会有更好的开源库,或者原有的开源库有局限性满足不了现有的项目需求,需要拓展或者替换的新的框架。所以需要做封装处理,避免后续替换改动太大。可参考:
对于有多种可替代解决方案的业务逻辑,提供一种快速替换方法PS:项目中封装的是Volley,根据实际项目做了简单网络库的封装,不做任何商业模块使用,慎用!
public class RequestExecutor {
public static final String GET = "GET";
public static final String POST = "POST";
private IRequestManager iRequestManager;
private String method;
private String url;
private String header;
private String userAgent;
private Map<String,Object> params;
private RequestCallback requestCallback;
private RequestExecutor(RequestExecutor.Builder builder){
this.method = builder.method;
this.url = builder.url;
this.header = builder.header;
this.userAgent = builder.userAgent;
this.params = builder.params;
this.requestCallback = builder.requestCallback;
}
public void startRequest(){
/**
* 网络框架要替换成别的时候,实现具体封装就OK了,并修改具体实现
* 比如换成okhttp写法 :return new OkHttpRequestManager();
*/
iRequestManager = new VolleyRequestManager();
iRequestManager.setHeader(header);
iRequestManager.setUserAgent(userAgent);
if (RequestExecutor.GET.equals(method)){
iRequestManager.get(url,params,requestCallback);
}else if (RequestExecutor.POST.equals(method)){
iRequestManager.post(url,params,requestCallback);
}
}
/**
* 取消当前的网络请求,
*/
public void cancel(){
iRequestManager.cancel();
}
public static class Builder{
private String method;
private String url;
private String header;
private String userAgent;
private Map<String,Object> params;
private RequestCallback requestCallback;
public RequestExecutor build(){
return new RequestExecutor(this);
}
public RequestExecutor.Builder setMethod(String method){
this.method = method;
return this;
}
public RequestExecutor.Builder setUrl(String url){
this.url = url;
return this;
}
public RequestExecutor.Builder setHeader(String header){
this.header = header;
return this;
}
public RequestExecutor.Builder setUserAgent(String userAgent){
this.userAgent = userAgent;
return this;
}
public RequestExecutor.Builder setParams(HashMap<String,Object> params){
this.params = params;
return this;
}
public RequestExecutor.Builder setRequestCallback(RequestCallback requestCallback){
this.requestCallback = requestCallback;
return this;
}
}
}
第二部分:项目需求开发
基础库搭建好了之后就是根据项目需求进行实际功能开发了,因为不同的项目有不同的项目需求,我简单的以项目初始化、账号登录/切换账号/账号登出、支付三大功能点来进行框架的代码实现。
1、需求开发 - Manager控制模块
Manager模块是SDK的核心模块,主要负责业务的功能实现及逻辑控制。根据项目需求,暂定为初始化Manager、账号Manager、支付Manager。
初始化Manager:处理SDK的初始化逻辑,全局参数缓存、环境切换、权限问题等。
public class InitManager {
private final String TAG = getClass().getSimpleName();
private volatile static InitManager INSTANCE;
private InitManager() {
}
public static InitManager getInstance() {
if (INSTANCE == null) {
synchronized (InitManager.class) {
if (INSTANCE == null) {
INSTANCE = new InitManager();
}
}
}
return INSTANCE;
}
/**
* 加载SDK项目配置入口插件(这是项目最开始加载的)
* @param context 上下文
* @param isdebug 日志调试开关
*/
public void initApplication(Application cxt, Context context, boolean isdebug){
ApplicationCache.init(cxt);
LogUtils.setDebugLogModel(isdebug);
ProjectManager.init(context).loadAllProjects();
//聚合SDK加载渠道插件
ChannelManager.init(context).loadChannel();
}
private static Handler sApiHandler;
private static boolean initState = false;
/**
* SDK初始化逻辑
* @param activity
* @param callBackListener
*/
public void init(final Activity activity, final String gameid, final String gamekey, final CallBackListener callBackListener) {
if (sApiHandler == null) {
HandlerThread ht = new HandlerThread("project_sdk_thread",
Process.THREAD_PRIORITY_BACKGROUND);
ht.start();
sApiHandler = new Handler(ht.getLooper());
}
Runnable r = new Runnable() {
@Override
public void run() {
//1、初始化全局缓存变量
BaseCache.init(activity.getApplication());
BaseCache.getInstance().put(KeyConfig.GAME_ID,gameid);
BaseCache.getInstance().put(KeyConfig.GAME_KEY,gamekey);
//2、初始化SDK参数
SDKInfoCache.getDefault(activity.getApplication());
//3、初始化持久化数据
SharePreferencesCache spCache = new SharePreferencesCache(activity);
spCache.init();
//4、加载功能插件
PluginManager.init(activity).loadAllPlugins();
//5、初始化域名配置
UrlConfig.initUrl();
//6、开始初始化逻辑
startInitLogic(activity,callBackListener);
}
};
sApiHandler.post(r);
}
/**
* 真正的初始化逻辑
*/
private void startInitLogic(final Activity activity, final CallBackListener callBackListener){
//-----------------------------已初始化完成--------------------------------
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
setInitState(true);
callBackListener.onSuccess(null);
}
});
}
/**
* 初始化功能插件()
*/
private void initFunctionPlugin(Activity activity){
//腾讯bugly日志收集
}
public void setInitState(boolean state) {
initState = state;
//将当前状态存储到全局变量供其他模块插件使用
BaseCache.getInstance().put(KeyConfig.IS_INIT,initState);
}
public boolean getInitState() {
return initState;
}
}
账号Manager:管理账号的各个功能接口:登录、切换账号、注销账号、绑定账号。登录可细分为设备登陆、游客登录、账号登陆、三方登陆(google/facebook/微信)等登录逻辑和切换、绑定逻辑。
public class AccountManager {
public static final String TAG = "AccountManager";
private volatile static AccountManager INSTANCE;
private AccountManager() {
}
public static AccountManager getInstance() {
if (INSTANCE == null) {
synchronized (AccountManager.class) {
if (INSTANCE == null) {
INSTANCE = new AccountManager();
}
}
}
return INSTANCE;
}
private Activity mActivity;
private AccountBean mLoginInfo; //当前登陆的登陆信息
private boolean isSwitchAccount = false; //通过标记位来判断是否是切换账号按钮的登录回调
/****************************************** 获取Project账号监听 ****************************************/
private CallBackListener projectLoginCallBackListener;
public void setLoginCallBackLister(CallBackListener callBackLister){
projectLoginCallBackListener = callBackLister;
}
private void CallBackToProject(int event, int code, AccountBean accountBean, String msg){
//设置回调信息
AccountCallBackBean accountCallBackBean = new AccountCallBackBean();
accountCallBackBean.setEvent(event); //事件类型ID
accountCallBackBean.setErrorCode(code); //事件码
accountCallBackBean.setAccountBean(accountBean); //事件的账号信息
accountCallBackBean.setMsg(msg); //设置事件的信息
if (projectLoginCallBackListener != null){
projectLoginCallBackListener.onSuccess(accountCallBackBean);//回调给Project的信息
}
}
/**
* 登录结果监听
*/
private CallBackListener LoginCallBackLister = new CallBackListener<AccountBean>(){
@Override
public void onSuccess(AccountBean loginInfo) {
LogUtils.d(TAG, "loginInfo:" + loginInfo.toString());
mLoginInfo = loginInfo;
//登陆成功,设置登录信息
setLoginSuccess(loginInfo);
if (isSwitchAccount){
CallBackToProject(TypeConfig.SWITCHACCOUNT, ErrCode.SUCCESS,loginInfo, "user switchAccount success");
isSwitchAccount = false; //置为false
}else {
CallBackToProject(TypeConfig.LOGIN,ErrCode.SUCCESS,loginInfo, "user login success");
}
}
@Override
public void onFailure(int code, String msg) {
mLoginInfo = null; //当前登陆失败就置为null
if (isSwitchAccount){
if (code == ErrCode.CANCEL){ //如果切换账号时,不走登录,给登出回调
CallBackToProject(TypeConfig.LOGOUT, ErrCode.SUCCESS, null, "user logout success");
}else {
CallBackToProject(TypeConfig.SWITCHACCOUNT, code, null, msg);
}
}else {
CallBackToProject(TypeConfig.LOGIN, code, null, msg);
}
}
};
/****************************************** 登录 ****************************************/
/**
* 显示登录界面
*/
public void showLoginView(final Activity activity, HashMap<String,Object> loginMap){
mActivity = activity;
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage("是否登录?");
builder.setTitle("登录界面");
builder.setPositiveButton("登录",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
AccountBean loginInfo = new AccountBean();
loginInfo.setLoginState(true); //将登录成功状态返回
loginInfo.setUserToken("dasfkaf-SAFA-kfad");
loginInfo.setUserID("userID-123");
loginInfo.setUserName("测试用户"); //聚合将用名设置为UserID
LoginCallBackLister.onSuccess(loginInfo);
}
});
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
LoginCallBackLister.onFailure(ErrCode.FAILURE,"login fail");
}
});
builder.create().show();
}
/**
* 授权登录,具体项目具体实现逻辑
*/
public void authLogin(Activity activity, HashMap<String,Object> loginMap){
mActivity = activity;
AccountBean loginInfo = new AccountBean();
loginInfo.setLoginState(true); //将登录成功状态返回
loginInfo.setUserToken("dasfkaf-SAFA-kfad");
loginInfo.setUserID("userID-123");
loginInfo.setUserName("测试用户"); //聚合将用名设置为UserID
LoginCallBackLister.onSuccess(loginInfo);
}
/**
* 获取当前登陆状态,默认false
* @return
*/
public boolean getLoginState() {
if (mLoginInfo != null){
return mLoginInfo.getLoginState();
}
return false;
}
/****************************************** 切换账号 ****************************************/
/**
* 切换账号
* @param activity
*/
public void switchAccount(Activity activity){
mActivity = activity;
//先走登出逻辑
mLoginInfo = null; //登录信息清空
isSwitchAccount = true;
clearLoginInfo(activity);
}
/****************************************** 登出 ****************************************/
/**
* 账号登出
*/
public void logout(Activity activity){
mActivity = activity;
mLoginInfo = null; //登录信息清空
isSwitchAccount = false;
clearLoginInfo(activity);
CallBackToProject(TypeConfig.LOGOUT, ErrCode.SUCCESS, null, "user logout success");
}
/**
* 设置登录成功行为
*/
private void setLoginSuccess(AccountBean loginInfo){
if (loginInfo != null){
BaseCache.getInstance().put(KeyConfig.PLAYER_ID,loginInfo.getUserID());
BaseCache.getInstance().put(KeyConfig.PLAYER_NAME,loginInfo.getUserName());
BaseCache.getInstance().put(KeyConfig.PLAYER_TOKEN,loginInfo.getUserToken());
//将当前状态存储到全局变量供其他模块插件使用
BaseCache.getInstance().put(KeyConfig.IS_LOGIN, getLoginState());
}
}
/**
* 清空登陆信息
*/
private void clearLoginInfo(Activity activity){
mLoginInfo = null;
//清空内存的用户信息
BaseCache.getInstance().put(KeyConfig.PLAYER_ID,"");
BaseCache.getInstance().put(KeyConfig.PLAYER_NAME,"");
BaseCache.getInstance().put(KeyConfig.PLAYER_TOKEN,"");
//将当前状态存储到全局变量供其他模块插件使用
BaseCache.getInstance().put(KeyConfig.IS_LOGIN, getLoginState());
}
}
支付Manager:购买管理类,管理SDK的各个购买功能接口:创建订单、三方支付、运营商支付、渠道支付、补单逻辑、包月、订阅等。注意可能还会有各个复杂的支付逻辑: 可能会先短代支付、然后渠道支付、三方支付,还有后台切换支付开关等。
public class PurchaseManager {
public static final String TAG = "PurchaseManager";
private volatile static PurchaseManager INSTANCE;
private PurchaseManager() {
}
public static PurchaseManager getInstance() {
if (INSTANCE == null) {
synchronized (PurchaseManager.class) {
if (INSTANCE == null) {
INSTANCE = new PurchaseManager();
}
}
}
return INSTANCE;
}
/**
* 创建订单,具体项目具体实现
*/
public void createOrderId(Activity activity, HashMap<String, Object> payParams , final CallBackListener callBackListener){
LogUtils.debug_d(TAG,"payParams = " + payParams.toString());
String orderID = "DD1441";
callBackListener.onSuccess(orderID);
}
/**
* 显示支付界面
*/
public void showPayView(Activity activity, HashMap<String, Object> payParams, final CallBackListener callBackListener){
LogUtils.debug_d(TAG,"payParams = " + payParams.toString());
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
String message = "充值金额:" + "2"
+ "\n商品名称:" + "大饼"
+ "\n商品数量:" + "1"
+ "\n资费说明:" + "2元";
builder.setMessage(message);
builder.setTitle("请确认充值信息");
builder.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int index) {
//支付结果回调到这里来
PurchaseResult purchaseResult = new PurchaseResult(PurchaseResult.PurchaseState,null);
callBackListener.onSuccess(purchaseResult);
}
});
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
callBackListener.onFailure(ErrCode.FAILURE,"pay fail");
}
});
builder.create().show();
}
}
2、需求开发 - Plugin三方功能插件层
Plugin三方功能插件层分两部分组成:反射API和具体Plugin插件。反射API是动态插拔功能插件的关键,可以打包时动态打对应的供插件。Plugin插件是具体实现和封装对应的功能层。额外拓展新的功能插件时,继承Plugin类即可。下面以微信插件为例:
微信功能Plugin:封装和实现微信的登录、支付、分享等常见功能
public class WechatPlugin extends Plugin {
private String TAG = "WechatPlugin";
@Override
protected synchronized void initPlugin() {
super.initPlugin();
LogUtils.d(TAG,"init " + getClass().getSimpleName());
}
/**
* 调用微信支付接口
*/
public void wechatPay(Context context, Map<String,Object> payMap, CallBackListener callBackListener){
WechatPay.getInstance().pay(context,payMap,callBackListener);
}
/**
* 调用微信登录接口
*/
public void wechatLogin(Context context, Map<String,Object> LoginMap, CallBackListener callBackListener){
}
/**
* 调用微信分享接口
*/
public void wechatShare(Context context, Map<String,Object> ShareMap, CallBackListener callBackListener){
}
/**
* 根据当前的生命周期
* @param context
*/
@Override
public void onResume(Context context) {
WechatPay.getInstance().onResume(context);
}
}
微信功能PluginAPI:对接微信Plugin接口。
public class WechatPluginApi extends PluginReflectApi {
private String TAG = "WechatPluginApi";
private Plugin wechatPlugin;
private volatile static WechatPluginApi INSTANCE;
private WechatPluginApi() {
wechatPlugin = PluginManager.getInstance().getPlugin("plugin_wechat");
}
public static WechatPluginApi getInstance() {
if (INSTANCE == null) {
synchronized (WechatPluginApi.class) {
if (INSTANCE == null) {
INSTANCE = new WechatPluginApi();
}
}
}
return INSTANCE;
}
/**
* 调用微信app支付
*/
public void pay(Context context, Map<String,Object> map, CallBackListener callBackListener){
if (wechatPlugin != null){
invoke(wechatPlugin,"wechatPay",new Class<?>[]{Context.class, Map.class, CallBackListener.class},
new Object[]{context, map, callBackListener});
}
}
}
3、需求开发 - Project项目业务层
Project层主要分自定义SDK项目和聚合SDK项目两大类,自定义SDK是自己实现SDK的功能逻辑;聚合SDK主要是封装渠道SDK用于游戏的联运。业务需求是不一样的。相对而已聚合SDK会简单点。如有新的项目需求,可继承Project对应实现就OK了。主要自定义SDK为例讲解下
public class CustomProject extends Project{
private final String TAG = getClass().getSimpleName();
/**
* 项目实例化入口
*/
@Override
protected synchronized void initProject() {
LogUtils.d(TAG, getClass().getSimpleName() + " has init");
super.initProject();
}
/****************************************** 初始化 ****************************************/
@Override
public void init(Activity activity, String gameid, String gamekey, final CallBackListener callBackListener) {
LogUtils.d(TAG,"init");
if (activity == null || callBackListener == null) {
callBackListener.onFailure(ErrCode.PARAMS_ERROR,"activity or callBackListener is null");
return;
}
//设置账号监听
AccountManager.getInstance().setLoginCallBackLister(projectAccountCallBackListener);
InitManager.getInstance().init(activity, gameid, gamekey, new CallBackListener() {
@Override
public void onSuccess(Object object) {
callBackListener.onSuccess(null);
}
@Override
public void onFailure(int code, String msg) {
callBackListener.onFailure(code,msg);
}
});
}
/****************************************** 账号 ****************************************/
/*** SDKApi层设置回调监听 */
private CallBackListener ApiAccountCallback;
@Override
public void setAccountCallBackLister(CallBackListener callBackLister) {
ApiAccountCallback = callBackLister;
}
/**
* 监听AccountManager登录、切换账号、绑定、注销的回调信息
*/
private CallBackListener projectAccountCallBackListener = new CallBackListener<AccountCallBackBean>() {
@Override
public void onSuccess(AccountCallBackBean callBackBean) {
ApiAccountCallback.onSuccess(callBackBean);
}
@Override
public void onFailure(int code, String msg) {
//不会走到这里来
}
};
private void AccountOnFailCallBack(int event, int code, String msg){
AccountCallBackBean callBackBean = new AccountCallBackBean();
callBackBean.setEvent(event);
callBackBean.setErrorCode(code);
callBackBean.setMsg(msg);
ApiAccountCallback.onSuccess(callBackBean);
}
@Override
public void login(Activity activity, HashMap<String, Object> loginParams) {
LogUtils.d(TAG,"login");
if (!InitManager.getInstance().getInitState()){
Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
return;
}
if (activity == null ) {
AccountOnFailCallBack(TypeConfig.LOGIN,ErrCode.PARAMS_ERROR,"activity is null");
return;
}
AccountManager.getInstance().showLoginView(activity,loginParams);
}
@Override
public void switchAccount(Activity activity) {
LogUtils.d(TAG,"switchAccount");
if (!InitManager.getInstance().getInitState()){
Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
return;
}
if (!AccountManager.getInstance().getLoginState()){
AccountOnFailCallBack(TypeConfig.SWITCHACCOUNT,ErrCode.NO_LOGIN,"account has not login");
return;
}
if (activity == null ) {
AccountOnFailCallBack(TypeConfig.LOGIN,ErrCode.PARAMS_ERROR,"activity is null");
return;
}
AccountManager.getInstance().switchAccount(activity);
}
@Override
public void logout(Activity activity) {
LogUtils.d(TAG,"logout");
if (!InitManager.getInstance().getInitState()){
Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
return;
}
if (!AccountManager.getInstance().getLoginState()){
AccountOnFailCallBack(TypeConfig.LOGOUT,ErrCode.NO_LOGIN,"account has not login");
return;
}
if (activity == null ) {
AccountOnFailCallBack(TypeConfig.LOGIN,ErrCode.PARAMS_ERROR,"activity is null");
return;
}
AccountManager.getInstance().logout(activity);
}
/****************************************** 购买 ****************************************/
@Override
public void pay(Activity activity, HashMap<String, Object> payParams, CallBackListener callBackListener) {
LogUtils.d(TAG,"pay");
if (!InitManager.getInstance().getInitState()){
Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
return;
}
if (!AccountManager.getInstance().getLoginState()){
callBackListener.onFailure(ErrCode.NO_LOGIN,"account has not login");
return;
}
if (activity == null || payParams == null || callBackListener == null) {
callBackListener.onFailure(ErrCode.PARAMS_ERROR,"activity or PayParams or callBackListener is null");
return;
}
PurchaseManager.getInstance().showPayView(activity,payParams,callBackListener);
}
/****************************************** 退出 ****************************************/
/**
* 退出SDK
*/
@Override
public void exit(Activity activity, CallBackListener callBackListener) {
LogUtils.d(TAG,"exit");
if (activity == null || callBackListener == null) {
callBackListener.onFailure(ErrCode.PARAMS_ERROR,"activity or callBackListener is null");
return;
}
callBackListener.onFailure(ErrCode.NO_EXIT_DIALOG,"channel not exitDialog");
}
/************************************* 生命周期接口(必接) ****************************************/
@Override
public void onCreate(Activity activity, Bundle savedInstanceState) {
LogUtils.d(TAG,"onCreate");
if (InitManager.getInstance().getInitState()){
super.onCreate(activity, savedInstanceState);
}
}
@Override
public void onStart(Activity activity) {
LogUtils.d(TAG,"onStart");
if (InitManager.getInstance().getInitState()){
super.onStart(activity);
}
}
@Override
public void onResume(Activity activity) {
LogUtils.d(TAG,"onResume");
if (InitManager.getInstance().getInitState()){
super.onResume(activity);
}
}
@Override
public void onPause(Activity activity) {
LogUtils.d(TAG,"onPause");
if (InitManager.getInstance().getInitState()){
super.onPause(activity);
}
}
@Override
public void onStop(Activity activity) {
LogUtils.d(TAG,"onStop");
if (InitManager.getInstance().getInitState()){
super.onStop(activity);
}
}
@Override
public void onDestroy(Activity activity) {
LogUtils.d(TAG,"onDestroy");
if (InitManager.getInstance().getInitState()){
super.onDestroy(activity);
}
}
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
LogUtils.d(TAG,"onActivityResult");
if (InitManager.getInstance().getInitState()){
super.onActivityResult(activity, requestCode, resultCode, data);
}
}
@Override
public void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions,int[] grantResults) {
LogUtils.d(TAG,"onRequestPermissionsResult");
if (InitManager.getInstance().getInitState()){
super.onRequestPermissionsResult(activity, requestCode, permissions, grantResults);
}
}
}
4、需求开发 - 项目Channel业务层
主要是面向聚合SDK项目,主要封装渠道的SDK内容。如需封装新的渠道SDK,继承Channel类即可。
public class TestChannelSDK extends Channel {
private final String TAG = getClass().getSimpleName();
@Override
protected void initChannel() {
LogUtils.d(TAG, getClass().getSimpleName() + " has init");
}
@Override
public String getChannelID() {
return "1";
}
@Override
public boolean isSupport(int FuncType) {
switch (FuncType){
case TypeConfig.FUNC_SWITCHACCOUNT:
return true;
case TypeConfig.FUNC_LOGOUT:
return true;
case TypeConfig.FUNC_SHOW_FLOATWINDOW:
return true;
case TypeConfig.FUNC_DISMISS_FLOATWINDOW:
return true;
default:
return false;
}
}
@Override
public void init(Context context, HashMap<String, Object> initMap, CallBackListener initCallBackListener) {
LogUtils.d(TAG,getClass().getSimpleName() + " init");
initOnSuccess(initCallBackListener);
}
@Override
public void login(Context context, HashMap<String, Object> loginMap, CallBackListener loginCallBackListener) {
LogUtils.d(TAG,getClass().getSimpleName() + " login");
showLoginView(context,loginCallBackListener);
}
@Override
public void switchAccount(final Context context, final CallBackListener changeAccountCallBackLister) {
LogUtils.d(TAG,getClass().getSimpleName() + " switchAccount");
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("是否切换账号?");
builder.setTitle("切换账号");
builder.setPositiveButton("切换账号",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
showLoginView(context,changeAccountCallBackLister);
}
});
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
switchAccountOnCancel("channel switchAccount cancel",changeAccountCallBackLister);
}
});
builder.create().show();
}
@Override
public void logout(Context context, final CallBackListener logoutCallBackLister) {
LogUtils.d(TAG,getClass().getSimpleName() + " logout");
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("是否注销账号?");
builder.setTitle("注销账号");
builder.setPositiveButton("成功",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
logoutOnSuccess(logoutCallBackLister);
}
});
builder.setNegativeButton("失败",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
logoutOnFail("channel logout fail",logoutCallBackLister);
}
});
builder.create().show();
}
@Override
public void pay(Context context, HashMap<String, Object> payMap, final CallBackListener payCallBackListener) {
LogUtils.d(TAG,getClass().getSimpleName() + " pay");
String orderID = (String) payMap.get("orderId");
String productName = (String) payMap.get("productName");
String productDesc = (String) payMap.get("productDesc");
String money = String.valueOf(payMap.get("money"));
String productID = String.valueOf(payMap.get("productID"));
LogUtils.d(TAG,productID);
final HashMap<String,Object> paymap = new HashMap<>();
paymap.put("orderID",orderID);
paymap.put("productName",productName);
paymap.put("money",money);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
String message = "充值金额:" + money
+ "\n商品名称:" + productName
+ "\n商品数量:" + "1"
+ "\n资费说明:" + productDesc;
builder.setMessage(message);
builder.setTitle("请确认充值信息");
builder.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, int index) {
payOnSuccess(payCallBackListener);
}
});
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
OnCancel(payCallBackListener);
}
});
builder.create().show();
}
private void showLoginView(final Context context, final CallBackListener loginCallBackListener){
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("是否登录?");
builder.setTitle("登录界面");
builder.setPositiveButton("登录",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
JSONObject json = new JSONObject();
try {
json.put("sid", "testID");
} catch (JSONException e) {
e.printStackTrace();
}
loginOnSuccess(json.toString(),loginCallBackListener);
}
});
builder.setNegativeButton("取消",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int index) {
loginOnFail("channel login fail",loginCallBackListener);
}
});
builder.create().show();
}
@Override
public void exit(Context context, CallBackListener exitCallBackLister) {
LogUtils.d(TAG,getClass().getSimpleName() + " exit");
channelNotExitDialog(exitCallBackLister);
}
}
项目代码总结
----------------------------------------------------------------------------------------------
GameSDK_API层:
SDK对外接口层:是暴露给CP的接口,底层返回的数据格式在这一层转化,该层不参与混淆。
所以不要在该层做业务逻辑处理,避免被反射调用修改。
后续有新的接口,可以在该层做拓展对外给CP。
----------------------------------------------------------------------------------------------
GameSDK_BeginProject层:
SDK项目层:是与Api层对接的项目入口层,可以通过修改配置文件 Project_config.txt
进入和替换成不同的Project SDK项目。
注意:
顶层Project已经确定好后,不要修改太多,如果有拓展的功能接口,
可以通过extendFunction()对应type拓展,避免改动太多底层代码。
SDK大体分为两类:
1、Project_JuHe为聚合SDK项目,主要业务用于封装三方渠道。
2、Project_Custom为自定义SDK项目,主要是做自己的渠道,每个公司可能会有不同的名称,
主要业务是,实现用户入口、支付逻辑、数据统计等功能。
可能根据不同的游戏运营需求会有不同的登录逻辑、绑定逻辑、支付方式、数据上报等
会细分为公版项目、海外项目(针对地区)、根据游戏的定制化项目等。可根据不同的项目,拓展Project
该层是做业务逻辑处理的,希望能有清晰的架构思路。
----------------------------------------------------------------------------------------------
SYSDK_Channel: SDK项目渠道层
该层负责对接渠道接入的业务,不同的渠道都会有对应的Module实现具体的代码调用和逻辑处理,以及资源配置
通过配置文件 Channel_config.txt 管理。
而且每个渠道Module都会有对应的开发者说明文件,方便后续的开发同事维护更新。详见开发者说明。
开发者格式说明:
格式: 渠道名 -- 版本号 -- 开发人员 (如果渠道没有版本就按V1.0.0来) 日期
相关注意事项说明:
1、.... xxx ....
2、.... xxx ....
----------------------------------------------------------------------------------------------
SYSDK_Manager:
SDK逻辑管理层:
该层为整个SDK功能逻辑的实现:初始化、账号、支付、获取道具信息、补单逻辑、退出
为避免逻辑层因业务太乱导致代码过多,及后续的功能模块抽离.
采用模块化化思想进行模块管理.
初始化:SDK初始化(全局参数缓存、环境切换、权限问题等) >- 项目初始化(默认初始化 和 项目初始化)
登陆:设备登陆、游客登录、账号登陆、三方登陆(google/facebook/微信)
支付:三方支付:google、支付宝、微信、及特有的项目支付、补单
退出:
当有额外的功能添加的时候,在该层实现即可。
----------------------------------------------------------------------------------------------
GameSDK_Manager_Impl:
SDK逻辑功能拓展反射层,通过反射解耦插件:
该层更多的是针对后续Plugin插件做不同的功能反射,具体调用Plugin层插件。
只负责对接GameSDK_Manager_Impl 和 Plugin插件层。
----------------------------------------------------------------------------------------------
GameSDK_Plugin:
SDK功能插件层,与渠道层有点类似,通过配置文件 Plugin_config.txt 管理。
通过逻辑控制层 Manager_Logic_Impl 反射调用实现,把插件拔除不影响。
可以是三方的功能插件,也可以是自己实现的插件。
目前为说明功能,用支付宝和微信说明
Alipay功能插件层:实现Alipay功能,封装Alipay相关接口
Wechat功能插件层:实现Wechat功能,封装Wechat相关接口
当有额外的功能添加的时候,在该层实现即可。
----------------------------------------------------------------------------------------------
GameSDK_Utils:
GameSDK_Utils层:该层为整个SDK功能基础库:目前分为业务基础库 和 功能基础库,方便后续将业务分离。
基础组件:
1、数据缓存、域名配置、项目/插件/渠道管理
注意:将项目、渠道、插件分别加载的目的:
是为了快速替换项目Project的入口类
一个项目Project 可以对应多个渠道、多个插件。后续可以在多渠道、多插件上
进行快速的插拔和后台开关的切换渠道。
不过正常的需求都是一个项目,对应零个或一个渠道、一个或多个功能插件
2、网络请求、日志输出、Gson解析
注意:网络请求,目前封装的volly (volly是比较轻量级的,主要是为减少SDK包体)
日志输出,目前封装的logger(logger比较轻量级,主要用于开发日志信息输出)
第三方库快速替换方案思路:(方便后续维护替换成更好的库)
接口解耦封装,实现上层业务网络请求接口不改动,只做封装层的api调用即可
基础库不要轻易修改! 不要轻易修改! 不要轻易修改! 不要轻易修改! 不要轻易修改!
---------------------------------------------------------------------------------------
结语:
关于手游SDK的客户端的实现就大体介绍到这里。Demo的地址: 手游SDK框架Demo。
声明下,该Demo只是框架的讲解,不具备任何商业价值。如涉及到纠纷,可不能赖我呀。