美文网首页Android技术知识Android开发
Android进阶——SDK开发【学习+经验+注意事项】

Android进阶——SDK开发【学习+经验+注意事项】

作者: 谁动了我的代码 | 来源:发表于2022-12-26 16:00 被阅读0次

    SDK简介

    SDK全称 Software Development Kit,广义上的 SDK 是为特定的软件包、软件框架、硬件平台、操作系统等建立应用程序时所使用的开发工具的集合,狭义上的 SDK 则是基于系统 SDK 进行开发包装的、新的、独立的、能够完成特定功能并返回相关数据的一组工具的集合。

    简单来说:将一些业务逻辑独立出来,打包成jar、so、aar,暴露一些APIs给外部调用,也可以称为SDK。如推送SDK,支付SDK,地图SDK…

    jar和aar包区别:

    • JAR(Java Archive,Java 归档文件)是与平台无关的文件格式,它允许将许多文件组合成一个压缩文件。
    • Aar中Android库项目的二进制归档文件,包含所有资源,class以及res资源文件全部包含。

    SDK包开发流程:

    1)设计sdk 主要从:稳定性,性能,安全三方面考虑。 2)开发编码。 3)编写集成文档。

    安卓SDK开发注意点

    1)资源id命名

    Values中的colors、strings、styles中的id命名应当注意保持一定的唯一性(如:命名统一加项目前缀),避免与主项目中的资源id冲突,造成SDK中的资源被覆盖,如theme、string等等。

    2)资避免创建Application对象

    若SDK中定义了Application对象,而主项目也定义了Application或者应用了第三方Application,则需将android:name属性替换掉,才能正常 编译,因为一个App只能指定一个Application,若直接替换,则会造成SDK中的Application无法初始化。

    解决方案:

    1、避免在SDK中创建Application对象,暴露出一个初始化方法,在主项目的Application相关方法如onCreate()中注入相关参数执行; 2、若无法避免,则可指定主项目的Application继承自SDK中的内置Application,问题可以解决。

    3)无法将第三方库打包进aar

    library打包出来的 AAR ,不会将依赖的第三方库打包进去。

    解决方案:

    1、将AAR发布到远程仓库,这样gradle依赖下来的时候就会自动依赖第三方库了。 2、在主项目中显式指定SDK中的第三方依赖包,如常见的gson、okhttp等等…

    4)混淆问题

    默认情况下,proguard-rules.pro中的混淆配置是不会被打包进aar中的所以一般需要在主项目中手动指定混淆规则。

    解决方案:

    指定consumerProguardFiles属性,自定义引入的混淆规则,即可将*.pro文件打包进入aar中,项目打包时就会自动合并该配置文件。

    consumerProguardFiles 'proguard-rules.pro'
    

    如何保证提供的SDK安全

    1)添加混淆。 2)尽量少暴露接口/方法。

    如何二次打包(封装)AAR

    1)先将提供的aar解压到本地。 2)创建一个新的moudle,将classes.jar文件放到新的moudle。 3)将对应的解析文件放到对应的文件夹。 4)将jar包依赖到.gradle下。

    生成aar包

    image image

    2.9aar使用方式

    
     1.将打包出来的arr文件加入到libs中
     2.在module的build.gradle中与android{}平级下加入
           repositories {
              flatDir {
               dirs 'libs'
                   }
               }
      3.在module的build.gradle中的dependencies里加入
       implementation(name: 'sdk', ext: 'aar')//注意这里加入的名字没有后缀名
      4.同步后可以在External Libraries中查看新加入的包
    

    sdk开发经验总结

    降低接入成本

    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

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

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

    image

    编辑切换为居中

    添加图片注释,不超过 140 字(可选)

    1.3 api接口设计

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

    接口保持精简,不要提供过多的接口

    每个接口均应当提供详细的接口说明

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

    image
    UpdateListener updateListener = new SimpleUpdateListener();
    HotFix.registerListener(updateListener);//注册生命周期监听
    HotFix.unRegisterListener(updateListener);//反注册生命周期监听         
    

    接口参数不应过多

    个人觉得当参数超过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)
    

    接口方法过多,应当提供默认实现

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

    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) {
            
        }
    });
    
    

    接口尽可能和系统或者业内标准保持一致

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

    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。

    image

    2.2 监控

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

    • 热更新成功和失败上报
    • 热更新失败触发日志上传
    • 上报达到阈值,触发告警
    • 本地在热更新完成之后会进行异常监控,达到条件,触发回滚
    • 回滚上报并触发日志上传

    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 机器测试,供参考

    image

    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才能够接入,具体包括以下几个维度:

    image image

    以上就是有关Android开发中的,SDK开发的基本简介以及开发经验与注意注意事项。更多Android开发的核心技术可以参考《Android核心技术手册》点击即可查看。

    总结

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

    相关文章

      网友评论

        本文标题:Android进阶——SDK开发【学习+经验+注意事项】

        本文链接:https://www.haomeiwen.com/subject/wrdgqdtx.html