您的位置:首页 > 移动开发 > IOS开发

iOS 游戏渠道SDK 抽象工程封装(上)

2015-12-01 16:41 507 查看

iOS 游戏渠道SDK 抽象工程封装(上)

一款手机游戏,要是想挣钱,接入渠道SDK是很重要滴。但是渠道SDK有那么多家,每一家的接口也不一样,那么是否需要每一家渠道SDK都来接入一次呢?游戏的研发同学,每次想到这边,都表示一个头,两个大。

那么为了给研发的同学减轻负担,让他们专心搞研发,给所有渠道SDK封装一个抽象工程,是很有必要的一件事情。这样,游戏接入一次抽象工程就OK了,到时候要接入渠道SDK,只需要把文件替换一下,省时又省力,岂不是美美哒。



什么是渠道SDK的抽象工程?

抽象工程,可以说是渠道SDK的驱壳。这个驱壳,可以装下各种各样的渠道SDK。

虽然渠道SDK种类繁多,但是细心一看,他们的接口也是大同小异的。大体上有这么几个:

- 初始化

- 用户登陆

- 用户退出

- 用户支付

- 用户中心

- 工具栏打开关闭

摸清了他们的套路,咱们也可以大大方方地出手了。

抽象工程的总入口

游戏与抽象工程的所有交互,都是由这个类来完成。我们把它命名为SDKAccount

先来个SDKAccount.h的代码

//获取单例
+ (instancetype)sharedInstance;

//sdk用户初始化
- (void)doInit:(NSString*)gameVersion;

//sdk登陆
- (void)doLogin;

//sdk退出
- (void)doLogout;

//sdk切换用户
- (void)doSwitchAccount;

//sdk支付
- (void)doPay:(SDKPayInfo *)sdkPayInfo;

//调用暂停页面
- (void)doPause;

//设置工具栏 YES打开/NO关闭
- (void)doSetting:(BOOL)visible;

//打开用户中心
- (void)doUserCenter;


为了使我们的抽象工程更方便地使用,我们这边采用单例的模式,使用sharedInstance 来获取抽象工程的单例。

然后大家是不是以为,接下来就是在SDKAccount.m里头来实现渠道SDK的代码啦。no no no,这样会使我们抽象工程的业务代码,和渠道SDK的业务混杂在一起,使代码变得杂乱不堪,这是我所不能容忍的。我们把渠道SDK的代码,统统放在另外一个地方,这个后面再讲。

先来讲讲SDKAccount.m

获取单例,我们用最常见的gcd方式来创建。

+ (instancetype)sharedInstance {
static SDKAccount* instance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
instance = [[SDKAccount alloc] init];
});
return instance;
}


初始化

- (void)doInit:(NSString*)gameVersion {
[[SDKContainer sharedInstance] doThirdInit:self gameVersion:gameVersion];
}


登陆

- (void)doLogin {
[[SDKContainer sharedInstance] doLogin];
}


退出

- (void)doLogout {
[[SDKContainer sharedInstance] doLogout];
}


支付

- (void)doPay:(SDKPayInfo *)sdkPayInfo {
SDKUser *sdkUser = [SDKUser sharedInstance];

if(sdkUser.uuid == nil || sdkUser.uuid.length == 0) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_NO_LOGIN]];
return;
}

if(sdkPayInfo == nil) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_PARAM_ERROR]];
return;
}

if([sdkPayInfo.amount intValue] <= 0 || [sdkPayInfo.roleId length] == 0 || [sdkPayInfo.serverId length] == 0 || [sdkPayInfo.productId length] == 0) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_PARAM_ERROR]];
return;
}

SDKPayReq *payReq = [[SDKPayReq alloc] init];
payReq.amount = sdkPayInfo.amount;
payReq.platformUserId = [[SDKUser sharedInstance] uid];
payReq.appOrderId = sdkPayInfo.orderId;
payReq.appProductId = sdkPayInfo.productId;
payReq.appProductName = sdkPayInfo.productName;
payReq.appUserId = sdkPayInfo.userId;
payReq.appUserName = sdkPayInfo.username;
payReq.appRoleId = sdkPayInfo.roleId;
payReq.appRoleName = sdkPayInfo.roleName;
payReq.appRoleLevel = sdkPayInfo.roleLevel;
payReq.appServerId = sdkPayInfo.serverId;
payReq.appServerName = sdkPayInfo.serverName;
payReq.channelId = [self getChannelId];
payReq.deviceId = [self getDeviceId];
payReq.appNotifyUri = sdkPayInfo.notifyUri;
payReq.appExt = sdkPayInfo.ext;
payReq.vipLevel = sdkPayInfo.vipLevel;

[payReq post:^(NSHTTPURLResponse *response, NSDictionary *data) {

SDKLog(@"订单信息---%@", data);

NSNumber *code = data[@"code"];
if([code intValue] == 0) {

NSDictionary *info = data[@"data"];

NSString *orderId = info[@"order_id"];
NSString *amountStr = [NSString stringWithFormat:@"%d", [[payReq amount] intValue]/100];//单位为元
NSString *productId = payReq.appProductId;
NSString *productName = payReq.appProductName;
NSString *roleId = payReq.appRoleId;
NSString *serverId = payReq.appServerId;
NSString *serverName = payReq.appServerName;
NSString *payDesc = [NSString stringWithFormat:@"Product_%@", payReq.appProductId];

NSDictionary *orderMsg = @{
@"orderId":CleanNil(orderId),
@"amountStr":CleanNil(amountStr),
@"productId":CleanNil(productId),
@"productName":CleanNil(productName),
@"roleId":CleanNil(roleId),
@"serverId":CleanNil(serverId),
@"serverName":CleanNil(serverName),
@"payDesc":CleanNil(payDesc)
};

[[SDKContainer sharedInstance] doPayWithOrder:orderMsg];

} else {

[self notifitionCreateOrderError];
}

} failure:^(NSHTTPURLResponse *response, NSDictionary *data, NSError *error) {
[self notifitionCreateOrderError];
}];
}


到这边,有些朋友会问,为什么doPay的代码会多出这么多?那是因为这些代码,是我们自己的业务逻辑。

在调用渠道SDK的支付之前,我们需要先把支付信息传到我们的服务器上面,生成一个订单号,这个订单号,会记录在数据库中,作为以后游戏用户的支付凭据。生成完以后,再回传回来。这时候我们才能用这个订单号,来调用渠道SDK的支付接口。

暂停页面

- (void)doPause {
[[SDKContainer sharedInstance] doPause];
}


有些渠道SDK(比如91助手),要求在按下home键回到主界面,再切换回游戏时,会弹出一个暂停页面,用来展示广告。这时就要在AppDelegate中的applicationWillEnterForeground方法中,调用doPause这个方法。

工具栏(悬浮球)开启关闭

- (void)doSetting:(BOOL)visible {
[[SDKContainer sharedInstance] doSetting:visible];
}


用户中心开启关闭

- (void)doUserCenter {
[[SDKContainer sharedInstance] doUserCenter];
}


切换账号

- (void)doSwitchAccount {
[[SDKContainer sharedInstance] doSwitchAccount];
}


切换账号实际上也就是先退出,再调用登陆窗口。

盛放渠道SDK代码的容器

前面讲到,为了不使我们的代码变得杂乱不堪,我们把业务逻辑和渠道SDK的代码分开来。创建一个新的类,用来盛放渠道SDK代码。这个类大家也猜到了,就叫做SDKContainer

先上SDKContainer.h的代码。

@protocol SDKContainerDelegate <NSObject>

- (void)initFinish:(NSDictionary*)initMsg;

- (void)loginFinished:(NSDictionary*)loginMsg;

- (void)logoutFinished:(NSDictionary*)logoutMsg;

- (void)payFinished:(NSDictionary*)payMsg;

//登录成功
- (void)notifitionLoginSuccess:(SDKUser*)sdkUser;

//登录失败
- (void)notifitionLoginError;

//登录取消
- (void)notifitionLoginCancel;

//注销成功
- (void)notifitionLogoutSuccess;

//创建订单失败
- (void)notifitionCreateOrderError;

//充值用户未登录
- (void)notifitionPayNoLogin;

//充值成功
- (void)notifitionPaySuccess;

//充值失败
- (void)notifitionPayError;

//充值取消
- (void)notifitionPayCancel;

//充值发货中
- (void)notifitionPayShipping;

//充值网络异常
- (void)notifitionPayNetError;

@end


首先一上来是一个协议,有协议就有人遵守。没错,这个协议是为SDKAccount准备的。

先在SDKAccount.h的头部写上

@interface SDKAccount : NSObject<SDKContainerDelegate>


然后在SDKAccount.m中实现这些方法

- (void)initFinish:(NSDictionary*)initMsg {
[self doLogin];
}

- (void)loginFinished:(NSDictionary*)loginMsg {
// 登录成功,开发者可继续游戏逻辑
SDKLoginReq *loginReq = [[SDKLoginReq alloc] init];
loginReq.code = loginMsg[@"code"];
loginReq.uid = loginMsg[@"uid"];
loginReq.username = loginMsg[@"username"];
loginReq.nickname = loginMsg[@"nickname"];

[loginReq post:^(NSHTTPURLResponse *response, NSDictionary *data) {

//打印日志
SDKLog(@"登录信息---%@", data);

NSNumber *code = data[@"code"];
if([code intValue] == 0) {

NSDictionary *dataDic = data[@"data"];

SDKUser *sdkUser = [SDKUser sharedInstance];

sdkUser.uuid = dataDic[@"uuid"];
sdkUser.token = dataDic[@"check_token"];
sdkUser.platform = FYSDK_PLATFORM_NAME;

NSDictionary *user = dataDic[@"user"];
NSString *uid = user[@"id"];
NSString *username = user[@"name"];
NSString *nickname = user[@"nickname"];

if (loginReq.uid.length != 0) {
sdkUser.uid = loginReq.uid;
} else {
sdkUser.uid = uid;
}

if (loginReq.username.length != 0) {
sdkUser.username = loginReq.username;
} else {
sdkUser.username = username;
}

if (loginReq.nickname.length != 0) {
sdkUser.nickname = loginReq.nickname;
} else {
sdkUser.nickname = nickname;
}

self.sdkUser = sdkUser;
[self notifitionLoginSuccess:sdkUser];
} else {
[self notifitionLoginError];
}

} failure:^(NSHTTPURLResponse *response, NSDictionary *data, NSError *error) {
[self notifitionLoginError];
}];
}

- (void)logoutFinished:(NSDictionary*)logoutMsg {
[self notifitionLogoutSuccess];
}

- (void)payFinished:(NSDictionary*)payMsg {

}

//-------------------------各种通知-------------------------------
//登录成功
- (void)notifitionLoginSuccess:(SDKUser*)sdkUser{
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_LOGIN object:self userInfo:[SDKResponseBase dict:SDK_RESP_SUCCESS sdkUser:sdkUser]];
}

//登录失败
- (void)notifitionLoginError {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_LOGIN object:self userInfo:[SDKResponseBase dict:SDK_RESP_LOGIN_ERROR]];
}

//登录取消
- (void)notifitionLoginCancel {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_LOGIN object:self userInfo:[SDKResponseBase dict:SDK_RESP_CANCEL_ERROR]];
}

//创建订单失败
- (void)notifitionCreateOrderError {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_CREATE_ORDER_ORDER]];
}

//注销成功
- (void)notifitionLogoutSuccess {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_LOGOUT object:self userInfo:[SDKResponseBase dict:SDK_RESP_SUCCESS]];
}

//注销失败
- (void)notifitionLogoutError {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_LOGOUT object:self userInfo:[SDKResponseBase dict:SDK_RESP_PARAM_ERROR]];
}

//充值用户未登录
- (void)notifitionPayNoLogin {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_NO_LOGIN]];
}

//充值成功
- (void)notifitionPaySuccess {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_SUCCESS]];
}

//充值失败
- (void)notifitionPayError {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_PAY_ERROR]];
}

//充值取消
- (void)notifitionPayCancel {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_CANCEL_ORDER]];
}

//充值发货中
- (void)notifitionPayShipping {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_PAY_ING]];
}

//充值网络异常
- (void)notifitionPayNetError {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAY object:self userInfo:[SDKResponseBase dict:SDK_RESP_NET_ERROR]];
}

//暂停页面关闭通知
- (void)notifitionPuasePageClose {
[[NSNotificationCenter defaultCenter] postNotificationName:SDK_CALLBACK_PAUSE_PAGE_CLOSE object:self userInfo:nil];
}


SDKAccountdoInit中,将self传给SDKContainer。这样SDKContainer就可以回调SDKAccount啦。

上面的loginFinished方法,也是调用了我们的业务逻辑。在渠道SDK登陆完以后,需要将token发到我们的服务器中,然后再由我们的服务器,转发给渠道SDK服务器去验证登陆。

我们来继续SDKContainer的内容。

SDKContainer.h

@interface SDKContainer : NSObject<XXXSDKDeletage>

+ (instancetype)sharedInstance ;

- (void)doThirdInit:(id<SDKContainerDelegate>)delegate gameVersion:(NSString*)gameVersion;

- (void)doLogin;

- (void)doLogout;

- (void)doPayWithOrder:(NSDictionary*)orders;

- (void)doPause;

- (void)doSetting:(BOOL)visible;

- (void)doUserCenter;

- (void)doSwitchAccount;

@end


有没有很眼熟,和SDKAccount很像,是吧。

然后是SDKContainer.mm

#import "SDKContainer.h"
@interface SDKContainer()

@property (nonatomic) id<SDKContainerDelegate> delegate;

@end

@implementation SDKContainer

+ (instancetype)sharedInstance {

static SDKContainer* instance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
instance = [[SDKContainer alloc] init];
});
return instance;
}

- (void)doThirdInit:(id<SDKContainerDelegate>)delegate gameVersion:(NSString*)gameVersion {
self.delegate = delegate;

//----------------打印平台版本号---------------
NSString *platformVersion = @"";//更新SDK必填
NSLog(@"---Platform Version---%@", platformVersion);

//---------------sdk初始化代码-----------------

//0横屏 1竖屏
if([SDK_CONFIG_ORIENTATION isEqual:@"0"]) {
//-----------sdk横屏设置-----------

} else {
//-----------sdk竖屏设置-----------

}

}

- (void)doLogin {
//-----------sdk登陆接口-----------

}

- (void)doLogout {
//-----------sdk退出接口-----------

}

- (void)doPayWithOrder:(NSDictionary*)orders {

NSString *orderId = orders[@"orderId"];             //订单
NSString *amountStr = orders[@"amountStr"];         //金额,单位为元
NSString *productName = orders[@"productName"];     //商品名
NSString *roleId = orders[@"roleId"];               //角色名
NSString *serverId = orders[@"serverId"];           //区服id
NSString *payDesc = orders[@"payDesc"];             //额外支付信息

//-----------sdk支付接口-----------

}

- (void)doPause {
//------------sdk暂停页面-------------
}

- (void)doSetting:(BOOL)visible {

if (visible) {
//-----------sdk打开工具栏-----------

} else {
//-----------sdk关闭工具栏-----------

}
}

- (void)doUserCenter {
//-----------sdk打开个人中心-----------

}

- (void)doSwitchAccount {
[self doLogout];
[self doLogin];
}

//---------------渠道sdk回调接口----------------

//-----------------------------------------


这边我们为渠道SDK预留了位置,将来要接入渠道SDK的时候,只需要将渠道SDK的代码,放到SDKContainer.mm中对应的位置就行了。是不是很方便呢?

细心的朋友发现,SDKContainer.mm多了一个m出来。这是因为有些渠道SDK是用C++Obj-C混合写的。所以要求我们要将.m改为.mm。平时我们使用.mm也不妨碍我们的代码。

当渠道SDK需要回调游戏的时候该怎么办?只需要调用一下delegate中的方法,就可以了。

*(1)初始化结束调用以下代码
[self.delegate initFinish:nil];

*(2)登陆结束调用以下代码
NSString *code = ;      //登陆时的token
NSString *uid = ;       //游戏账号的唯一值
NSString *username = ;  //游戏账号名
NSDictionary *loginMsg = @{
@"code":code,
@"uid":uid,
@"username":username
};
[self.delegate loginFinished:loginMsg];

*(3)登陆取消调用以下代码
[self.delegate notifitionLoginCancel];

*(4)登陆失败调用以下代码
[self.delegate notifitionLoginError];

*(5)支付成功调用以下代码
[self.delegate notifitionPaySuccess];

*(6)支付取消调用以下代码
[self.delegate notifitionPayCancel];

*(7)支付失败调用以下代码
[self.delegate notifitionPayError];

*(8)支付用户未登陆调用以下代码
[self.delegate notifitionPayNoLogin];

*(9)创建订单失败调用以下代码
[self.delegate notifitionCreateOrderError];

*(10)充值发货中调用以下代码
[self.delegate notifitionPayShipping];

*(11)充值网络异常调用以下代码
[self.delegate notifitionPayNetError];

*(12)退出成功调用以下代码
[self.delegate notifitionLogoutSuccess];


然后由SDKAccount统一去回调游戏。

这样是不是将渠道SDK的代码和我们的业务代码,完全地分离开来了呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息