当前位置: 首页 > 科技观察

谈谈AndroidSDK开发经验

时间:2023-03-19 02:14:33 科技观察

本人在公司做了两年多的SDK开发,结合??自己的所学所学,分享一些SDK开发的经验。一、什么是SDK相信做Android开发的朋友一定用过第三方SDK,比如推送SDK、分享SDK等,SDK的全称是SoftwareDevelopmentKit,译为“软件开发工具包”.SDK通常是为了辅助某类软件的开发而编写的特定软件包、框架集合等。SDK可以分为系统SDK和应用SDK。所谓系统SDK,就是为使用特定的软件框架和硬件平台而开发的工具集合。应用SDK是基于系统SDK开发的一套独立于具体业务、具有特定功能的工具。SDK的使用对象主要是B端客户,最终交付的产品为代码、样例、文档。客户接入SDK也是一个与SDK提供商沟通的过程。对外沟通的成本高于内部沟通,遇到的问题也会更严重。许多。所以SDK开发对开发者的要求比应用开发更高。能开发出好的SDK,就一定能开发出好的应用,但是能开发出好的应用,却未必能开发出好的SDK。2.SDK实现目标SDK的实现目标可以概括为简单、稳定、高效。简洁对于用户来说,一个好的产品应该是简单易用的,不应该花太长时间让他们学习。SDK也应如此。它不应该有复杂繁琐的对接工作。用户只要阅读代码和文档,花一点时间,就可以很好地对接SDK。例如,当开发者需要使用SDK服务时,只需要在代码中增加一行即可。只需要一行代码就可以在项目中初始化SDK,开发者不需要关心内部已经处理好的GLContext,也不需要关心同步或者异步的问题。publicclassFURenderer{//Definepublicstaticvoidsetup(Contextcontext){//...}}//一行代码调用FURenderer.setup(context);稳定从SDK用户的角度,我们期望第三方SDK服务稳定高效,体现在提供稳定可靠的服务,而运行时性能应该是高效的。这就需要我们在设计和实现SDK时做到以下几点:对外提供稳定的API。SDK的API一旦确定,除非有特殊情况,否则不能更改,而且提供商更改API的成本非常高。对外提供稳定的业务。提供了稳定的API之后,必然有稳定的业务支撑。运行时的稳定性。确保SDK本身运行稳定,不能因为接入SDK导致宿主应用不稳定。版本稳定,更新中。SDK版本迭代非常缓慢,迭代过程应尽可能对用户屏蔽,避免不必要的适配成本。效率无论是普通应用开发还是SDK开发,都要考虑性能问题。SDK设计者应该关注以下问题:更少的内存使用。一般SDK和App运行在同一个进程中。这时候SDK就要对自己占用的内存进行管理,合理分配,注意释放。更少的内存抖动。在占用内存少的前提下,SDK设计者必须减少频繁GC带来的内存抖动。耗电量少。低功耗和高性能之间难以取舍,可以从CPU计算量和屏幕刷新帧率的角度考虑。3、SDK架构设计SDK架构实现决定了后续维护的难度,所以最好根据实际业务确定合适的方案。以项目中的模块化开发为例,谈谈架构设计的原则。遵循面向对象开发的几个原则,目的是实现三个目标:可维护性、可重用性和可扩展性。具体来说:按照单一职责原则,将系统拆分成多个小模块,每个模块保持相对独立,降低实现类的复杂度。根据接口隔离的原则,为每个模块定义一个契约接口。接口的粒度要小,功能要细。接口越小,越容易维护。模块之间通过协议或接口进行通信,避免直接相互依赖,降低耦合,相互了解最少,体现了迪米特定律。根据开闭原则定义各模块的公共行为,通过模板方法设计方式提供骨架实现,便于功能扩展。根据组合优于继承的原则,当多个模块的功能叠加时,采用类的组合来保证设计的灵活性。例如,项目第三方demo的功能模块借鉴了Java集合框架的架构,分为合约接口、抽象类、具体实现三部分。首先定义IEffectModule作为特效的契约接口,包括各个功能模块的创建、设置参数、销毁等公共操作。AbstractEffectModule作为IEffectModule的骨架实现,实现了常用的方法,定义了公共的成员变量。定义美颜IFaceBeautyModule接口,继承IEffectModule接口,包括额外的设置参数操作,FaceBeautyModule作为其实现类,继承AbstractEffectModule重用基类的代码。美体模块类似,先定义契约接口,再定义具体实现,接口之间相互隔离,接口内聚性强。FURenderer实现了IFURenderer渲染接口和IModuleManager模块管理接口,组合了各个功能模块。4、SDK设计规范API设计在任何开发中都是非常重要的,软件的好坏往往体现在API设计上。在正常的应用开发中,API只会在开发者之间流通,不会暴露给其他非开发者。但是SDK作为一种服务,需要将一部分API暴露给开发者,才能使用SDK的服务。以下是一些需要重点关注的原则。方法名称表明其用途。一个好的方法名最直观地表明了它的功能。该名称不言自明,不需要额外的文档。这将减少不必要的沟通成本。对于开发者来说,还有什么比直接阅读代码更直观的呢?按照《重构》这本书的说法,给每个变量起名字就像给自己的孩子起名字一样不过分。参数合法性检查如果程序在运行时出现异常,会破坏用户的体验,影响非常恶劣。我们采用“防御性编程”的思想来避免非法输入对系统的破坏性。当合法性检查失败时,不同的处理策略对应不同的方法权限:对于公共方法,显式检查是否抛出异常,并使用@throw说明抛出异常的原因对于私有方法,使用断言检查参数的合法性。检查构造函数参数的合法性,使对象处于统一状态。需要注意的是,如果检验成本过高,需要综合考虑。该方法仅实现一个功能。一个方法应该有一个单一的功能,做尽可能少和更专业的事情。这也是单一职责原则的体现。《阿里巴巴代码规范》规定一个方法不能超过80行,庞大的方法要拆分成更小的方法。另请注意,与其提供大而全面的方法,不如提供小而美的方法。大而全的方法往往变化频繁,风险可能性较大。因此,最好提供更小的方法进行组合使用。小而美的方法更容易实现代码重用。访问控制包括类方法和变量的权限。那些可以声明为私有的不应公开。外界知道的越少越好。静态方法可以声明为静态的。静态方法天然是线程安全的,将体现继承关系的方法用protected修饰,保证公共方法和变量安全可靠。避免太长的参数太长的参数会造成记忆困难,调用和传递参数时容易出错,所以要尽量避免。在无法避免参数过长的情况下,考虑其他的解决方案:通过使用Builder模式,通过将多个参数封装到一个类对象中来实现比如项目中有一个方法,参数比较多。intonDrawFrameSingleInput(byte[]img,intw,inth,intformat,byte[]readBackImg,intreadBackW,intreadBackH);重构后将参数封装成一个对象,调用方法只需要构造一个对象传入即可,避免了大量的参数。体验感。publicclassVideoFrame{privateintwidth;privateintheight;privatebyte[]data;privatebyte[]readback;privateintreadbackWidth;privateintreadbackHeight;privateintpixelFormat;//...}intonDrawFrameSingleInput(VideoFramevideoFrame);谨慎使用方法重载和滥用重载很容易让开发者感到困惑。重载方法时,您可以使用不同的方法名称。对于构造函数,可以使用静态工厂来代替重载。Java中提供的ObjectOutputStream类就是一个很好的例子:它的write对于每个基本类型都有一个变体,比如写出字符,写出boolean等等。设计者没有使用重载将其设计为write(Longl)、write(Booleanb),而是设计为writeLong(l)、writeBoolean(b)。比如项目对外的处理方法都是重载的,只能根据参数来区分,非常混乱。改成不同的方法名后,看到名字就知道要调用的方法,就清楚多了。//重构前intonDrawFrame(byte[]img,inttex,intw,inth);intonDrawFrame(byte[]img,intw,inth);//重构后intonDrawFrameDualInput(byte[]img,inttex,intw,inth);intonDrawFrameSingleInput(byte[]img,intw,inth,intformat);避免直接返回null的方法对于需要返回数组或集合的方法,不要返回null。比如我们去糕点店买面包,面包没有了是正常状态,所以我们不应该返回null,而是返回一个长度为0的数组或者集合。Java提供了Collections.emptyXXX()来表示一个空集合。避免引入第三方库GitHub有很多开源的第三方库,比如网络请求OkHttp、图片加载Glide等,但是在SDK开发中,要遵循的基本原则是:Minimumusabilityprinciple,即使用最少的代码,如果没有必要不要添加实体。最少依赖原则,即使用最少的外部依赖,非必要不增加依赖。引入第三方库可能会导致以下问题:宿主应用的第三方库和SDK依赖的版本不一致,容易造成冲突,增加对接成本。开源库不断更新,SDK也要及时更新,增加额外的维护工作量。由于开源库的引入,问题排查困难。保证兼容性SDK不断迭代,每个版本都会有新功能和错误修复。对于用户来说,升级后的版本应该不会有太大的变化。一般直接替换库文件或者修改远程依赖库的版本号即可。避免直接重命名公共接口。如果旧接口被弃用,则必须用@Deprecated关键字标记,并给出替代和弃用时间。要减少入侵,我们必须确保更少的代码入侵。主要是在对外提供服务的时候,充分考虑开发者的使用场景,设计出优秀的API。一套好的API在定义时应该满足大多数开发者的期望——语义应该易于理解,使用应该简单可靠。具体表现为正常情况下能稳定可靠地运行,异常情况下能及时反馈错误信息。比如在使用Gradle下载依赖库时,AAR包中存在不需要的bundle资源。我们在打包apk时提供构建配置,您可以自由选择打包的bundle,减少对宿主应用的侵入。applicationVariants.all{variant->variant.mergeAssetsProvider.configure{doLast{delete(fileTree(dir:outputDir,//删除不需要的bundle文件includes:['model/ai_face_processor_lite.bundle','model/ai_gesture.bundle','graphics/controller.bundle','graphics/tongue.bundle']))}}}5.SDK交付包Android平台通常使用jar和aar来发布SDK,不同的是jar只包含代码,aar可以包含代码、资源和动态库。一般来说,aar是最合适的交付方式。上传到maven服务器,用户一行代码即可集成。对于需要灵活定制的客户,我们也会提供SDK的源码。缺点是升级困难,需要改很多代码。对于代码混淆,公共接口和native使用的接口不能混用,可以混淆内部实现细节,减少SDK包的体积。接入文档接入文档用于告诉SDK用户如何使用SDK、详细步骤和可能出现的问题。文档内容包括:更新记录、基本信息、API说明、集成步骤、FAQ等。好的文档的标准是清晰易懂。完全不懂SDK的开发者看文档就可以连接,经常遇到的问题一一列出,专业术语也有相应的解释。Demo实例集成Demo通常是一个简单的app,用于展示如何快速连接SDK。Demo的源代码托管在GitHub上,方便用户参考。其版本变更政策与SDK版本变更一致。虽然是Demo,但其开发原则要与SDK保持一致,以保证高质量交付。