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]; }
在SDKAccount的 doInit中,将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的代码和我们的业务代码,完全地分离开来了呢?
相关文章推荐
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 我是运营,我没有假期
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- 每个 Linux 游戏玩家都绝不想要的恼人体验
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- 在 Fedora 上使用 Steam play 和 Proton 来玩 Windows 游戏
- Steam 让我们在 Linux 上玩 Windows 的游戏更加容易
- 如何使用 Steam Play 在 Linux 上玩仅限 Windows 的游戏
- 新一代iPad适配应用之游戏篇
- VB实现的《QQ美女找茬游戏》作弊器实例
- C#实现洗牌游戏实例
- C#实现的算24点游戏算法实例分析
- C#实现简单的井字游戏实例
- C++编写简单的打靶游戏
- C++实现基于控制台界面的吃豆子游戏
- 纯javascript实现的小游戏《Flappy Pig》实例