Android 手游聚合SDK那些事

前言

在安卓游戏SDK这个技术领域呆了已经有4年多,从游戏发行中的一员逐渐转为游戏研发中的一员。从开始什么都不懂的菜鸟,摸爬滚打了几年,多多少少对这个行业以及技术领域有了相对成熟的理解。本文纯概念性的东西较多,其主要是为了记录一下这几年知识,分享给有需要的人。也让大家对这个游戏SDK行业,有一个基本的理解。

1. 聚合SDK的概念

在手游行业里,聚合SDK就是一个中间层,可以理解为连接游戏和渠道SDK交互的桥梁。举个例子说明一下印象会更深刻,我们都会去买火车票,买火车票不一定在火车站买,去12306官方网站购买或者电话订票 ,也可以去代售点购买,中国13亿人口可不是每个人都会网络或者手机订票 ,还是有部分人选择火车票代售点去买票。 铁路局官网就好比是游戏研发方,火车票代售点就好比游戏发行方(提供聚合SDK),铁路局授权给火车票代售点,代售点需要给官方授权费,代售点可以提供相关服务从购票者中获取相关利益。换个技术角度来看代售点就是一个很好的代理模式。

1.1 游戏研发

游戏研发方就是专门开发游戏的公司,我们称之为CP。一般游戏研发,很少会自己去推广、运营自己的游戏,术业有专攻,重点还是开发游戏为主。

1.2 游戏发行

游戏发行方负责游戏推广,运营以及相关技术服务,发行方手里有资源,这种资源来自哪里呢?其中来自两个点,第一点自身平台的用户量(资源),第二点是渠道或者平台方,例如华为渠道,华为渠道的用户量不用我说大家就应该知道。

研发方有游戏,手里没有很多资源 。发行方有丰富资源,没有游戏团队来开发游戏。互补一下商量如何共赢合作一起赚钱呗,由发行方提供一套手游聚合SDK,其中包含核心的登录 支付等功能,研发方需要按照文档定的标准,只要接入发行方的聚合SDK,剩下的什么都不用管了。发行方要做的就是把融合完成的游戏包上传到各大游戏应用商店和自己的官方平台即可,发行方和研发方就可以坐等对账分成咯。

1.3 渠道SDK

上面提到过资源,用户量这一个概念,一个游戏想要赚钱就需要一个庞大的用户量,玩的人数越多游戏越吸引人意味着付费率会很高。平台上线了游戏之后,依赖用户自己来下载游戏,起量是很慢的,所以需要推广,如果使用推送让用户去下载,那么用户体验会很差。所以需要让那些有影响力的人来做有偿推广。例如华为游戏商店 、 oppo游戏商店 、vivo游戏商店等 硬核联盟。这样的渠道有着大量的用户,所以我们为了赚钱要把游戏上架到各大游戏商店,让渠道平台的大量用户去玩。

1.3.1 游戏渠道SDK

上架游戏商店之前,我们需要在游戏中接入对应联运SDK 。Huawei、OPPO 、ViVO等联运sdk是对应自己的平台,其中就包含最核心的2个体系 登录和支付。

要上架我们渠道平台,我们给你做推广 ,让大量的用户上我的平台来玩你的游戏。这就相当于你上我家来做客,就要按我们的规矩来,就需要使用我们提供的服务(渠道sdk API)。

当然了使用渠道的服务也是需要与渠道进行资金的分成,一般情况下总收入的50%是划到 渠道平台的,剩下 50%是发行和研发来进行分成。(特殊平台例如腾讯 划分比例会高一些)

1.3.2 官方渠道SDK

上游戏渠道要和平台进行比例分成,那么上自己的游戏平台收益就是百分百的完全不用跟别人分成总收入。官方渠道SDK是代表着自身的发行平台,做发行的一般都会有属于自己渠道平台SDK,自己平台即可以封装好给别人用,也可以接入到聚合SDK 中使用。

官方SDK 本质上跟渠道SDK没什么区别,自身也是一个渠道SDK,拥有核心登录和支付一系列API。它可以打成aar包接入到聚合SDK中 ,也可以作为一个独立的渠道SDK 对外给别人使用。

1.4 聚合SDK是什么?

上述给大家说了一些概念和名词,可能有些朋友还是不好理解聚合sdk。那么我们一步步去分析讲解。先抛弃聚合SDK的概念,先看一下一个游戏是怎么直接去接入渠道联运SDK的,我之前写过博客Unity接入OPPO联运SDK这个大家可以去看看。

图中为游戏直接接入OPPO网游SDK的登录的逻辑,逻辑图中数字对应下列的描述数字

接入sdk逻辑图.png
  1. 游戏客户端调用SDK客户端登录API,如果满足登录条件的话,SDK客户端携带数据去请求SDK服务器端进行接口验证。

  2. 到第二步了说明SDK服务器端登录验证通过,然后会向SDK客户端返回一些数据。

  3. 游戏客户端 通过SDK客户端的回调通知后得到数据,一般情况下我们只需要得到token(令牌)和ssoid(用户唯一标识)即可。

  1. 游戏客户端获取到token 和ssoid后 ,拿数据给游戏服务器端发起验证请求。

  2. 游戏服务器得到token 和ssoid后,去请求SDK服务器(SDK服务器会有一份文档对外提供,正常情况下是一个登录请求的URL,游戏服务器按照要求进行拼串加密请求即可)

  3. 到这一步说明SDK服务器的登录二次验证是没问题的,SDK服务器校验的结果传回给游戏服务器

  4. 游戏客户端收到了游戏服务端的成功结果,就可以进行游戏的操作。


上述7个步骤清晰的解释了游戏直接和渠道SDK交互的逻辑,文章开头介绍过游戏研发重点是放在游戏开发以及功能实现上,虽然游戏研发可以直接接入SDK渠道,但是考虑到一个问题,游戏需要推广和运营,游戏研发不擅长这些。国内的SDK渠道多如牛毛大大小小150个渠道应该是有了,要是150个渠道都接入一遍想想就酸爽, 而且渠道SDK还需要不定时更新,这无疑占用研发的大量时间精力。那么有没有针对游戏研发方一个更好的方案呢?游戏方只需要接入一个SDK 就可以完成N个游戏渠道SDK的上架呢 ,答案 有的 ,聚合SDK就这样诞生了。


聚合SDK是连接渠道SDK和游戏方的一个桥梁或者说是中间层, 何解呢? 首先聚合SDK要和游戏渠道SDK进行交互,按照渠道SDK的文档要求进行接入,渠道SDK N多个他们的api 都是大同小异区别不是很大,那么把SDK共同点都抽象出来加以扩展(对内包容所有渠道SDK对外统一接口和回调),整合成一套完善的聚合框架提供给游戏研发方。研发方只需要接入一套聚合SDK就完活,剩下什么都不需要管可以安安心心去忙游戏相关的工作。由发行方进行游戏的打包分包上架游戏商店。


下图很好的体现出聚合SDK,作为聚合只有一个,需要兼容接入多家游戏(为了多赚钱不可能只跟一家研发方合作),多家渠道(上架越多意味着收入会更多一些)

聚合SDK.png

聚合SDK是连接游戏研发与渠道SDK的桥梁,让他们可以通话,但是通话内容是经过聚合层加密的,也就达到了发行公司代理发行运营的目的。可以让你们通话,但不让你们直接见面,再通俗的说,聚合==中间商。下图是加入聚合SDK的统一登录流程,就是在原来研发直接接入渠道sdk基础上,多套了一层聚合客户端和聚合服务端而已。聚合支付流程和聚合登录流程差不多就不展示了。

聚合SDK登录流程.jpg


章节小结:

手游聚合SDK 和普通的SDK(例如统计、定位)还是有很大区别的,最大的区别就是聚合SDK 需要提供一个统一的账号体系和支付体系,分别对应游戏登录和支付功能。

2. 聚合SDK对外接口设计

聚合SDK对外的设计就是一个抽象层,对每个游戏来说,只需要接入抽象层足以,而对每个渠道SDK的接入,就是抽象层的具体的实现。

考虑到一些渠道SDK 有的API(例如退出游戏)会没有,建议通过反射来判断当前是否支持该方法,例如isSupportFunction("exitSDK") 如果返回false ,说明当前渠道不支持退出功能,CP需要处理游戏自身的退出游戏方法。

    /**
     *判断是否支持对应方法
     * @param function
     * @return
     */
    public boolean isSupportFunction(String function) {
        Method method = null;
        try {
            method = getClass().getMethod(function, Map.class);
            return true;
        } catch (NoSuchMethodException e) {
            try {
                method = getClass().getMethod(function);
                return true;
            } catch (NoSuchMethodException e1) {

            }
        }
        return method != null;
    }
  1. 初始化
    • Application初始化:
      优秀的SDK会考虑到兼容性,设计聚合SDK的时候要多给别人选择性,考虑到游戏有application逻辑的,和没有application逻辑,聚合SDK都要做处理。

    • Activity的初始化

  1. 登录

统一登录验证接口,聚合客户端接收渠道回调回来的参数,去聚合服务器校验。聚合客户端会提供给cp 登录成功 、登录失败 、登录取消三个回调状态,聚合服务器校验成功 cp会得到token 和uid 等信息,格式为json

  1. 支付

统一支付验证接口,跟登录差不多 不做描述。备注下:订单号不要过长不能超过30个字符

  1. 注销(切换账号)

  2. 退出

  3. 生命周期

设计的时候生命周期最好都要考虑全

 @Override
    protected void onStart() {
        super.onStart();     
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onRestart() {
        super.onRestart();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    @Override
    public void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
    }
    @Override
    public   void  onRequestPermissionsResult(int requestCode, String[] permissions,  int[] grantResults){
        super.onRequestPermissionsResult(requestCode,permissions,grantResults);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
    }
  1. 渠道数据上报
    正常情况下集合中的value值是从游戏哪里获取真实数据 ,传输格式json。文中代码仅供演示
        private static Map<String, String> mRoleInfo = null;

        mRoleInfo = new HashMap<String, String>();
        mRoleInfo.put("roleId", "123456");// 当前登录的玩家角色ID,若无,可传入userid
        mRoleInfo.put("roleName", "角色名称");// 当前登录的玩家角色名,不能空
        mRoleInfo.put("roleLevel", "10");// 当前登录的玩家角色等级,不能为空,必须为数字,且不能为null,若无,传入0
        mRoleInfo.put("serverId", "1");// 当前登录的游戏区服ID,不能为空,必须为数字,若无,传入0
        mRoleInfo.put("serverName", "服务器名称");// 当前登录的游戏区服名称,不能为空,长度不超过50,不能为null,若无,传入“无”
        mRoleInfo.put("vip", "vip20");// 玩家VIP等级   若无,传入0
        mRoleInfo.put("moneyNum", "100金币");// 当前玩家身上拥有的游戏币数量  若无,传入“无”
        mRoleInfo.put("roleLevelUpTime", System.currentTimeMillis() / 1000 + "");// 角色等级变化时间,仅创建角色时传入,同一角色创建时间不可变(单 位:秒 ),长度 10
        mRoleInfo.put("roleCTimeroleCTime", System.currentTimeMillis() / 1000 + "");// 角色创建时间,仅创建角色时传入,同一角色创建时间不可变(单 位:秒 ),长度 10
  • 选择区服

  • 创建角色

  • 选择角色

  • 进入游戏

  • 角色升级时

  • 退出游戏

    对应场景(这些场景满足国内大部分SDK)接收游戏传来的数据 上报给渠道SDK,这个一般渠道SDK做统计用

  1. 实名认证

    2020年新增的一个接口 ,SDK渠道为了响应国家号召,所以聚合也需要提供接口给CP


3.聚合SDK开发规范

开发一套聚合SDK,会提供给多个CP去使用,我们无法保证CP开发的游戏,是否有接入其他的SDK,或者自身使用到了第三方的SDK。所以我们在开发SDK时候要注意以下几点。

  1. 依赖第三方库导致的冲突

说明:作为聚合SDK,应该尽量少使用开源库,或者说不用开源库,而是通过手写网络框架,手写数据库等等,主要是考虑两个方面:

  • 减小SDK体积

  • 避免渠道和CP接入的时候发生依赖冲突

曾经遇到过这样的场景 渠道用了低版本的v4包 ,游戏给的母包(接入好聚合sdk的游戏包apk)用了高版本的 v4包 。导致一个问题拆包合并覆盖资源时候smali文件丢失,重新生成的渠道包 各种崩溃闪退。简述一下这个问题最后解决方案 就是删除掉渠道低版本v4包采用高版本的。v4向下兼容

规范:应该尽可能地避免依赖过多的第三方库。特别是一些知名的库,比如android support库,okhttp3, glide, eventbus等等。 如果你确实要使用,也可以使用工具将这些开源库的包名改为您自定义的名称。

  1. 聚合SDK中的资源文件命名冲突

说明:聚合SDK中可能会引用的布局资源,如果与CP或者渠道SDK资源重名会很麻烦。资源重名冲突的问题一般会出现在偏门或者小众的sdk上。比较出名大众SDK都会对资源命名有严格要求。举个例子sdk中有layout_main.xml, 游戏母包或者渠道SDK中也带有同名的layout_main.xml。在解包合并资源时候就会出现资源丢失奔溃的问题。这也是为什么强调资源命名一定区分开。

规范:资源等命名,需要加上自己独特的前缀,避免与CP或渠道SDK冲突。例如
baidu_layout.xml,baidu_dimen.xml.资源id的命名同样如此。一定要规范,不然会有很多麻烦。

  1. 聚合SDK中的所使用资源索引(R.java)直接引用,会导致接入游戏中,资源引用丢失或崩溃等情况

规范: 所有对res文件下资源的引用,代码统一使用context.getResources().getIdentifier() 动态方式获取资源id

技术博客:Android动态获取资源ID之getIdentifier()

  1. 聚合SDK对外接入文档规范

说明:考虑到CP可能是国内的研发团队 也可能是歪果仁,所以文档要提供两份 一份国内简体中文版 和国外英文版两套文档。文档内容一定要简单明了,不要做过多赘述,层次要清晰,文档内容涉及的专业术语要写的官方些。常见的SDK问题和聚合SDK架构逻辑要描述清楚,这样能减少不必要的沟通成本。

文档规范:

  • 聚合SDK文档概要说明

  • 更新日志

  • 快速升级

  • 接入准备与环境配置

  • 聚合SDK架构逻辑图

  • SDK接口(每个接口都要标注好必接和选接)

  • SDK接入常见问题

4.聚合SDK常见的一些问题,以及解决方案

  • HTTP和HTTPS
    Android9.0 默认是禁止所有的http请求的,作为聚合SDK是需要兼容做下兼容的。避免游戏工程或者渠道SDK使用HTTP导致崩溃出现。我列出两种方案,供大家参考。

    1. 在建立res/xml/network_security_config.xml,
      <?xml version="1.0" encoding="utf-8"?>
      <network-security-config>
          <base-config cleartextTrafficPermitted="true" />
          <domain-config cleartextTrafficPermitted="true">
                <domain includeSubdomains="true">gamecfg-mob.ubi.com</domain>
          </domain-config>
      </network-security-config>
    

    在清单文件的application中添加android:networkSecurityConfig="@xml/network_security_config"

    1. 在清单文件的application中添加 属性 android:usesCleartextTraffic="true" (这种方式比较省事)
  • Java版本管理

      compileOptions {
          sourceCompatibility JavaVersion.VERSION_1_8
          targetCompatibility JavaVersion.VERSION_1_8
     }
    
    

    主要为了兼容,这种设置国内渠道SDK很少 ,海外的SDK比较多

  • 权限问题
    关于权限问题,聚合SDK可以做成工具类或者做成一个权限库对CP开放接口。可能CP也会对权限做处理,如果同时获取,在玩家还没给权限的情况下,有些手机是会重复弹出权限询问框的,所以这个权限问题最好是让CP控制。

  • 支付补单逻辑
    无论是聚合客户端还是聚合服务端,都有自动补单的能力,聚合服务器一定要完善,对新的补单请求要做及时处理。聚合客户端也要做好本地数据的存储以及定时的清除补单数据,在初始化或者支付前校验一下是否有调单的订单。针对调单的问题大部分是单机游戏 。网络游戏正常情况下CP方会有调单处理的逻辑。但也并非绝对。

  • Android Studio 与Eclipse
    考虑到CP特殊性,会提供AS和Eclipse两种DEMO工程,对于海外的一些CP,部分还是有用Eclipse做接入的。正常情况下提供aar文件,eclipse是不支持aar文件的,但是可以把aar文件拆分下,aar本质上也是个压缩包。

  • 方法超限的问题
    方法超限问题在国内的SDK比较常见的 例如接入百度和奇虎360的网游联运SDK,考虑到这种情况,在设计聚合SDK的时候,在application里会处理对应的逻辑。在跟CP对接时候文档一定要明确 版本避免与CP和渠道方的冲突。关于MultiDex的用法可参考我写过的文章:MultiDex使用和原理解析

  • 部分高版本机型 出现底部栏黑边情况
    在清单文件中application属性里设置android:resizeableActivity = “true”并加上
    <meta-data android:name="android.max_aspect" android:value="2.4"/>属性,
    这俩属性配套使用。如果还会不行在Activity的onCreate()里调用此方法。

      private void SetScreen() {
          Window window = getWindow();
          //使内容出现在status bar后边,如果要使用全屏的话再加上View.SYSTEM_UI_FLAG_FULLSCREEN
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
              Log.i("VERSION_CODES", "Build.VERSION_CODES.JELLY_BEAN");
              window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
          }
          //设置页面全屏显示
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
              Log.i("VERSION_CODES", "Build.VERSION_CODES.P");
              WindowManager.LayoutParams lp = window.getAttributes();
              lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
              //设置页面延伸到刘海区显示
              window.setAttributes(lp);
          }
      }
    
    
  • 关于异形屏的问题
    异形屏是最近这一两年出来的,每个手机厂商平台提供对应的异形屏适配的文档,我们可以根据文档提供的方式封装成工具类, 对外供CP调用。这里我就不做展示了很简单。
  • 部分机型广告SDK问题
    聚合SDK根据需求封装过广告的插件 ,一些特殊的渠道广告SDK ,例如OPPO和VIVO的广告SDK,只能在OPPO和VIVO手机上才能做广告展示。很坑,这个我觉得需要记录一下。

结语

本人表述能力有限,大家多多谅解,希望这篇文章能带给你一些帮助,如果还是没有理解欢迎私信骚扰我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,100评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,862评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,993评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,309评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,303评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,421评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,830评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,501评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,689评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,506评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,564评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,286评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,826评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,875评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,114评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,705评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,269评论 2 341