一句话说透Android里面的单例模式

259 阅读3分钟

一句话总结:
单例模式就像「公司财务部」—— 整个系统只此一家,全局共享,Android 里的 InputMethodManagerLayoutInflater 都靠它确保关键服务唯一性!


一、单例模式在 Android 的经典应用

1. 系统服务单例(如 InputMethodManager)

源码实现

// frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java  
public final class InputMethodManager {  
    // 静态单例实例  
    static InputMethodManager sInstance;  

    // 双重检查锁定(线程安全)  
    public static InputMethodManager getInstance() {  
        synchronized (InputMethodManager.class) {  
            if (sInstance == null) {  
                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);  
                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);  
                sInstance = new InputMethodManager(service, Looper.getMainLooper());  
            }  
        }  
        return sInstance;  
    }  

    // 私有构造器  
    private InputMethodManager(IInputMethodManager service, Looper looper) {  
        mService = service;  
        mMainLooper = looper;  
    }  
}  

设计亮点

  • 双重检查锁:保证线程安全且高效
  • 延迟初始化:首次调用 getInstance() 时才创建
  • 跨进程调用:通过 Binder 获取系统服务

2. 布局填充器 LayoutInflater

源码实现

// frameworks/base/core/java/android/view/LayoutInflater.java  
public abstract class LayoutInflater {  
    // 单例缓存(按 Context 隔离)  
    private static final HashMap<Context, LayoutInflater> sInflaterMap = new HashMap<>();  

    public static LayoutInflater from(Context context) {  
        synchronized (sInflaterMap) {  
            LayoutInflater inflater = sInflaterMap.get(context);  
            if (inflater == null) {  
                inflater = new PhoneLayoutInflater(context);  
                sInflaterMap.put(context, inflater);  
            }  
            return inflater;  
        }  
    }  
}  

特殊处理

  • 按 Context 隔离单例:防止不同 Activity 的布局冲突
  • 使用 HashMap 缓存:平衡性能与内存占用

二、单例模式的五种实现方式对比

实现方式优点缺点Android 案例
饿汉式简单、线程安全可能提前占用内存TextUtils 工具类
懒汉式(同步锁)延迟加载每次获取都同步,性能差较少使用
双重检查锁定线程安全且高效JDK < 5 可能失效InputMethodManager
静态内部类懒加载 + 无同步开销无法传参初始化常见于应用层单例
枚举单例防反射攻击不够灵活Android 中较少使用

三、源码级设计原理剖析

1. 系统服务的单例管理(ServiceManager)

// frameworks/base/core/java/android/os/ServiceManager.java  
public final class ServiceManager {  
    private static final Map<String, IBinder> sCache = new ArrayMap<>();  

    // 获取系统服务单例  
    public static IBinder getService(String name) {  
        synchronized (sCache) {  
            IBinder service = sCache.get(name);  
            if (service != null) return service;  

            // 通过 Binder 驱动获取服务  
            return getIServiceManager().getService(name);  
        }  
    }  
}  

核心机制

  • 全局缓存:所有系统服务单例存储在 sCache
  • 跨进程通信:通过 Binder 与 system_server 进程交互

2. 避免内存泄漏的注意事项

错误示例

// 错误:单例持有 Activity 引用导致泄漏  
public class AppManager {  
    private static AppManager instance;  
    private Context context;  

    private AppManager(Context context) {  
        this.context = context; // 传入 Activity Context  
    }  

    public static AppManager getInstance(Context context) {  
        if (instance == null) {  
            instance = new AppManager(context);  
        }  
        return instance;  
    }  
}  

正确实现

// 使用 Application Context  
private AppManager(Context context) {  
    this.context = context.getApplicationContext();  
}  

四、单例模式的最佳实践

1. 使用 Dagger/Hilt 管理单例

// 使用 Hilt 定义单例  
@Module  
@InstallIn(SingletonComponent::class)  
object NetworkModule {  
    @Provides  
    @Singleton // 单例注解  
    fun provideOkHttpClient(): OkHttpClient {  
        return OkHttpClient.Builder().build()  
    }  
}  

优势

  • 生命周期管理:与组件生命周期绑定
  • 依赖解耦:便于测试和替换实现

2. 结合 ContentProvider 初始化

// 利用 ContentProvider 的 onCreate 初始化单例  
public class InitProvider extends ContentProvider {  
    @Override  
    public boolean onCreate() {  
        // 在应用启动时初始化  
        AppConfig.init(getContext());  
        return true;  
    }  
}  

原理

  • ContentProvider 的 onCreate() 在 Application.onCreate() 之前执行

五、总结口诀

单例模式管全局,系统服务最典型
双重检查锁线程安,静态内部类也推荐
内存泄漏要警惕,Application Context 记心间

使用场景建议

  1. 全局配置管理(如 SharedPreferences 封装)
  2. 网络请求客户端(如 Retrofit 实例)
  3. 数据库访问对象(Room Database

注意事项

  1. 避免在单例中保存 UI 相关引用
  2. 多进程应用需使用 @Singleton + @BindsInstance
  3. 单元测试时注意 Mock 单例对象

源码对照表

类名单例实现方式关键方法
InputMethodManager双重检查锁定getInstance()
TextUtils饿汉式全部静态方法
WindowManager系统服务单例WindowManagerGlobal