SDK 的封装与替换

1,016 阅读11分钟

前言

在项目中,引入三方SDK,如果不做封装,直接使用,三方SDK代码就会入侵项目代码:

%%{ init: { "theme": "base", "themeVariables": { "edgeLabelColor": "#2D2D2D" } } }%%
graph TD
    subgraph 项目
        A[业务模块 A] -->|直接调用| SDK
        B[业务模块 B] -->|直接调用| SDK
        C[业务模块 C] -->|直接调用| SDK
    end

    SDK[三方 SDK]

    classDef project fill:#50BFA0,stroke:#3A8578,color:white;
    classDef sdk fill:#B2EBF2,stroke:#8CD5CF,color:#2D2D2D;

    class A,B,C project
    class SDK sdk

    linkStyle 0,1,2 stroke:#FF5252,stroke-width:2px;

当三方SDK在项目中运行一段时间后,如果需要把它替换为另外一个SDK(三方或自研)。此时剥离它,就容易把项目改的面目全非:

%%{ init: { "theme": "base", "themeVariables": { "edgeLabelColor": "#2D2D2D" } } }%%
graph TD
    subgraph 项目
        A[业务模块 A] -->|直接调用| SDK[三方 SDK]
        B[业务模块 B] -->|或调用| SDK
        C[业务模块 C] -->|动态判断后调用| SDK_A
        B -->|动态判断后调用| SDK_A
        C -->|或调用| SDK
    end

    classDef module fill:#50BFA0,stroke:#3A8578,color:white;
    classDef sdk1 fill:#B2EBF2,stroke:#8CD5CF,color:#2D2D2D;
    classDef sdk2 fill:#FFCDD2,stroke:#E57373,color:#B71C1C;

    class A,B,C module
    class SDK sdk1
    class SDK_A,SDK_B sdk2

    linkStyle 0 stroke:#FF5252,stroke-width:2px
    linkStyle 1 stroke:#FF5252,stroke-width:2px
    linkStyle 2,3 stroke:#FFC107,stroke-width:2px,stroke-dasharray: 4,2

特别是在替换过程中,对 SDK 做 AB 实验时,需添加很多 if else 逻辑。

那么该如何封装和替换三方SDK呢?首先,SDK的封装和替换重点是:让业务层对SDK无感知,也就业务不关心具体使用的是哪一个SDK。然后开始:

  1. 第一步:业务选型,选择某个业务场景做实验;
  2. 第二步:SDK摸底,了解项目对SDK的依赖信息;
  3. 第三步:SDK封装与替换,按照业务对SDK无感知进行设计;
  4. 第四步:数据回收,关注线上质量。

业务选型

三方SDK在项目中运行并覆盖多个业务场景很长一段时间后,不能立马就全部替换掉,只能选择某个业务进行实验。

业务选型需要评估的核心要点有:

  • 研发成本:从UI操作和SDK功能评估;
  • 影响用户数:从PV和影响等级评估;
  • 开发节奏:分期实现,第一期,第二期,第X期等;
  • 灰度上线方案:业务特性维度,系统特性维度等;

研发成本,如:

场景UI 操作SDK功能
业务1全部
业务2全部
业务3部分

影响用户数,如:

场景PV影响等级
业务13w
业务210w
业务35w

开发节奏,如:

节奏实现目标
第一期SDK桥接层上线,支持三方和自研SDK的动态切换,保留原业务逻辑并使用原三方SDK
第二期业务X接入,覆盖业务X的所有链路节点,进行灰度
第三期推广覆盖其他业务,进行灰度,最后全量

灰度上线方案,如:

维度实现目标
系统特性根据系统版本,系统生产商等进行灰度
业务特性根据业务特殊情况进行灰度

SDK 摸底

要替换为其它SDK(三方或自研),需要提前回答问题:

  • 项目目前使用了三方SDK哪些能力?
  • 这些能力对应哪些API?
  • 这些API在项目中是如何被使用的?

针对上面的问题,期望能输出:项目依赖的三方 aar 信息,项目依赖的三方 so 信息,项目依赖的三方SDK api 信息。

项目依赖的三方 aar 信息

输出项目依赖的三方 aar 信息,目的是给其它SDK(三方或自研)参考,避免依赖冲突,避免构建问题。

在命令行执行 ./gradlew :app:dependencies --configuration releaseRuntimeClassPath > dependencies.txt

> Task :app:dependencies

------------------------------------------------------------
Project ':app'
------------------------------------------------------------

releaseRuntimeClasspath - Runtime classpath of compilation 'release' (target  (androidJvm)).
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21
|    |    +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21
|    |    \--- org.jetbrains:annotations:13.0
|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21
|         \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.21 (*)
+--- androidx.core:core-ktx:1.7.0
+--- com.squareup.okhttp3:okhttp:3.12.13.18 (*)
//.....省略

如果有需要,可格式化信息后输出,如:

依赖的aar
com.squareup.okhttp3:okhttp:4.12.0
其他

项目依赖的三方 so 信息

输出项目依赖的三方 so 信息,目的也是给其它SDK(三方或自研)参考,避免依赖冲突,避免构建问题。

通过自定义 gradle task,在 app 下的 build.gradle 中添加 Task:app:mergeDebugNativeLibs(或 mergeReleaseNativeLibs) 相关监听:

project.afterEvaluate {
    project.android.applicationVariants.all { variant ->
        //获取构建变体的名称
        logger.lifecycle("${variant.name}")
        def variantName = variant.name
        def name = String.valueOf(variantName.charAt(0)).toUpperCase() + variantName.substring(1)
        def mergeNativeLibsTask = project.tasks.getByName("merge${name}NativeLibs")
        mergeNativeLibsTask.doLast { task ->
            logger.lifecycle("project native libs:")
            //当前项目相关的 so 文件列表
            logger.lifecycle("${task.projectNativeLibs.getFiles()}")
            logger.lifecycle("------")
            //子项目相关的 so 文件列表
            logger.lifecycle("sub project native libs:")
            logger.lifecycle("${task.subProjectNativeLibs.getFiles()}")
            logger.lifecycle("------")
            //三方库相关的 so 文件列表
            logger.lifecycle("external project native libs:")
            logger.lifecycle("${task.externalLibNativeLibs.getFiles()}")
        }
    }
}

在命令行执行 ./gradlew app:assembleDebug./gradlew app:assembleRelease的时候,会输出依赖的 so 信息。如果有需要,可格式化信息后输出,如:

名称abiso
android-database-sqlcipher-3.5.9armeabi-v7alibsqlcipher.so
x86libsqlcipher.so
arm64-v8alibsqlcipher.so
其他armeabi-v7ax.so
x86x.so
arm64-v8ax.so

项目依赖的三方SDK api 信息

输出项目依赖的三方SDK api 信息,目的是:

  1. 通过这些 api 输出 SDK 原子能力,让其它SDK(三方或自研)实现这些原子能力,并提供与当前SDK具有相同功能的 api 。
  2. 通过这些 api 信息,配合 SDK 封装规则,自动化生成SDK接口层和接口实现层代码。
  3. 了解对三方 SDK api 的依赖情况,方便估时和任务拆解。

这里采用之前开源的自定义 lint 插件lint_method_collector工具来输出。

lint_method_collector插件工具工作流程:

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#50BFA0",
      "primaryBorderColor": "#3A8578",
      "primaryTextColor": "#3C3C3C",
      "nodeTextColor": "#2D2D2D",
      "lineColor": "#3AAFA9",
      "secondaryColor": "#E0F7FA",
      "tertiaryColor": "#B2EBF2",
      "background": "#F5FDFB",
      "fontSize": "16px",
      "edgeLabelBackground": "#F0FDFA",
      "edgeLabelColor": "#2D2D2D",
      "tertiaryBorderColor": "#8CD5CF",
      "arrowheadColor": "#3AAFA9",
      "clusterBkg": "#E0F7FA",
      "clusterBorder": "#8CD5CF",
      "titleColor": "#004D40"
    }
  }
}%%
graph TD
subgraph custom[输出项目依赖的三方SDK api 信息]
a[App] --> b[执行 Lint]
b --> c[执行目标类方法调用者 Detector]
c --> d[开始执行 Detector]
d --> e[读取要检查的类方法配置文件]
e --> f[项目和三方库 class 文件扫描]
f --> g[结束执行 Detector]
g --> h[结果分析]
h --输出--> j[JSON 报告文档]
h --输出--> k[HTML 报告文档]
end 

lint_method_collector插件工具输出结果,HTML 报告文档示例:

截屏2025-04-30 14.38.38.png

SDK封装与替换

SDK 封装与替换设计

要把三方SDK替换为其它SDK(三方或自研),首先需要将三方 SDK API 从项目中剥离出来,封装并下沉,让业务对SDK无感知。

markdown11.png

SDK 接口层:对使用到的三方SDK API抽象为接口。

SDK 实现层:三方和自研SDK对接口层接口的实现。

SDK 适配层:创建SDK对象,提供SDK上下文对象和生命周期管理:

  • SDK上下文对象:所有与SDK相关的对象,都需要通过SDK上下文对象来设置或获取,这样才能在灰度期间,让某个业务的开始到结束节点使用的是同一个SDK。
  • 生命周期管理:灰度期间,在应用程序中可能会同时存在三方和自研SDK对象,对象可以存在,但是持有的资源需要释放。

业务层:使用的是 SDK 接口层抽象,对SDK实现无感知。

SDK 封装规则

在业务中,可能使用了 SDK 中的静态常量,属性,静态方法,数据模型类,常规类,回调接口,View 类,内部类,工具类等。这些都需要基于一定的规则封装,目的是让业务层对SDK无感知

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#50BFA0",
      "primaryBorderColor": "#3A8578",
      "primaryTextColor": "#3C3C3C",
      "nodeTextColor": "#2D2D2D",
      "lineColor": "#3AAFA9",
      "secondaryColor": "#E0F7FA",
      "tertiaryColor": "#B2EBF2",
      "background": "#F5FDFB",
      "fontSize": "16px",
      "edgeLabelBackground": "#F0FDFA",
      "edgeLabelColor": "#2D2D2D",
      "tertiaryBorderColor": "#8CD5CF",
      "arrowheadColor": "#3AAFA9",
      "clusterBkg": "#E0F7FA",
      "clusterBorder": "#8CD5CF",
      "titleColor": "#004D40"
    }
  }
}%%
graph TD
subgraph abs[流程]
Biz[业务] --> Interface[interface 抽象层]
Interface --> InterfaceImpl[interface 实现层]
InterfaceImpl --> SDK[三方或自研 sdk 实体]
end

静态常量和静态方法

静态常量和静态方法保持一致。

三方SDK:

public class TTStreamingContext {
    public static final int STREAMING_CONTEXT_FLAG_SUPPORT_4K_EDIT = 1;
}

接口层:

public interface StreamingContext {
    public static final int STREAMING_CONTEXT_FLAG_SUPPORT_4K_EDIT = 1;
}

接口实现层:

public class TTStreamingContextImpl implementation StreamingContext{
    private TTStreamingContext mTTStreamingContext;
    
    public TTStreamingContextImpl(){
       this.mTTStreamingContext = new TTStreamingContext();
    }
    
    public static void setCheckEnable(boolean enable){
       TTStreamingContext.setCheckEnable(enable);
    }
}

属性和数据模型类

属性和数据模型类,提供 setter 和 getter 方法,以及 sdk 对象 box(装箱) 和 unbox(拆箱) 方法。

三方SDK:

public class TTRational {
    public int num;
    public int den;

    public TTRational(int num, int den) {
        this.num = num;
        this.den = den;
    }
}

接口层:

public interface Rational {

    void setDen(int den);

    int getNum();

    int getDen();

    void setNum(int num);
    
    /**
     * 返回真实实例对象
     */
    Object getRational();
    
    void setRational(Object object);
}

接口实现层:

public class TTRationalImpl implementation Rational{

    private TTRational mTTRational;
    
    public TTRationalImpl(int num, int den){
       this.mTTRational = new TTRational(num,den);
    }
    
    private TTRationalImpl(@NonNull TTRational ttRational) {
        this.mTTRational = ttRational;
    }

    /**
     * 返回真实实例对象
     */
    @Override
    public Object getRational() {
        return this.mTTRational;
    }
    
    @Override
    public void setRational(Object object){
        this.mTTRational = (TTRational)object;
    }

    public void setDen(int den){
       this.mTTRational.setDen(den);
    }

    public int getNum(){}

    public int getDen(){}

    public void setNum(int num){}
    
     /**
     * 将真实实例对象包装成一个接口对象
     */
    @NonNull
    public static Rational box(@NonNull TTRational ttRational) {
        return new TTRationalImpl(ttRational);
    }

    /**
     * 将接口对象拆解成一个真实实例对象
     */
    @NonNull
    public static TTRational unbox(@NonNull Rational rational) {
        return (TTRational) rational.getRational();
    }
}

boxunbox 方法用于接口实现层内部调用。比如,业务层传递接口对象Rational rational参数过来,那么首先需要将 rational 对象转换为真实的 sdk 对象。

常规类

常规类保留继承关系。

三方SDK:

public class TTClip extends TTObject {
 
    public long getTrimIn() {
       
    }
}

public class TTAudioClip extends TTClip {
    public TTAudioClip() {
    }

    public TTAudioFx appendFx(String fxName) {
        
    }
}

接口层:

public interface Clip {

    long getTrimIn();
}

public interface AudioClip extends Clip {
    AudioFx appendFx(String fxName);
}

接口实现层:

public class TTClipImpl implements Clip {
    private TTClip mClip;

    protected TTClipImpl(@NonNull TTClip ttClip) {
        this.mClip = ttClip;
    }

    @Override
    public long getTrimIn() {
        return this.mClip.getTrimIn();
    }
}


public final class TTAudioClipImpl extends TTClipImpl implements AudioClip {
    private TTAudioClip mAudioClip;

    public TTAudioClipImpl(@NonNull TTAudioClip ttAudioClip) {
        super(ttAudioClip);
        this.mAudioClip = ttAudioClip;
    }

    @Override
    public AudioFx appendFx(String name) {
        return TTAudioClip.box(this.mAudioClip.appendFx(name));
    }
}

TTClipImpl 实现了 Clip 接口, TTAudioClipImpl 继承 TTClipImpl 并实现了 AudioClip,而 AudioClip 又继承了 Clip 接口,那么 TTAudioClipImpl 实现了两次Clip 接口?

TTAudioClipImpl 只实现了一次Clip 接口,因为 JVM 在编译器会打平所有接口继承关系,不会重复实现,也就是不管通过多少路径继承,接口方法最终只会存在一次。

回调接口

回调接口采用代理模式来封装。

三方SDK:

public class TTIconGenerator {

    public void setIconCallback(IconCallback callback) {
        this.mCallback = callback;
    }

    public interface IconCallback {
        void onIconReady(Bitmap bitmap);
    }
}

接口层:

public interface IconGenerator {

    void setIconCallback(IconCallback callback);

    interface IconCallback {
        void onIconReady(Bitmap bitmap);
    }
}

接口实现层:

public final class TTIconGeneratorImpl implements IconGenerator {
    private TTIconGenerator mIconGenerator;

    public TTIconGeneratorImpl() {
        this.mIconGenerator = new TTIconGenerator();
    }
  
    @Override
    public void setIconCallback(IconCallback callback) {
        this.mIconGenerator.setIconCallback(TTIconCallbackWrapper.wrap(callback));
    }

    static final class TTIconCallbackWrapper implements TTIconGenerator.IconCallback {
        private final IconGenerator.IconCallback mIconCallback;

        private TTIconCallbackWrapper(@NonNull IconGenerator.IconCallback iconCallback) {
            this.mIconCallback = iconCallback;
        }

        static TTIconCallbackWrapper wrap(IconGenerator.IconCallback iconCallback) {
            return new TTIconCallbackWrapper(iconCallback);
        }

        @Override
        public void onIconReady(Bitmap bitmap) {
            this.mIconCallback.onIconReady(bitmap);
        }
    }
}

XXXCallbackWrapper使用代理模式,对 Callback进行封装。

View 类

对于 SDK 中的 View 类,不能直接抽象为接口。因为业务场景可能是:

  • 继承 SDK View 类
  • 在 xml 文件中配置 SDK View

所以,对 View 的封装是添加一个父 View 容器,并把它的其它方法抽象为接口方法。

三方SDK:

public class TTWindowView extends FrameLayout {
    public TTWindowView(@NonNull Context context) {
        super(context);
    }

    public TTWindowView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TTWindowView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TTWindowView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setFillMode(int mode){

    }

    public int getFillMode(){

    }
}

接口层:

public interface WindowView {
    void setFillModeX(int mode);

    int getFillModeX();
}

public class WindowViewContainer extends FrameLayout implements WindowView {
    public WindowViewContainer(@NonNull Context context) {
        super(context);
    }

    public WindowViewContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WindowViewContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public WindowViewContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * 业务使用 WindowViewContainer 时,SDK 内部添加真正的 WindowView
     */
    public static void addView(WindowViewContainer container, View impl) {
        //示例代码
        container.addView(impl);
    }

    /**
     * SDK 内部调用
     */
    public static View getView(WindowViewContainer container) {
        //示例代码
        return container.getChildAt(0);
    }

    @Override
    public void setFillModeX(int mode) {
        View view = this.getChildAt(0);
        if (view != null) {
            ((WindowView) view).setFillModeX(mode);
        }
    }

    @Override
    public int getFillModeX() {
        View view = this.getChildAt(0);
        if (view != null) {
            return ((WindowView) view).getFillModeX();
        }
        return -1;
    }
}

接口实现层:

public final class TTWindowViewImpl extends TTWindowView implements WindowView{
    public TTWindowViewImpl(@NonNull Context context) {
        super(context);
    }

    public TTWindowViewImpl(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TTWindowViewImpl(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public TTWindowViewImpl(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void setFillModeX(int mode) {
        this.setFillMode(mode);
    }

    @Override
    public int getFillModeX() {
        return this.getFillMode();
    }
}

业务继承 WindowViewContainer 或 在 xml 中配置 WindowViewContainer使用时,SDK 内部通过 WindowViewContainer#addViewTTWindowViewImpl 添加进来。

业务使用 WindowViewContainer 通过 setFillModeXgetFillModeX方法,调用到SDK 实体的 setFillModegetFillMode方法。

其它

如果有基础库对象传递到 SDK,或者从 SDK 返回基础库对象时,需要对基础库对象深拷贝或者让它只读

接口实现层:

public void setConfig(Map<String, String> map) {
    Map<String, String> copy = new HashMap<>(map);
    this.sdk.setConfig(copy);
}

public Map<String, String> getConfig() {
    Map<String, String> sdkMap = this.sdk.getConfig();
    Map<String, String> copy = new HashMap<>(sdkMap);
    return copy;
}

自动化生成SDK接口层和接口实现层代码

根据输出依赖的 api 信息,配合 SDK 封装规则,自动化生成SDK抽象接口。

lint_method_collector插件工具输出结果,JSON 报告文档示例:

[
  [
    {
      "ownerClassName": "com/example/TTGifDecoder",
      "ownerClassMethodName": "int getDelay(int)",
      "ownerClassFieldName": "",
      "callerClassName": "com/example/myapplication/CustomizeHelper",
      "callerClassMethodName": "calGifFrameRate(String gifPath)",
      "callerClassMethodLine": 426
    },
    {
      "ownerClassName": "com/example/TTGifDecoder",
      "ownerClassMethodName": "int getFrameCount()",
      "ownerClassFieldName": "",
      "callerClassName": "com/example/myapplication/CustomizeHelper",
      "callerClassMethodName": "getGifDuration(String filePath)",
      "callerClassMethodLine": 405
    },
    {
      "ownerClassName": "com/example/TTGifDecoder",
      "ownerClassMethodName": "void \u003cinit\u003e()",
      "ownerClassFieldName": "",
      "callerClassName": "com/example/myapplication/CustomizeHelper",
      "callerClassMethodName": "getGifDuration(String filePath)",
      "callerClassMethodLine": 405
    },
    {
      "ownerClassName": "com/example/TTGifDecoder",
      "ownerClassMethodName": "boolean isGif()",
      "ownerClassFieldName": "",
      "callerClassName": "com/example/myapplication/CustomizeHelper",
      "callerClassMethodName": "getGifDuration(String filePath)",
      "callerClassMethodLine": 405
    }
  ]
]

根据JSON,那么接口层:

public interface GifDecoder {
    int getDelay(int arg0);

    int getFrameCount();

    boolean isGif();

    void setGifDecoder(@NonNull Object object);

    @NonNull
    Object getGifDecoder();
}

接口实现层:

import com.example.TTGifDecoder
import com.sdk.interface.GitDecoder

public final class TTGifDecoderImpl implements GifDecoder {
    private TTGifDecoder mTTGifDecoder;

    public TTGifDecoderImpl() {
        this.mTTGifDecoder = new TTGifDecoder();
    }

    public TTGifDecoderImpl(TTGifDecoder ttGifDecoder) {
        this.mTTGifDecoder = ttGifDecoder;
    }

    @Override
    public int getDelay(int arg0) {
        return this.mTTGifDecoder.getDelay(arg0);
    }

    @Override
    public int getFrameCount() {
        return this.mTTGifDecoder.getFrameCount();
    }

    @Override
    public boolean isGif() {
        return this.mTTGifDecoder.isGif();
    }

    @Override
    public void setGifDecoder(@NonNull Object object) {
        this.mTTGifDecoder = (TTGifDecoder) object;
    }

    @NonNull
    @Override
    public Object getGifDecoder() {
        return this.mTTGifDecoder;
    }

    public static GifDecoder box(@NonNull TTGifDecoder ttGifDecoder) {
        return new TTGifDecoderImpl(ttGifDecoder);
    }

    @NonNull
    public static TTGifDecoder unbox(@NonNull GifDecoder gifDecoder) {
        return (TTGifDecoder) gifDecoder.getGifDecoder();
    }

}

只需按照 SDK 封装规则,通过 javapoet 就可生成 SDK 接口层和 SDK 实现层代码,这不仅能提高效率,而且能提代码质量

这里不介绍具体怎么实现,流程图如下:

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#50BFA0",
      "primaryBorderColor": "#3A8578",
      "primaryTextColor": "#3C3C3C",
      "nodeTextColor": "#2D2D2D",
      "lineColor": "#3AAFA9",
      "secondaryColor": "#E0F7FA",
      "tertiaryColor": "#B2EBF2",
      "background": "#F5FDFB",
      "fontSize": "16px",
      "edgeLabelBackground": "#F0FDFA",
      "edgeLabelColor": "#2D2D2D",
      "tertiaryBorderColor": "#8CD5CF",
      "arrowheadColor": "#3AAFA9",
      "clusterBkg": "#E0F7FA",
      "clusterBorder": "#8CD5CF",
      "titleColor": "#004D40"
    }
  }
}%%
graph TD
subgraph auto[自动化生成代码流程]
App --> Plugin[执行自定义 Gradle Plugin]
Plugin --> Task[执行自定义 Plugin Task]
Task --> StartTask[开始执行 Task]
StartTask --> ReadJson[读取 JSON 数据]
ReadJson --> JavaPoet[使用 JavaPoet]
JavaPoet --按照 SDK 封装规则--> Generate[构建 SDK 接口层和接口实现层代码]
Generate --> EndTask[结束执行 Task]
EndTask --生成--> Artifact[SDK 接口层和接口实现层 Java 代码]
Artifact --自动复制--> Target[SDK 模块]
end

数据回收

引入其它三方或自研SDK首次上线,需要关注线上质量,可以从覆盖率,转换率,成功率等方面进行数据回收,如:

分类场景三方自研
覆盖率业务189.29%10.71%
业务291.53%8.47%
转换率业务138.02%40.39%
业务228.21%34.73%
成功率业务198.02%99.79%
业务298.93%99.21%

总结

在项目中引入三方SDK,需要让业务层对SDK无感知。要达到无感知,可以将SDK下沉并封装,封装SDK适配层,SDK接口层,SDK接口实现层。此时,业务层通过SDK接口对象对SDK实体进行访问。

对已接入三方SDK多年的项目,可以通过lint_method_collector插件工具输出 SDK API JSON 信息,并根据SDK封装规则,利用自定义 Gradle 插件和javapoet, 自动化生成SDK接口层和接口实现层代码,这不仅能提高效率,而且能提代码质量。