SDK 生命周期:从加载到销毁的阶段

做 SDK 开发这么多年,我最深的感触是——搞懂生命周期,比背一百个 API 都有用。你想想看,一个 SDK 从被宿主 App 加载进来,到最后被卸载清理,中间经历了多少个关键节点?这些节点里,哪个最容易出幺蛾子?

说白了,生命周期就是 SDK 的“生老病死”。你只有摸清了每个阶段的特征,才能在出现重复注册、初始化顺序混乱的时候,一眼看穿问题在哪。

我个人习惯把 SDK 的生命周期分成四个大阶段:加载、初始化、运行、销毁。每个阶段都有它自己的“脾气”,咱们一个一个来拆。

一、加载阶段:SDK 刚进内存的时候

这是最容易被忽略的阶段。很多工程师觉得,SDK 只要编译进了 APK,就算“加载”了。其实不是的。真正的加载,是从 ClassLoader 把 SDK 的类首次加载进 JVM 开始算起。

关键点:加载阶段不做任何业务逻辑,只做两件事:
1. 静态代码块执行(static block)
2. 静态变量的初始化

我遇到过有的团队,喜欢在静态代码块里偷偷干“大事”——启动线程、创建单例、甚至调用网络请求。结果呢?App 一启动就死锁了。为什么会这样?因为静态代码块的执行时机完全不受你控制,它由 ClassLoader 决定。你想想看,宿主 App 主线程还没准备好,你的 SDK 就抢着干活,不出问题才怪。

我的建议:不要在静态代码块里做任何“活”操作。只做变量声明和常量定义。真要初始化,等主动调用再说。

二、初始化阶段:SDK 的“开张”

初始化阶段,才是 SDK 真正开始干活的起点。这里的水非常深。我见过的重复注册问题,十有八九都出在这个阶段。

为什么?因为初始化通常要完成:

  • 读取配置(本地缓存 or 远程下发)
  • 注册广播/回调/监听器
  • 创建线程池或连接池
  • 预加载资源或数据

嗯,这里要注意。如果初始化被调了两次,或者回调的顺序搞反了,你就等着崩溃吧。我个人习惯在入口方法里加一个 幂等保护:

public class SdkCore {
    private static AtomicBoolean isInit = new AtomicBoolean(false);

    public static synchronized void init(Context context, SdkConfig config) {
        if (isInit.getAndSet(true)) {
            // 防止重复注册
            Log.w("SdkCore", "已经初始化过了,忽略本次调用");
            return;
        }
        // 真正的初始化逻辑...
    }
}
注意:getAndSet(true) 是线程安全的,比用 if + 赋值更可靠。我曾经在一个项目里用普通布尔变量判断,结果多线程环境下被坑惨了——两个线程同时过了 if 判断,导致注册了两次,回调执行了两遍,资源泄漏了一堆。

三、运行阶段:平稳还是暴雷?

初始化成功之后,SDK 就进入了“稳定运行期”。但稳定的假象背后,隐藏着很多雷。比如:

  • 宿主 App 的 Activity 生命周期变化,SDK 是否跟着变?
  • 多个 SDK 之间,有没有共享全局变量?
  • 重复注册是否发生在运行时?

我印象很深的一个案例:某次线上反馈,App 偶尔闪退。查了两天,最后发现是 SDK 内部的一个 Handler 在 Activity 销毁后没有移除回调,导致内存泄漏。等下一个 Activity 创建时,又注册了新的 Handler,两个线程同时操作同一个 List,直接 ConcurrentModificationException。

运行期最核心的调试原则:
- 所有回调/监听器必须支持“去重注册”
- 所有全局资源必须按生命周期“成对”申请和释放

四、销毁阶段:善始善终

很多 SDK 长于开疆拓土,却短于功成身退。销毁阶段,说白了就是把初始化时占用的资源,全部干干净净地还回去。

如果销毁不彻底,后果很严重:

资源类型 未释放的后果
线程池 线程泄漏,OOM
文件句柄 文件无法删除,磁盘爆满
网络连接 连接池耗尽,请求超时
BroadcastReceiver 重复注册 + 内存泄漏

销毁接口必须设计成“幂等”的,调用一次和调用十次效果相同。而且最好提供一个 强制重置 的选项,防止宿主 App 在某些极端情况下需要完全重启 SDK。

一个小技巧:在销毁方法里,把初始化状态的标志位也重置掉。这样下次再调用 init 时,可以重新开始。我曾经见过某个 SDK,destroy 之后不重置标志位,结果第二次 init 直接被幂等保护过滤掉了,等于“死了就活不过来”,体验极差。

核心逻辑总结

为了让你看得更清楚,我把这四个阶段以及它们之间的关键检查点,画成了一张图:

SDK 生命周期核心节点 1. 加载阶段 ClassLoader 加载类 静态块执行 2. 初始化阶段 配置读取/注册入口 幂等保护 3. 运行阶段 业务调用/回调 去重监听 4. 销毁阶段 资源回收/线程停止 状态重置 ⚠️ 检查点: 1. 静态块是否干“活”了? 2. 是否加了幂等保护? ⚠️ 检查点: 3. 监听器是否去重? 4. 回调是否成对? ⚠️ 检查点: 5. 是否彻底清理资源? 6. 状态标志是否重置? 横向流程为正常生命线 虚线为“重启”路径

总结一下

SDK 的生命周期,不是你写完了就完事的。它是一个闭环,从加载到销毁,每一步都需要你精心设计。什么时候该做什么事、做了之后怎么还原、重复调用怎么防护——这三条想清楚,你的 SDK 至少能避免 80% 的重复注册和初始化顺序问题。

嗯,下一阶段我们就开始逐个攻破这些具体问题了。先记住这张生命周期图,后面所有调试技巧都建立在这个框架之上。