重复注册的根本原因:单例失效与多重初始化
上一章我们聊了重复注册到底长什么样,崩得有多疼。今天咱们挖一挖根因——为什么好好的 SDK,会注册两次?说白了,就是单例没守住,或者初始化被调了多次。
我在团队里排查这类崩溃时,发现 90% 的场景都逃不出这两个坑。一个是单例失效,一个是多重初始化。两者经常结伴出现,但根因不同,修法也不同。
单例失效——你写的单例真的安全吗?
SDK 的作者通常会在内部写一个单例来持有全局注册信息。比如这样:
public class SdkManager {
private static SdkManager instance;
public static SdkManager getInstance() {
if (instance == null) {
instance = new SdkManager();
}
return instance;
}
private SdkManager() {
// 注册服务、准备环境
registerCoreComponents();
}
}
看起来很标准对吧?但我敢打赌,你在项目里至少见过它「失效」的场景。为什么?
- 多线程并发:两个线程同时走到
if (instance == null),分别创建实例,单例瞬间变成双胞胎。 - 反射破坏:调用
Constructor.setAccessible(true)强制 new 出第二个对象。 - 序列化反序列化:
readObject()会返回新实例,除非你加了readResolve()。 - ClassLoader 隔离:不同 ClassLoader 加载同一个类,各自持有自己的单例。这在插件化、热修复场景下非常常见。
特别提醒:我曾经在一个大型项目中,第三方 SDK 使用了懒加载单例且没加 volatile/synchronized。线上用户同时启动多个 Activity 时,崩溃率飙升到 8%。后来加上 synchronized 和 volatile 双重检查锁,问题消失。嗯,这个坑你们要记住。
多重初始化——谁调了我两次?
另一大元凶是 SDK 被主动/被动调用了多次 init()。常见场景有:
| 场景 | 典型表现 | 根因 |
|---|---|---|
| Application 多次调用 | Base 和子模块各调一次 sdk.init() |
框架自动合并清单时未去重 |
| ContentProvider 初始化 | 主进程 + 子进程各自触发一次 | Android 多进程模式下的重复触发 |
| 热修复/插件化重新加载 | 补丁包加载时重新走初始化流程 | 插件代码中重新调用了 init |
| 反射手动调用 | 通过反射绕过单例限制 | 开发者对 SDK 内部不熟悉 |
你想想看,SDK 的 init() 一般会做资源分配、广播注册、Service 绑定。调两次相当于把同一份注册动作执行了两遍,先注册的还没来得及清理就被第二次覆盖,内部状态乱成一团。
从崩溃现场反推根因
我习惯在拿到堆栈时先画一张图,把调用链和原因串联起来。下面这张图帮你快速定位到单例失效还是多重初始化:
如何定位具体是哪种?
我个人最常用的方法是加日志。在 SDK 的 register() 和 init() 入口处打一个堆栈日志,然后复现崩溃。从堆栈里看调用次数和调用链:
- 如果
getInstance()被调了两次且返回不同对象 → 单例失效。 - 如果
init()被调了两次且都是同一个对象 → 多重初始化。
小技巧:我曾经在一个崩溃日志里发现,同一个线程调了两次 register(),但中间隔了 5 秒。分析后发现是 Activity 的 onCreate 和 onResume 各调了一次。这种情况属于多重初始化,修起来很简单——加一个 boolean 标识位,第一次执行后置 true,后续跳过。
典型案例:类加载器隔离导致的单例失效
这里要特别提一下 ClassLoader 隔离。Android 插件化框架(如 RePlugin、VirtualAPK)会把插件代码放在单独的 ClassLoader 里。如果 SDK 在主 ClassLoader 和插件 ClassLoader 中各加载一次,那么 SdkManager.class 实际上是两个不同的 Class 对象,自然就有两个静态 instance 变量。
解决办法?我建议 SDK 作者将所有静态变量放到一个 Application 对象的成员中,或者使用 ContentProvider 来做唯一初始化。实在不行,把 SDK 强制固定在主 ClassLoader 中加载。
好了,到这你应该明白:重复注册的本质不是「多写了代码」,而是单例机制被攻破,或者初始化逻辑被重复执行。下一层我们要聊如何从代码层面彻底预防这些问题,也就是「防御性注册」的设计模式。