带你写SDK连载3之SDK的外观类

how to write a SDK

Posted by 宸笙 on June 30, 2018

SDK的外观类

在使用一些SDK或者三方库都会有一个外观类比如Bmob,AVOSCloud,或者ImageLoader,Retrofit,EventBus等,他们的职责基本是全局配置,具体有:

  1. 配置全局的AppKey或AppId;

    多见于SDK厂商,平台申请下发,用于唯一标识你的App,当然配置后SDK需要组好保存的操作比如加密保存防止泄露;

  2. 配置全局开关;

    比如配置全局Debug日志开关,开启后SDK即把请求过程中的数据打到Logcat中,或者如LeankCanary设置Debug/Release模式;

    其他全局开关,如PushSDK的startWork()方法,统计SDK设置上报域名或数据上报策略等;

  3. 其他可默认配置的信息;

    相对于Appkey的必须配置,SDK中的其他配置信息可以是默认配置的,开发者需要的时候再单独配置,满足定制化的需求,比如Bmob的一些关于文件上传快大小的配置,虽然一般是在一个配置类(参数传到Builder内部类)中,不过也单独放在这里;

     BmobConfig config =new BmobConfig.Builder(this)
         //设置appkey
         .setApplicationId("Your Application ID")
         //请求超时时间(单位为秒):默认15s
         .setConnectTimeout(30)
         //文件分片上传时每片的大小(单位字节),默认512*1024
         .setUploadBlockSize(1024*1024)
         //文件的过期时间(单位为秒):默认1800s
         .setFileExpiration(2500)
         .build();
         Bmob.initialize(config);
    

    类似的例子在OkHttpClient中也异曲同工;

  4. 获取单例;

    笔者在看到一个封装欠妥的SDK看到的,通过该类直接获取业务对象并调用业务方法,类似伪代码如下:

     public class CloudCard{
         // getInstance() 单例实现
            
         // resetDomain(String newDomain)
            
         // applyCard(Card card)
            
         // init()
            
         // getQrCode()
            
         // clean()
            
         // update(Card card)
            
         // ...
     }
    

    在笔者看来,SDK的外观类和业务对象应该抽离开,如果杂糅在一个类中,后续的业务方法新增会导致外观类频频修改,类的职责很不单一;同时,如果较为核心的业务方法较大程度暴露在改类中也较为危险,没有中间层不好隔离SDK内部修改保证对外的一致性和向下兼容;

  5. 封装SDK内部不同模块并提供统一的接口给开发者;

    这个其实就是外观模式的基本实现目的,SDK里面有一些不同模块,甚至是一些较细粒度的Api,这些Api不该直接裸露给开发者调用,不管是出于开发者很多时候仅仅需要一个较粗粒度的业务功能定义(比如启动生成云码但具体的内部交互其实开发者不用或者不想干预)还是出于尽量降低开发者使用SDK的心智负担的考虑;当然,如果层次较高的开发者想调一些较细粒度的Api那么SDK可以提供一些Hook钩子方法供开发者调用,SDK很多时候需要封装Api并对外提供接口,中间做的转化需要结合具体业务而定;

  6. 获取全局Context;

    上述几点可能很多用过厂商SDK开发者能很好理解,但是这一点可能你会突然觉得好像是,想起来了,但是不知具体功用,笔者的理解是:

    • Context在Android中的设计定位在于上帝组件,有着上帝视角,具体说就是系统的获取资源,服务等都能通过该组件得到;
    • SDK获取全局Context的目的可能有:

      方便调用Android其他组件,比如SDK内部也要用到Service,SharePerference做简单缓存等;

      一般SDK传Context的时机是初始化,比如在Application中的onCreate()方法中:

        Bmob.initialize(this, "Your Application ID");
      

      一般建议在Application中而不是Activity的onCreate()方法中调用初始化,因为Application的生命周期和整个应用是同步的,非全局的Context容易导致使用过程中为空导致的SDK报错,这点在做技术支持和工单系统中就屡见不鲜;

      有一个读者可能忽略的点,一些SDK中的异步回调方法都是帮开发者做好了线程切换(考虑到很多开发者拿到结果之后直接去更新UI和toast操作,至于为什么需要切换和子线程更新UI或toast等笔者不赘述),笔者可以验证一下;而之前看到一个知名SDK的实现是在SDK初始化时获取到主线程的Handler,后续用该Handler去做线程的切换,具体可以看下SDK回调方法在哪个线程;

简单实现

比如暂定为Cmob类,初步代码如下,较为简单:

public final class Cmob {

    /**
     * SDK初始化操作
     * @param context 全局Context
     * @param appKey 平台下发的appKey
     */
    public static void initialize(Context context,String appKey){
        // TODO:暂定操作
        // 1. 报错Context
        // 2. 加密保存appKey
    }


    /**
     * 初始化BmobSDK,允许设置BmobConfig
     *
     * @param config 配置类包含:超时时间、上传文件的分块大小及文件的超时时间等
     */
    public static void initialize(BmobConfig config) {
        // TODO:
    }
    
    

    // 一些获取全局配置信息的方法,其他同理:
    
    /**
     * 获取当前的上传文件分块大小
     *
     * @return
     */
    public static int getFileBlockSize() {
        // TODO:
    }

    /**
     * 数据迁移需要调用的重置域名方法
     * @param newDomain 新域名
     */
    public static void resetDomain(String newDomain){
        // TODO: 对URLManager中域名等信息的变更
    }


    

    /**
     * 获取当前配置的超时时间
     *
     * @return
     */
    public static long getConnectTimeout() {
        // TODO:
    }



    // 一些供内部调用的方法
    
    /**
     * 检查Context是否为空,并及早在开发阶段报错给开发者
     */
    private static void checkContext() {
        // TODO:
    }
    
    
    
    
}

在下一篇中,我将讲解数据基类CmobObject的建模和设计;