由于SDK的特殊性,所以对于SDK的开发来说,一开始对于SDK的一些通用的整体的元素的设计至关重要。因为SDK(尤其很多平台SDK,使用的应用成百上千)一个及其细微的调整都会影响很多开发者的版本周期。因此前期的设计显得尤为重要。关于这部分内容,我会分两篇来介绍,这篇重点介绍具体接口的设计。另一篇SDK设计心得之架构和资源将重点介绍SDK的架构和一些资源的使用方式。
一个牛逼的接口名称可以替代无数的注释
一个接口只做一件事。如果有两个比较接近的功能,但是用一个接口实现有点麻烦,那就用两个接口,不要为了减少接口而生硬的把两个接口合为一个。
举个让我很蛋疼的例子吧。估计说太直接了有人看到就知道我在批评他,委婉一点。我们有个功能有两个接口:一个是需要传参数,另一个不需要传参,两者的逻辑完全是独立的。本来是根据第一个原则设计了两个信、达也算雅的接口来实现。但是由于这部分内容接入比较复杂,加上又都是异步接口,所以接入成本确实比较高,然后有人反映能不能优化一下。然后优化的结果就是强制我们把两个接口合为一个,增加一个参数来标识最终调用的是带参数的接口还是不带参数的接口。我当时就懵逼了。但是又不能不改,实在是痛苦。
其实这里游戏反映接入成本高,我们应该是去分析什么原因引起的接口成本身高,能不能优化。不能直观的认为一个功能要两个接口,接口太多了接入成本就高,就要删接口。非把两个只是功能相关但是逻辑完全没有关系的接口合为一个带来最大的问题就是游戏的接入成本没有降低,但是自己的代码逻辑变得复杂了……
接口调用的参数要尽可能少,SDK能自身获取的就不要让开发者继续传递,尽可能少的在一个接口中使用同一数据类型的参数,如果确实很多,建议封装为Object作为参数。
好吧,在举个实际的例子,发现所有的经验都是血泪史,要哭了啊。我们有不少功能(其实就是分享)游戏调用的时候需要传递比较多的参数,而且这些参数有个共同点,都是String。有些参数调用了是直接展示出来的,有些参数是放在回调或者数据统计使用的,完全是不知道参数正确与否的。然后在某一次,有开发者调用的时候就把两个非关键值填反了,因为两个String,所以所有的调用都是通的,因为这两个参数调用以后都不是直接展示出来的,所以没有人发现掉错了。等到版本发布,数据量变大的时候,才发现统计结果和一个业务逻辑都有问题。最后问题的原因竟然真的就是两个String参数位置写错了!!!
对于这种问题,我们目前的做法是当有多个同一数据类型的参数时,直接把他们封装为一个Obj,调用的时候先给obj赋值,然后再调用,这样就大大减少了出错的几率。当然,这还是不能避免问题,比如下图这样的……
所有接口参数必须要做合法性校验。不要让别的接口去保证调用你接口的参数一定是合法的。
所有接口做的第一件事就应该是对参数做合法性校验。不要等到逻辑跑完大半了再告诉参数不合法,调用失败。
Are u kidding me?对于需要转义、需要类型转换等的参数,一定要处理,而且尽量尽早的去处理,虽然客户端没有XSS或者SQL注入什么的,不代表你就可以不用考虑
实在不想再举例子了,这才写了一点,我就严重的感觉自己就是个loser,为什么什么问题我遇到过…………为了总结经验,还是正视自己吧。我们有一个接口,最开始只在一个地方用,而且调用和实现都是同一个人写的,所以他就偷懒没有做参数合法性校验。然后过了好多天,另一个同学开发了一个接口,也调用了之前的接口。然后问题来了:这个接口参数偶现Crash,最终定位是因为传递了个null进去,而被调函数没做参数合法性校验…………
即使再小的系统,也会有一些通用名词,对于一些通用名词或者模块的叫法、写法一定要统一。
忽然发现在这一系列文档中,有很多东西自己叫的都不统一,又打脸了。具体来说就是一些通用名词的写法一定要规范,其实大多数可以通过编码规范来避免,例如openid和open_id这种只要编码规范统一就不会同时出现。但是对于具体的名词就不好说了,比如openid
,openID
,openId
,OpenID
。这个我还能忍,但是对于QQ的写法我就不能忍了,QQ已经是一个名词,是一个标准的写法了,在QQ结构中看到qq
也就算了,竟然还见过有人写Qq
,这是什么鬼,什么鬼?!!再举一个微信的例子,写WX
,wechat
,weChat
,wx
,weixin
的都有,这么多写法,晕不晕?让新来的同学怎么搞~~
又一次感概只有你想不到,没有程序猿哥哥做不到~~
可以同步的接口,一定不要异步
能不用全局回调就一定不要用全局的回调
一定要用全局回调最好按照模块分开,一个模块一个回调。开发者只需实现他关心模块的回调即可。无关模块的回调设置与否对SDK的正常使用没有影响;另外每一个回调里最好又能区分回调属于哪次调用的字段。
同一个回调里面的接口尽可能的少,可以合并的尽量合并。
个人感觉总结的已经比较到位了,就再讲讲我们怎么做的以及有什么优缺点。最开始我们所有模块公用同一个全局回调,因为当时模块也不多,而且都是必接模块,最关键是多个回调开发者接入的时候比较麻烦,所以选择了不区分模块把所有回调统一整合到一个全局回调。
随着业务发展,我们的SDK包含了越来越多的模块,有些模块是属于个性化的,小众需求的,再把他合到通用的全局回调并不合适,因为很多开发者并不使用这部分功能,却还要关心对应的回调。因此对于这些完全独立而且小众的需求开始使用自己的独立的全局回调。这样虽然解决了所有的问题,但是感觉太像小作坊的模式。因此建议还是最好一开始就按照模块各自设置独立的全局回调。
最新补充,最新的SDK中,我们已经在逐步弃用全局回调,直接在接口调用的时候让同步添加对应的接口回调。
关于多线程,其实本来和SDK关系不大,但是个人觉的有必要专门说明一下。就说一下下吧。关于多线程、handle是什么,用了有什么好处就不多说了,就简单总结几点吧。
SDK除非必须,不要使用应用的主线程,就算使用也只能是简单操作,不能长时间占用。
SDK应该有一个专门的线程来处理SDK相关的操作。
这样做最主要的目的就是尽可能的减少SDK对应用本身的影响,尽可能减少SDK引起的ANR等问题。具体案例就不说了,太废话了。上面的结论就带来了下面的问题:既然UI进程的影响要尽可能小,那就带来一个进程间怎么通信的问题,Handle就是解决神器。
所有耗时、异步操作都通过handle扔给SDK的线程去处理。处理结束以后再把结果通过handle发给主线程。
任何时候主线程只做一件事,UI调整。所有的耗时操作:读取文件、读取DB、网络数据读取、网络请求发起等全部都要不要用UI线程去处理。
这里就不讲案例了,之前博客已经讲过一些因为主线程处理耗时操作引起的血淋淋的案例了。这里就说下目前个人比较常用的一个可能有耗时操作的函数的处理流程吧。
每个SDK不可能都是完全独立的一部分,尤其是为业务服务的SDK,很可能都还会和周边SDK有一些交集。因此对于怎么处理和周边平台相关的一些逻辑也比较麻烦。这里简单汇总下从客户端的角度认为需要关注的点。
对于非SDK内部逻辑的限制引起的接口不可用,不要直接判定为失败,而是让规则制定方去判定。而SDK本身可以加个Log提示,方便问题定位。
原因有俩:还是说具体的案例吧,不然显得干巴巴。我们的SDK会集成多个SDK,然后对于某些接口平台会有一些限制,比如分享的时候图片大小不能超过10M,缩略图不能超过32K等。最开始我们在做参数校验的时候发现图片大小超过规范以后打算直接返回失败,后来经过讨论,我们决定在超过大小以后打印一行错误日志,还是会去继续调用平台的接口,然后由平台接口调用失败以后回调给我们,我们再回调游戏。加上LOG以后游戏联调的时候,如果调用失败了,我们可以根据LOG快速定位到原因。上线一段时间以后,很多游戏反映32K太小了,果然和预期一致平台将大小改到了64K。那一瞬间,幸福如花儿一般绽放。
对于第三方平台的常量例如错误码等,最好是自己封装一层提供给开发者。不要直接将第三方平台暴漏给开发者
很多时候,开发者会通过一个SDK来同时集成和实现多个SDK的功能,这个时候开发者面对的只有一个SDK。如果你的SDK包含了或者集成了多个第三方的SDK,你要做的就是不要让开发者还需要了解其余SDK的东西。包括接入配置等。对于第三方平台的常量,不管是SDK自身还是最终的使用者,其实不会再调整但是SDK怎么处理还是很重要。不封装,开发者还需要关注周边SDK的内容,显得你不够专业;封装的话,万一第三方SDK有调整你也要调整(不要说不可能,我们就亲身经历了一个第三方SDK把常量名称和值都修改了的例子)。
为了防止上面的情况,建议封装第三方平台的常量的时候不要使用对方的常量值,而是直接使用对方的变量来赋值。例如:
public final static int eXXX = otherPlatform.eXXX;
对于第三方平台的各种配置,比如appid等,最好是仅仅用于第三方平台的逻辑中,不要为了省事把第三方平台的配置用于自己的业务逻辑
这个其实根据平台的自身需要了,建议既然是SDK,就是一个平台,还是有自己应用标识比较好(appid,appkey),虽然一开始用不到,但是有没有坏处。不要依赖第三方平台的一些应用配置信息做自己的逻辑。
再来一个赤裸裸的例子吧。我们最开始为了方便自己的一些前后台交互用了某个第三方平台的配置字段,后来接入另一个第三方平台,为了使用方便,我们经过协商也用相同的配置字段。嗯,感觉良好,然后有一天,有一个应用只接入第二个平台而不接入第一个平台,我们第一次懵逼了,但是当时偷懒了,没有根本解决这个问题,而是选择虽然不接入第一个平台,也去第一个平台配置这个字段~~~这就已经很傻了,还不赶紧改,第二波就来了,第一个平台忽然调整了那个配置字段,弃用了,换了新的字段来标示,同时值也变化了(他们做了兼容,可以同时支持新旧字段),但是第二个平台就瞬间跪了,只能二选一,用老的值,新版本有问题,用新的值,老版本有问题,瞬间煞笔了~~
这里主要说一些关于模块开关,平台配置相关的内容。主要涉及到配置文件的处理和下发。这部分内容主要探讨一下吧,还没有很好的结果。
不管是模块的开关还是接口的权限,都应该可以后台控制。当然前台最好也要有配置文件,可以减少一些无用的请求。而且在后台不能控制的时候,前台的开关还是很有必要的。
在同时有前后台的开关或者配置的时候,记得优先使用后台的配置,不然你搞个后台开关有毛用(不多说,亲身经历。)
目前我们项目的配置文件放在assert目录下,目前遇到的问题是我们云端下发配置文件的时候比较麻烦。这里就出现另一个问题,云端下发是下发配置文件还是下发配置开关。如果下发配置开关,那就放在什么位置都可以,如果下发配置文件,配置文件下发在什么位置就很重要。
所有的配置文件用key-value的方式来保存
所有的配置项的Key建议增加统一的前缀,例如MSDK_
。当然我们目前的没有增加,所以就显得很混乱。别学我们。