sdk开发经验总结-程序员宅基地

技术标签: java  android  kotlin  sdk  


博主之前做过一些 sdk 的开发,也对接过一些 sdk ,有一些 sdk 设计得非常优秀,也有非常糟糕的。
sdk 是给别人使用的,所以在开发 sdk 的过程当中,我们始终要提醒自己站在使用者的角度去考虑问题。

1. 降低接入成本

1.1 接入简单

Android 推荐使用 maven 仓库,通过 gradle 进行依赖。Ios 使用 pod 库。sdk 对外屏蔽内部的实现,只提供接口即可。

good case
通过 maven 仓库管理 sdk

maven {
    
    url "https://xxx.xxx.xxx/xxx/xxx"
}
implementation 'com.xxx.xxx:xxx:1.0.0'

bad case
之前接手过一个护眼 sdk,模块并没有良好的封装,而是直接将源码和工程提供给了业务方,导致维护起来特别麻烦,出现问题,业务方将问题抛给 sdk,sdk 觉得是业务方修改了他们的源码,另外,更新版本也特别麻烦,需要源码对比,然后手动将 diff 应用,体验极差。

1.2 文档和 demo

文档和笔记最重要的区别就是,文档是给别人看的,而笔记是给自己看的。
很多时候阅读文档的人并不了解相关的上下文,所以在文档当中最好要先介绍一下相关的背景,以及提供详细的示例。
另外文档更新要及时,当某个接口更新,需要及时的更新对应的文档,避免出现文档和代码不一致的情况。

good case
微信小程序官方文档
每篇文档均有详细的说明和代码示例,一目了然

bad case
如下所示为我之前对接过的一个后台接口文档,每个接口都没有参数和返回值的说明和例子,文档看了让人觉得很疑惑,可能只有开发者自己能看懂,看完之后还需要再次找对应的开发沟通和确认。
在这里插入图片描述

1.3 api接口设计

api 接口设计应当注意以下几点:

1. 接口保持精简,不要提供过多的接口
2. 每个接口均应当提供详细的接口说明

包括参数和返回值,以及接口的使用示例,
例如,以下为某个生命周期的接口文档,每个生命周期的接口均有详细的描述,并提供使用示例。

接口 参数 备注
onDownloading int total,int downloaded 下载进度
onDownloaded 下载结束
onMerged 合并结束,此时会调用xxx接口
onUpdated 更新完成
UpdateListener updateListener = new SimpleUpdateListener();
HotFix.registerListener(updateListener);//注册生命周期监听
HotFix.unRegisterListener(updateListener);//反注册生命周期监听         
3. 接口参数不应过多

个人觉得当参数超过5个时,就应当要考虑将参数封装成一个数据类
good case
上报接口,参数简洁明了

void report(String event, Map<String, String> params);

bad case
参数过多,应当将过多的参数封装成数据类

public static void report(final Config appConfig, final int eventType, final String page,
        final String info, final String cmdName,
        final int cmdRetCode, final String appType, final long cost, final String httpRequestUrl,
        final long timestamp,
        final String reserves1, final String reserves2, final String reserves3, final String reserves4,
        final String renderMode)
4. 接口方法过多,应当提供默认实现

以下是一个生命周期的接口:

public interface Listener {
    

    void onStartRequest();

    void onFinishRequest();

    void onStartDownload();

    void onDownloading(int total,int process);

    void onDownloadResult(boolean result,String msg);

    void onError(int code, String msg);

}

如果我们不做处理,那么用户在使用的时候就会变成下面这样,可能用户只关心 onError 和 onDownloadResult 两个方法,但是却需要复写所有的方法。

sdk.registerListener(new Listener() {
    
    @Override
    public void onStartRequest() {
    

    }

    @Override
    public void onStartDownload() {
    

    }
    
    ......

    @Override
    public void onDownloadResult(boolean result,String msg) {
    
        
    }

    @Override
    public void onError(int code, String msg) {
    
        
    }
});

针对这种情况,我们应当提供一个默认的实现,这样使用者只需复写他关心的方法即可。

public class SimpleListener implements Listener {
    

    @Override
    public void onStartRequest() {
    

    }

    @Override
    public void onStartDownload() {
    

    }

    @Override
    public void onDownloading(int total,int process) {
    

    }

    ......

    @Override
    public void onError(int code, String msg) {
    

    }
}

//使用方只需要复写自己关心的方法即可
sdk.registerListener(new SimpleListener() {
    

    @Override
    public void onDownloadResult(boolean result,String msg) {
    
        
    }

    @Override
    public void onError(int code, String msg) {
    
        
    }
});
5. 接口尽可能和系统或者业内标准保持一致

例如,以下是博主之前日志组件的一个接口

public static <T> void i(String tag, T obj, Throwable tr) {
    
    instance.log(LogLevel.INFO, tag, obj, tr);
}

public static <T> void i(String tag, T obj) {
    
    instance.log(LogLevel.INFO, tag, obj);
}

Android 系统的日志接口

public static int i(String tag, String msg) {
    
    return println(LOG_ID_MAIN, INFO, tag, msg);
}

public static int i(String tag, String msg, Throwable tr) {
    
    return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}

可以看到,日志组件的接口在原有系统接口上做到兼容和增强,
其中第二个参数为范型,可以根据类型自动匹配到 sdk 内部的 Formatter,例如 JsonFormatter,ArrayFormatter以及用户自定义的 Formatter,然后将日志格式化输出。
这样做的好处就是,使用方可以快速接入,并且对于接口几乎没有理解成本。

1.4 向后兼容

新版本应当做到和旧版本兼容,这样用户在进行升级的时候,只需要修改 gradle 文件里面的版本号即可。
版本号的命名规则可以使用 x.x.x,
第三位表示 bugfix,第二位表示新增了一些 feature,第一位表示发生 breaking changes。
这样我们就可以通过版本号可以清楚的知道哪些版本是兼容的。
例如发布3.1.1,我们就知道这是基于3.1.0做了一些bugfix;如果发布4.0.0,那么就与3.1.0可能不兼容了。

2.稳定性

稳定性是 sdk 的基础,没人愿意使用不稳定的产品。

2.1 plan B

在 sdk 设计方案和接入的场景,我们可以考虑一下 plan B。

场景 方案 例子
sdk 接入 增加配置开关,屏蔽sdk 之前做的日志组件 sdk,是为了替代原来的日志组件,我们可以在第一个版本内保留两个日志组件,然后通过配置开关来设置使用哪个日志组件。
默认使用新的日志组件,上线之后一旦观察到有异常,可以下发配置切换到原有的日志组件,以此来兜底。
sdk 设计方案 实现方案考虑兜底逻辑 在实现热更新 sdk 时,我们每次进行热更新之后,都会在本地保留一个历史版本,当发生异常时,可以回滚到旧版本,以此来保证用户体验。
2.2 监控

这里同样可以将监控区分为业务层和 sdk 内部,业务层的监控包括 crash 率监控,性能监控,这种我们可以接入一些专业的监控 sdk 来实现,
内部监控为 sdk 本身的业务监控。以热更新组件为例,
在热更新的场景,我们会进行以下几个监控步骤:

  1. 热更新成功和失败上报
  2. 热更新失败触发日志上传
  3. 上报达到阈值,触发告警
  4. 本地在热更新完成之后会进行异常监控,达到条件,触发回滚
  5. 回滚上报并触发日志上传
2.3 错误指引

sdk 的 onError 接口应当返回明确的错误码和错误信息,并且在文档当中每种错误信息均需要给出对应的说明。
我比较偏向于分类错误码的方式,这种方式比较方便后台统计和分类。
以之前做的插件化 sdk 的错误码为例,
我们将错误码分成了4类:

1xx:后台相关的错误码
2xx:下载相关的错误码
3xx:bundle加载相关的错误码
4xx:其他错误码

每一个类别下面有详细的错误类别,比如201:文件下载失败,202:文件MD5校验失败,203:文件解压失败。

3.其他

3.1 易扩展

一个好的sdk应当具有良好的扩展性,以日志组件为例,
我们将日志输出抽象成了一个 Printer 接口,在 sdk 内部默认实现了将日志输出到控制台,输出到文件。

public interface Printer {
    
    void print(LogItem item);
}

使用者可以实现这个接口,然后可以很方便的将日志输出任何地方,比如输出到网络,输出到其他进程。

3.2 关注性能

如果sdk涉及到性能,那么在 sdk 正式发布时,需要先进行一下性能测试,主要包括 cpu 峰值,内存占用率,以及和具体业务相关的维度。
以日志组件为例,在正式发布之前,我做了如下测试:
开启5个线程不间断的瞬间写入10w 条 50byte 的日志,同时观察写入完成的时间,日志大小,cpu 峰值以及内存占用的情况。
以下为部分实现结果,使用三星 s20 机器测试,供参考

维度 不压缩不加密 压缩加密
时间 269.2ms 276.6ms
文件大小 12.3MB 851kb
内存占用峰值增长 23MB 28MB
cpu峰值 12% 12%
3.3 鉴权

鉴权并不是每个 sdk 都会遇到的问题,主要涉及到和后台交互较多,并且有安全方面考量的场景就需要考虑到鉴权。

3.4 合规

合规是一个非常重要的问题,在之前的项目组,隐私合规问题也是由我来跟进,作为业务方,最麻烦的就是 sdk 出现隐私合规问题,这样就需要推动 sdk 方去更改,解决问题的链路就会被拉长,
更可怕的是,有时候还会遇到有一些 sdk 已经找不到维护的团队了,就必须使用一些特殊的手段来实现合规。

其实做到合规也不难,主要是对于一些隐私字段的获取,比如 AndroidId ,IMEI ,手机型号等。上报是隐私合规的重灾区,后台为了确定唯一性或者用户画像,通常需要 sdk 提供一些涉及隐私的字段。

如果实在需要这些字段,sdk 可以提供获取隐私字段的接口,让业务方去实现这些接口,以此来避免 sdk 的隐私合规问题以及获取隐私字段的频率问题。

good case
让业务方传入隐私字段,或者提供隐私接口让业务方实现

void init(String appid,String imei)

//或者
interface Privacy {
    
    public String getImei()
}

bad case
在 sdk 内部直接访问隐私字段

void report(){
    
    ...
    String device = Build.DEVICE;
    ...
}
3.5 sdk接入标准化

作为业务方,曾经接入过非常糟糕的sdk,体验非常差,至今回想起来都会觉得恶心,所以当时就一直想要尝试建立sdk接入的标准化,只有达到我们标准的sdk才能够接入,具体包括以下几个维度:

维度 备注
包大小增量 不同的团队的标准不一致
性能 对比接入前和接入后版本的cpu,内存,FPS等指标,若差值达到约定的阈值,团队内部应和sdk讨论性能问题以及是否将其下架
接入成本 必须要有良好的封装,使用 maven 库或者提供 aar,jar 包等,不应直接提供源码
稳定性 在灰度以及正式发版时,观察 crash 率,看是否有 crash 是由新的 sdk 引起,若超过一定的比例,考虑将其下架

4. 总结

作为业务方,我对接过非常优秀的 sdk 也对接过非常糟糕的 sdk,遇到糟糕的 sdk,内心一万匹草泥马呼啸而过。所以当自己作为 sdk 的开发者时,应当换位思考一下,如果我要接入这个 sdk,我希望这个 sdk 是什么样的,其实无非就是要有详细的说明文档,简单的接入方式,清晰的 API 接口以及一个简单的 demo,要求其实并不高。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hbdatouerzi/article/details/124439273

智能推荐

HBase数据大批量导入方式总结和对比_hbase 大数据量导入-程序员宅基地

文章浏览阅读3.4k次,点赞4次,收藏19次。HBase数据导入1. 背景在实际生产中,海量数据一般都不是直接存储在HBase中,这时候就需要一个数据导入到HBase的步骤上一篇博客讲述了可以通过java api的方式或者shell 客户端方式导入或者创建数据,但这对于实际生产中海量数据导入来说,速度和效率都太慢了,所以我们需要使用其他方式来解决海量输入导入到HBase的问题利用HBase底层文件是HFile形式存储再HDFS中,所以如果能够直接生成HFile的话,这时候再让HBase从HFile中读取数据,就会快很多。2. 批量数据导入_hbase 大数据量导入

Qt 串口通信之使用16进制发送数据的转换方式_qt串口发送16进制数据-程序员宅基地

文章浏览阅读1.7k次,点赞2次,收藏18次。16进制QString转成16进制QByteArray一 概述 有时候在做上位机串口通讯时,经常需要将字符串转成16进制的形式作为发送,借此分析记录一下。二 需求分析//假设需要转换的字符:如下QString str = "abcdef1234";//需求转换成 0xab,0xcd,0xef,0x12,0x34 由上图分析得出,很明显我们只需要拆分字符串然后再重新合并就ok啦,知道了解决方法,接下来就是上代码。三 编写代码方法1:/*******************_qt串口发送16进制数据

multipartfile上传文件 大小限制_multipartfile 大小限制-程序员宅基地

文章浏览阅读1.4w次,点赞7次,收藏15次。关于文件上传大小 主要看一个错误org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (59500387) exceeds the configured maximum (10485760) at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.ini_multipartfile 大小限制

基于python的信用卡评分模型_python 信用 评分卡模型-程序员宅基地

文章浏览阅读4.4w次,点赞45次,收藏418次。基于python的信用卡评分模型1. 项目背景介绍1.1 信用风险和评分卡模型的基本概念 信用风险指的是交易对手未能履行约定合同中的义务造成经济损失的风险,即受信人不能履行还本付息的责任而使授信人的预期收益与实际收益发生偏离的可能性,它是金融风险的主要类型。 借贷场景中的评分卡是一种以分数的形式来衡量风险几率的一种手段,也是对未来一段时间内违约、逾期、失联概率的预测。一般来说..._python 信用 评分卡模型

linux 下 tcpdump 详解 前篇(libpcap库源码分析)_libcap 源码-程序员宅基地

文章浏览阅读1.7k次,点赞3次,收藏22次。一 概述用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具。 至于tcpdump参数如何使用,这不是本章讨论的重点。liunx系统抓包工具,毫无疑问就是tcpdump。而windows的抓包工具,wireshark也是一款主流的抓包工具。wireshark 使用了winpcap库。tcpdump..._libcap 源码

http://mirrors.aliyun.com/epel/6/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 --程序员宅基地

文章浏览阅读6.5k次,点赞14次,收藏11次。http://mirrors.aliyun.com/epel/6/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 - “The requested URL returned error: 404 Not Found”Trying other mirror.Error: Cannot retrieve repository metadata (repomd.xml) for repository: epel. Please verify its_/epel/6/x86_64/repodata/repomd.xml: [errno 14] pycurl error 22 - "the reques

随便推点

spaCy V3.0 基于规则匹配(3)----基于规则的命名实体识别NER_spacy ner-程序员宅基地

文章浏览阅读2.2k次,点赞4次,收藏14次。EntityRuler是一个spaCy管道组件,可以通过基于patterns字典添加命名实体,能够方便基于规则和统计方式的命名实体识别方法相结合,从而实现功能更强大的spaCy管道。_spacy ner

悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(多重背包)-程序员宅基地

文章浏览阅读105次。急!灾区的食物依然短缺!为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:你用有限的资金最多能采购多少公斤粮食呢?后记:人生是一个充满了变数的生命过程,天灾、人祸、病痛是我们生命历程中不可预知的威胁。月有阴晴圆缺,人有旦夕祸福,未来对于我们而言是一个未知数。那么,我们要做的就应该是珍惜现在,感恩生活——感谢父母,他们给予我们生命,抚养我们成人;感谢老师,他们授给我们知识,.

Android源码设计模式探索与实战【面向对象六大基本原则】_android源码设计模式第2版 csdn-程序员宅基地

文章浏览阅读2.9k次,点赞43次,收藏40次。IT行业,一直讲一句话,拼到最后都拼的是“内功”,而内功往往就是指我们处理问题的思路、经验、想法,而对于开发者来说,甚至对于产品也一样,都离不开一个“宝典”,就是设计模式。今天我们一起借助Android源码去探索一下设计的六大基本原则。同时结合我工作经验中的两个例子,来总结实践一下。1.背景&定义定义:设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。理解:设计模式是什么?设计模式(Desi._android源码设计模式第2版 csdn

这台计算机似乎没有安装操作系统_前沿科技 | 浙江大学科学家联合之江实验室成功研制全球神经元规模最大的类脑计算机...-程序员宅基地

文章浏览阅读172次。1.6米高的三个标准机柜并排而立,黑色的外壳给人酷酷的感觉,红色的信号灯不停地闪烁,靠得近些似乎能听到里面脉冲信号飞速奔跑的声音。近日,浙江大学科学家团队联合之江实验室共同研制成功了我国首台基于自主知识产权类脑芯片的类脑计算机(Darwin Mouse)。这台类脑计算机包含792颗浙江大学研制的达尔文2代类脑芯片,支持1.2亿脉冲神经元、近千亿神经突触,与小鼠大脑神经元数量规模相当,典型..._浙江大学 类脑 方向

实现RTSP摄像机进行网页直播和微信直播的技术方案:EasyNVR版本免费更新方法_easynvr免费版-程序员宅基地

文章浏览阅读2.7k次。问题背景前文我们提过为保障服务器正常稳定运作,EasyNVR有专业的运维(售前支撑、商务咨询、售后维护)团队,随时对客户各种突发情况快速响应处理,保证互联网直播的顺利进行。这部分工作就包括技术问题咨询、需求分析、方案制定、版本更新、功能提升等,随着用户基数的增加,运维过程中或多或少存在一些回复延迟,主要包括以下几个方面:EasyNVR的用户越来越多,技术人员一一对应解答效率不高;随着Eas..._easynvr免费版

P1541 [NOIP2010 提高组] 乌龟棋 题解_乌龟棋2010-程序员宅基地

文章浏览阅读401次,点赞3次,收藏4次。更好的阅读体验蒟蒻的第一篇题解P1541 [NOIP2010 提高组] 乌龟棋简单的背包 首先确定状态,dp[a][b][c][d]用来存储使用a张爬行卡片1,b张爬行卡片2,c张爬行卡片3,d张爬行卡片4时的最大得分。 我们需要开一个桶的数组t存4种牌的个数,以便于暴力。 dp数组初始化。很显然,四种卡片都用0张时,在起点,分数为score[1] 即: dp[0][0][0][0]=score[1]; 状态转移。DP 4种卡片的个数,状态转移方程为_乌龟棋2010