一款稳定、易用、安全、小巧灵活的推送SDK是怎么样的?本文将从“小”、“稳”、“好用”以及“安全”四个角度来具体阐述。
对于非技术出身的产品经理来说,如果突然接到一个要“设计SDK”的活儿,其实并不容易。毕竟,SDK是主要面向开发者的,更像一个toD产品。那么,产品经理在设计SDK时,需要注意哪些点呢?换句话说,一款好的SDK应该具备哪些特性?本文将从“小”、“稳”、“好用”以及“安全”四个角度来具体阐述。
1. 小
1.1 65535限制
我们以一款好的推送SDK为例,那么首要需考虑到SDK包体的小巧灵活性。
为什么选择更小体积的包体?
对于商务人员来说,包体体积小,他们更容易接受。对于技术人员来说,他们在开发产品时,普遍追求“代码少、功能全”,这是来自程序员的代码洁癖。
那么从代码层面来看,是因为系统有“65535限制”。
如上图(左)所示,程序最终会生成dex文件,dex文件主要由以下几部分组成:header(标头)、一连串的ids(标识符列表)、data(数据区)以及link_data(静态链接文件中使用的数据)。
细看上图(右),它包含了一个method_ids_size字段,该字段的主要作用是定义个数。根据谷歌的定义,uint是一个16位的short类型,最长长度是65535。如果将dex工程反编译,会生成很多smali的文件,再去看smali里的函数调用(比如invoke direct {***} 函数名@BBB),会发现调用的地址其实就是刚才unit里定义的偏移量计算得出的。因此,这个函数地址最多也只能有65535个。
1.2 如何使包体体积变小
如何减小包体的体积,建议从以下几个方面考虑:
(1)自研,不嵌套
在设计研发SDK时,不建议在SDK内嵌套一系列框架,例如三方网络框架、db框架或任务调度框架等。我们主张选择最核心的一部分进行自主研发。
(2)代码优化
从算法层面,在效果相同的情况下,可适当减少代码的行数;对于有默认赋值的变量不需要进行初始化赋值;选择合适的字符串拼接方式,建议使用StringBuilder方法拼接字符串,可以解决字符串频繁修改带来的内存消耗,也有利于减少包体体积大小。
(3)追求实用,放弃完美
SDK包体应当追求实用性,以完善主功能为主,其他相对次要的部分可以适当减少时间或精力投入,放弃完美主义思维。
(4)代码混淆
借助代码混淆实现更小体积的包体,且不易被逆向。
1.3 省电省流量
省电省流量是“小”的另一个方面。SDK如果没有对流量和电量有严格的限制,否则会出现手机发烫、高耗电提醒、流量浪费、内置SDK APP难以上架等问题。
针对上述问题,我们可以设置通过Lock杀手,智能心跳、自定义协议、链路合并、按需活跃等方式尽可能地降低SDK对电量以及流量造成的消耗。
(1)Lock杀手:代码中WiFiLock、WakeLock等会强制唤醒APP,导致APP产生较大耗电量。在不影响功能的前提下,我们应尽量减少或者不用该类锁。
(2)智能心跳:应根据不同的运营商、网络状态等,选择不同的心跳策略,并且根据不同的应用场景探索心跳的最大边界,尽量延长心跳周期,减少电量和网络的消耗。
(3)自定义协议:市场上常用的json、xml、甚至PB协议,都有比较好的兼容扩展性,但同样也带来了空间浪费的问题,自定义协议可以充分利用空间,精确利用每一个byte甚至bit,极简化封装,承载最大的信息量,减少流量和电量浪费。
(4)链路合并:当一个设备有多个APP的推送链路同时活跃时,我们会运用合并链路技术,将使用同一款SDK的 APP 之间的长连接链路进行合并,减少流量电量的浪费。
2. 稳
2.1 提升稳定性
在设计SDK时,还要考虑到SDK的使用稳定性。否则在实际应用中,可能会遇到ANR、OOM、Crash、内存泄露、闪退等棘手的问题,我们需要通过持续的迭代和优化来将错误最小化。
(1)做好代码管理
除了借助SVN、GIT等工具做好代码托管外,还需遵循一定的代码规范,借助类似gerrit等工具进行代码review,使用verify流程。在保证机器找不到问题的前提下,再用人眼去辨别是否符合业务逻辑。
(2)自动化测试
自动化测试可以大幅提升回归测试的效率,非常适合敏捷的开发过程。此外,自动化测试可以替代大量的手工机械重复性操作,测试工程师可以把更多的时间花在更全面的用例设计和新功能测试上。
(3)运用代码模块化等小技巧
代码模块化能以最少的模块、零部件,更快速地满足更多的个性化需求。异常处理可以提高系统的容错性,让程序更加稳定。代码检测能及时发现程序中的缺陷和错误,比如检测内存是否泄露,是否有安全漏洞等,保证代码质量。
(4)线上灰度
实际场景中,我们很难去覆盖所有的环境,例如机型、网络等,需要通过线上用户的反馈去验证代码的健壮性。因此在产品大规模推向用户之前,我们需要进行少量的真实用户测试,即灰度上线来帮助减少风险。
(5)日志系统
系统有问题是必然的,在尽量保持系统稳定的前提下,要考虑容错性。当问题发生时,需要第一时间以最快的速度排查,因此需要有一套完整的日志系统。此外,平时我们也可以通过日志系统的拨测检测系统的健壮性,可以在用户反馈之前及时发现并解决问题。
2.2 兼容性
兼容性也是保证SDK稳定性的一个重要条件,主要考虑以下几个方面:
(1)接口兼容
每次版本更新后,对外接口要尽可能保持不变。对于改动较大的接口,可以使用 @Deprecated 注解对老接口进行标记,并且做新接口调用的兼容,而不是直接删除老接口。
(2)主键兼容
当主键发生变更(例如去掉service、provider)时,部分老的安卓系统会有组件缓存,运行时直接告知“类”找不到。建议在AndroidManifest中保留声明,且对应“类”进行代码空实现,以减少发生crash的概率。
(3)安卓系统兼容
可使用Build.VERSION.SDK_INT做API区分。
(4)真机兼容
可以借助云测等平台进行兼容性测试。
3. 好用
SDK的易用性可以从下面几个方面考虑:
(1)接入简单
接入SDK时有集成demo直接可以运行,且接入文档清晰、步骤简单,最好能实现一键集成。
(2)保持核心优势
一款好的消息推送SDK,我们主要考虑及时性、到达率、稳定性和准确性。
例如:新闻媒体类APP对推送的及时性要求较高;通知类推送(如转账信息)会特别注重消息的到达率;稳定性指的是要保证推送SDK在不同环境下的正常运行,尤其是11.11等高并发场景;准确性主要针对广告营销类推送,需要在合适的时间、合适的地点和合适的场景把合适的内容推送给合适的人。其中,关于如何保证稳定性,可以从多线路、多IDC、热备份等角度考虑。
①多线路调整:例如预埋三线域名,做一些轮询策略,防止域名被劫持。
②多IDC设置:除了域名被劫持外,还可能遇到网络攻击,物理性损坏的情况。设置多IDC一方面是为了实现分流,另一方面也降低了风险。
③热备份:系统处于正常运转状态下的备份,一旦系统出现问题,可以快速恢复。
(3)多样化需求
通过丰富的画像标签,对用户进行场景化的智能推送,满足用户的多样化需求。
(4)策略可控
我们还提供静默时间、推送控量、短信补量、定时展示等附加功能,满足客户的实际使用场景。
4. 安全
SDK设计开发过程中,我们还需要注意安全性。安全性不仅仅代表网络数据交互的安全、本地数据存储的安全,也涉及到 SDK 的加固、混淆、第三方安全软件审核等。
其中,我们重点讲解SDK的加固。目前安卓平台SDK绝大部分都是Java语言编写,容易被反编译。SDK如果只是进行了简单的混淆,很容易被窥探到内部实现细节,此外还可能存在SDK被二次打包、植入恶意广告等现象。因此,我们需要对SDK进行加固,以提升安全性。
如上图所示,SDK的加固主要是Java层面和so层面的操作。Java层面可以进行SSR IR指令转换或者做Java2C处理,把实现细节放入native中;so层面可以做一些扁平化、虚假控制等来混淆代码,也可以通过指令替换、指令跳转逻辑来增加逆向难度。此外,也可以通过常量字符串加密加固SDK,这是目前较为简单实用的一种方式。
现在看来,要设计开发一款好的SDK最难的还是如何让自己设计的SDK在复杂的环境下稳定运行,这需要我们对 SDK 的架构有比较清晰的认知,并不断迭代和优化。
那么以上提到的四点,也是各位产品经理和开发者需要注意的,希望对大家有所帮助。
作者:叶新江,个推CTO
本文由 @个推 原创发布于人人都是产品经理,未经许可,禁止转载。
题图来自 Unsplash,基于 CC0 协议