上一篇文章,我们总体地分析并设计了一套高效的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接入怎么来实现这个抽象层。