SDK接入(U8SDK)——SDK抽象层的设计
上一篇文章,我们总体地分析并设计了一套高效的SDK接入方案,也罗列出这套方案,我们需要完成的工作。这里再罗列并回顾下:
1、统一抽象的SDK接入框架
2、各个SDK接入实现
3、一键打包工具
4、统一的登陆认证中心和支付中心
5、对多个平台的支持,比如Unity3D,Cocos2D等
那么接下来这篇文章,我们就开始第一部分:抽象的SDK接入框架的实现。在实现之前,我们再深入地想一下,抽象层需要提供哪些接口。因为,对于每个游戏来说,都只需要接入这个抽象层,而所有SDK的接入就是实现这个抽象层。所以,这个抽象层设计的好坏,不仅影响到游戏的接入,同时也影响到各个渠道SDK的实现。
没有好的思路,我们可以看下AnySDK,或者棱镜SDK他们的宣传资料和文档,我们发现他们支持的组件有这些:
渠道SDK就不用说了,除了渠道SDK,他把部分支付SDK,广告SDK,分享SDK,统计SDK,消息推送SDK等都放到了这套统一SDK接入框架中来了。那么,作为我们这套抽象框架,我们也需要考虑以后可能会加入这些其他非渠道的SDK。所以,我们总体的设计思想是:
1、游戏各个渠道有一个主渠道SDK,比如UC,当乐,91等SDK。这个各个渠道只能同时有一个。不可能同时为UC也是91SDK
2、非渠道的功能性SDK,包括广告,分享,统计,推送等。这些东西,我们作为插件集成到这套抽象框架来。
3、所有SDK的实现可以很方便,而且结构比较统一
4、所有的渠道SDK也好,还是功能性SDK也好,SDK抽象层都抽象出对应的接口。方便游戏层的调用,也方便具体插件的实现。
那么,接下来,我们就根据前一篇我们画的那个登陆和支付流程图,和上面提到的总体设计思路来实现这个抽象层。上篇文章说道,我们这套东西暂且命名为u8 sdk,那么我们这个抽象层就叫U8 SDK。为了可以将各个功能作为插件式开发,我们抽象接口的时候,也将各个功能分开。
首先,我们定义两个接口,一个是登陆接口,一个是支付接口:
1
2
3
4
5
|
public interface IUser { public void login(); } |
1
2
3
4
|
public interface IPay { public void pay(PayParams data); } |
这两 个接口定义好了,我们简单说下。一般SDK的登陆成功之后,都会拿到sid对吧,那这个登陆login方法并没有返回值,而是后面,我们会定义一个总的
SDK监听器,通过监听器来实现。支付需要游戏层传入一些支付参数。但是,有童鞋可能要问了,各个渠道需要的支付参数好像不太一样吧?是的亲,但是对于游
戏来说,我能提供的支付数据也就那么些。我全传给你,各个渠道sdk各自按需索取就可以了。那么,我们简单看下,我们这个PayParams类,有哪些属 性呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
public class PayParams{ private String productId; private String productName; private int price; private int buyNum; private int coinNum; private String serverId; private String roleId; private String roleName; private int roleLevel; private String extension; public String getProductId() { return productId; } public void setProductId(String productId) { this .productId = productId; } public String getProductName() { return productName; } public void setProductName(String productName) { this .productName = productName; } public int getPrice() { return price; } public void setPrice( int price) { this .price = price; } public int getBuyNum() { return buyNum; } public void setBuyNum( int buyNum) { this .buyNum = buyNum; } public int getCoinNum() { return coinNum; } public void setCoinNum( int coinNum) { this .coinNum = coinNum; } public String getServerId() { return serverId; } public void setServerId(String serverId) { this .serverId = serverId; } public String getRoleId() { return roleId; } public void setRoleId(String roleId) { this .roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this .roleName = roleName; } public int getRoleLevel() { return roleLevel; } public void setRoleLevel( int roleLevel) { this .roleLevel = roleLevel; } public String getExtension() { return extension; } public void setExtension(String extension) { this .extension = extension; } } |
大家要问这里参数怎么抽象出来的,那么我告诉你,不是我拍脑袋想出来的,是参考AnySDK文档中提供的充值参数信息来的。(小贱一把)
两个接口有了,紧接着,上层游戏需要登录和支付的地方,怎么调用呢?对于游戏来说,这个接口需要new一个哪个实现?是UC还是当乐还是91呢?
所以,我们对每个插件定义一个单例的包装类。简单地说,就是怎么方便,怎么搞。这样就是方便上层游戏层得调用。那么,我们实现两个包装类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import com.u8.sdk.IUser; import com.u8.sdk.ComponentFactory; import com.u8.sdk.U8SDK; /** * 用户插件 * */ public class U8User{ private static U8User instance; private IUser userComponent; private U8User(){ } public void init(){ this .userComponent = (IUser)ComponentFactory.getInstance().initComponent(U8SDK.TYPE_LOGIN); } public static U8User getInstance(){ if (instance == null ){ instance = new U8User(); } return instance; } public void login(){ if (userComponent== null ){ return ; } userComponent.login(); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import com.u8.sdk.IPay; import com.u8.sdk.PayParams; import com.u8.sdk.ComponentFactory; import com.u8.sdk.U8SDK; /*** * 支付插件 * */ public class U8Pay{ private static U8Pay instance; private IPay payComponent; private U8Pay(){ } public static U8Pay getInstance(){ if (instance == null ){ instance = new U8Pay(); } return instance; } public void init(){ this .payComponent = (IPay)ComponentFactory.getInstance().initComponent(U8SDK.TYPE_PAY); } public void pay(PayParams data){ if ( this .payComponent == null ){ return ; } this .payComponent.pay(data); } } |
关于这两个包装类,大家可以看到,有一个初始化init方法,然后就是所有的插件对应的方法,他把插件接口作为一个私有属性,在init方法里面,将对应的插件接口赋值了。然后在插件对应的方法里面,间接地调用插件对应的接口。那么,这里的关键就是ComponentFactory这个类。刚刚说了,我们这个后面可能有多个插件,所以,我们需要一个插件管理类。ComponentFactory就是我们的插件管理类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
import java.io.IOException; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import com.u8.sdk.utils.SDKTools; import android.annotation.SuppressLint; import android.app.Activity; import android.util.Log; import android.util.Xml; @SuppressLint ( "UseSparseArrays" ) public class ComponentFactory { private static ComponentFactory instance; private Map<Integer, String> supportedComponents; private ComponentFactory(){ supportedComponents = new HashMap<Integer, String>(); } public static ComponentFactory getInstance(){ if (instance == null ){ instance = new ComponentFactory(); } return instance; } public void init(Activity context){ loadComponentInfo(); } private boolean isSupportComponent( int type){ return supportedComponents.containsKey(type); } private String getComponentName( int type){ if (supportedComponents.containsKey(type)){ return supportedComponents.get(type); } return null ; } public SDKConfigData getSDKConfigData(){ Map<String, String> configs = SDKTools.getAssetPropConfig(U8SDK.getInstance().getContext(), "developer_config.properties" ); return new SDKConfigData(configs); } @SuppressWarnings ({ "unchecked" , "rawtypes" }) public Object initComponent( int type){ Class localClass = null ; try { if (!isSupportComponent(type)){ Log.e( "U8SDK" , "The config of the U8SDK is not support plugin type:" +type); return null ; } String name = getComponentName(type); localClass = Class.forName(name); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); return null ; } try { return localClass.getDeclaredConstructor( new Class[]{Activity. class }).newInstance( new Object[]{U8SDK.getInstance().getContext()}); } catch (Exception e) { e.printStackTrace(); } return null ; } private void loadComponentInfo(){ String xmlStr = SDKTools.getAssetConfigs(U8SDK.getInstance().getContext(), "plugin_config.xml" ); Log.e( "The plugin Str:" , xmlStr); XmlPullParser parser = Xml.newPullParser(); try { parser.setInput( new StringReader(xmlStr)); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT){ switch (eventType){ case XmlPullParser.START_TAG: String tag = parser.getName(); if ( "plugin" .equals(tag)){ String name = parser.getAttributeValue( 0 ); int type = Integer.parseInt(parser.getAttributeValue( 1 )); this .supportedComponents.put(type, name); Log.e( "u8_plugin" , "Curr Supported Plugin: " +type+ "; name:" +name); } } eventType = parser.next(); } } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } |
大家可以看到initComponent方法里面,就是判断当前插件是否支持,如果支持,则从supportedComponents 里 面根据当前插件类型取到对应插件实现类的完整类名。通过Class.forName().newInstance()进行初始化。那么,我们怎么知道支持 哪些插件呢?怎么得到当前支持的各个插件的实现类呢?大家也许已经看到了,这个管理类中,也有一个初始化init方法。在init方法中,调用了 loadComponentInfo方法来加载当前支持的插件信息。大家可以看到它是从assets目录下的plugin_config.xml配置来读 取的。关于这个plugin_config.xml怎么生成的,我们后面说打包工具的时候,会详细讲到。这个文件不是我们手动写的,而是打包工具在生成各 个渠道包的时候动态生成的。
为了在SDK抽象层和SDK实现层传递数据,我们定义一个监听器:
1
2
3
4
5
6
7
8
|
package com.u8.sdk; public interface IU8SDKListener { public void onResult( int code, String msg); public void onLoginResult(LoginResult result); } |
这个 监听器,我们定义了两个接口,一个是onResult,是SDK实现层传递的状态信息,比如SDK初始化成功,SDK初始化失败,SDK登陆成功,登陆失 败等信息。而onLoginResult()这个接口,就是之前说到的,登陆成功之后,SDK实现层需要调用该接口,将封装好的登陆结果,返回给SDK抽 象层。这个LoginResult里面,就包含sid信息。
为了能够将我们刚刚说的这些插件,插件包装类,插件管理类,和事件监听接口整合到一起,我们最后定义一个总的单例类,也是我们整个抽象层的核心纽带:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
package com.u8.sdk; import com.u8.sdk.components.U8Pay; import com.u8.sdk.components.U8User; import android.app.Activity; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.util.Log; public class U8SDK{ public static final int TYPE_LOGIN = 1 ; public static final int TYPE_PAY = 2 ; private static U8SDK instance; private Activity context; private Handler mainThreadHandler; private SDKConfigData developInfo; private IU8SDKListener listener; private IActivityListener activityCallback; private U8SDK(){ mainThreadHandler = new Handler(Looper.getMainLooper()); } public static U8SDK getInstance(){ if (instance == null ){ instance = new U8SDK(); } return instance; } public SDKConfigData getSDKParams(){ return developInfo; } public int getCurrChannel(){ if ( this .developInfo == null || ! this .developInfo.contains( "U8_Channel" )){ return 0 ; } return this .developInfo.getInt( "U8_Channel" ); } public void setSDKListener(IU8SDKListener listener){ this .listener = listener; } public void setActivityCallback(IActivityListener callback){ this .activityCallback = callback; } public void init(Activity context){ this .context = context; ComponentFactory.getInstance().init(context); developInfo = ComponentFactory.getInstance().getSDKConfigData(); U8User.getInstance().init(); U8Pay.getInstance().init(); } public void runOnMainThread(Runnable runnable){ if (mainThreadHandler != null ){ mainThreadHandler.post(runnable); return ; } if (context != null ){ context.runOnUiThread(runnable); } } public Activity getContext(){ return this .context; } public void onResult( int code, String msg){ Log.e( "U8SDK Action Result:" , "code:" +code+ ";msg:" +msg); if (listener != null ){ listener.onResult(code, msg); } } public void onLoginResult(LoginResult result){ if (listener != null ){ listener.onLoginResult(result); } } public void onActivityResult( int requestCode, int resultCode, Intent data) { if ( this .activityCallback != null ){ this .activityCallback.onActivityResult(requestCode, resultCode, data); } } public void onBackPressed(){ if ( this .activityCallback != null ){ this .activityCallback.onBackPressed(); } } public void onPause() { if ( this .activityCallback != null ){ this .activityCallback.onPause(); } } public void onResume() { if ( this .activityCallback != null ){ this .activityCallback.onResume(); } } public void onNewIntent(Intent newIntent) { if ( this .activityCallback != null ){ this .activityCallback.onNewIntent(newIntent); } } public void onStop() { if ( this .activityCallback != null ){ this .activityCallback.onStop(); } } public void onDestroy() { if ( this .activityCallback != null ){ this .activityCallback.onDestroy(); } } public void onRestart() { if ( this .activityCallback != null ){ this .activityCallback.onRestart(); } } } |
大家可以看到,我们这里,通过U8SDK这个单例,将所有的东西进行了连接和整合。在init方法里面,我们init插件管理类,也init所有的插件包装类。然后,对事件监听器也进行一个简单的包装。使得SDK实现层的调用简单方便,游戏层的调用也简单方便。
最后,因为有的SDK,需要在Activity的系统事件中做一些处理操作,而Activity是在游戏接入我们这个抽象层时,传递进来的,所以,我们在抽象层定义了一个Activity事件监听器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.u8.sdk; import android.content.Intent; public interface IActivityListener { public void onPause(); public void onResume(); public void onRestart(); public void onBackPressed(); public void onNewIntent(Intent newIntent); public void onStop(); public void onDestroy(); public void onActivityResult( int requestCode, int resultCode, Intent data); } |
这样,整个SDK接入的抽象层就差不多了。还有一些细节,我们可以后面边开发,边迭代,边完善。后面我们将用实例来看看,游戏层怎么调用这个抽象层SDK,以及具体的SDK接入怎么来实现这个抽象层。
本文出自 优优网事,转载时请注明出处及相应链接。
SDK接入(U8SDK)——SDK抽象层的设计的更多相关文章
- 统一SDK接入(U8SDK)——总体思路和架构
题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360……据统计国内市场当前不下于100家渠道,还包括一些没有SDK的小渠道.每个渠道SDK接入的方法呢,多是大同 ...
- 教你高速高效接入SDK——Unity统一接入渠道SDK(Android篇)
U8SDK的设计之初,就是为了可以支持各种游戏引擎开发的游戏,而不不过Android的原生平台.眼下一大半的手游,都是採用Unity3D和Cocos2dx开发,那么这里,我们就先来一步步给大家演示,用 ...
- SDK接入(2)之Android Google Play内支付(in-app Billing)接入
SDK接入(2)之Android Google Play内支付(in-app Billing)接入 继上篇SDK接入(1)之Android Facebook SDK接入整理完Facebook接入流程之 ...
- 手机游戏渠道SDK接入工具项目分享(三)拨开云雾是个坑
一直在纠结是先写框架设计还是先写掉过的坑,最后本这娱乐大众的态度先写掉过的坑让大家乐呵下. 项目开发过程中遇问题无数,回顾下8个大坑照成了项目一定程度上延期甚至返工. 项目一开始几个人把现有3家主流的 ...
- 【Unity游戏开发】SDK接入与集成——小白入门篇
一.简介 通常一款游戏开发到后期,一般都会涉及到第三方SDK的接入与集成,对于不熟悉SDK接入的同学来说,接SDK每次都是云里雾里,而熟悉SDK接入的同学又觉得不断地重复做接入SDK工作这样没有成就感 ...
- Android开发SDK接入机智云,智能家居实现APP远程控制多设备
代码地址如下:http://www.demodashi.com/demo/12847.html 一.前言. 此框架只用了一周星期做了出来,因为对机智云的框架比较熟悉了 !期间SDK初始化出了问题,去咨 ...
- 手机游戏渠道SDK接入工具项目分享(二)万事开头难
一般接到任务后程序员们通常都开始着手进行技术调研了,但我这活是项目负责人.还有一大堆事情要先期准备,没人能帮忙. 一.人力配置 考虑的之前已经有一波人搞了大半年,但没有起色,先期也没有太大人力需求,所 ...
- SDK接入(3)之iOS内支付(In-App Purchase)接入
SDK接入(3)之iOS内支付(In-App Purchase)接入 继整理了Android平台的SDK接入过程.再来分享下iOS平台的内支付(In-App Purchase)接入,作为笔者在游戏开发 ...
- SDK接入(1)之Android Facebook SDK接入
SDK接入(1)之Android Facebook SDK接入 由于游戏已上线,且处于维护阶段,所以有空写写各种SDK接入过程和遇到的问题,也当作一种工作总结.SDK接入主流分为这么几类,登录.支付. ...
随机推荐
- fastjson将json字符串转化成bean对象解析出错的检查方法
我的情况是:解析第一层数据成功,解析第二层嵌套的数据失败.如: { "response": { "resultcode": "0", &qu ...
- java中trim()函数是什么
trim() 去除字符串前缀和后缀空格 文件名:Test.java ,编译通过 public class Test { public static void main(String args[ ...
- C++ Primer Plus 笔记记录
(一) /a 这个转移字符竟然能调用计算机的硬件 喇叭~~ 对于float c++只能保证6位有效数字 似乎 double是13位 cout.setf(ios_base::fixed, ios_bas ...
- 在Android开发中如何判读当前设备是否连接网络
1:前言: 我们在Android开发的过程中,很多实现是要向远程服务器拿数据的,但是未必当前设备一定连接了网络啊,那么此时我们就是要进行判断的了, 如果是有网络的话,那么此时就去向远程服务器去拿数据, ...
- html5调取手机摄像头或相册
html5调用手机摄像头或者相册 由于input的type=file 格式的文件的界面并不是我们所希望的界面,所以在此我隐藏input,自定义样式,这个样式就在<a>中自己定义,这里我就不 ...
- oracle 第一章总结
sysdba: 即数据库管理员,权限包括:打开数据库服务器.关闭数据库服务器.备份数据库.恢复数据库.日志归档.会话限制.管理功能.创建数据库.sys用户必须用 sysdba身份才能登录,syste ...
- Linux下Java安装与配置
一.卸载系统自带的JDK 如果Linux已经自带OpenJdk,我们需要将它卸载掉,否则可以直接[安装JDK] 查看Linux自带的JDK是否已安装,输入如下命令查看JAVA版本信息. java -v ...
- Windows安装mxnet
code { white-space: pre } div.sourceCode { } table.sourceCode,tr.sourceCode,td.lineNumbers,td.source ...
- python smtplib发送邮件遇到的认证问题
python的smtplib模块主要是用来发送邮件的,使用起来比较方便. 使用程序发送邮件只需要写以下几行代码就OK了: #!/usr/bin/env python import smtplib s ...
- Java 基本数据类型(新手必看资料)
变量就是申请内存来存储值.也就是说,当创建变量的时候,需要在内存中申请空间. 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据. 因此,通过定义不同类型的变量,可以在内存 ...