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

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的代码和我们的业务代码,完全地分离开来了呢?

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值