前言
Sqlite数据库操作辅助类(各种DbManager)、线程池、网络请求等会消耗大量的资源,应尽量避免频繁地创建与销毁对象,造成资源的浪费;其次,文件系统、缓存、日志对象等,需要保证程序中有且只有一个,否则会导致状态不一直等问题。
这时,就需要我们用一种机制实现此类仅有一个实例,也即是单例模式。
单例模式的基本模式都是私有化构造函数以及提供一个访问内部静态单例的方法:
单例的特点决定了它扩展困难、测试困难,修改有可能会影响一大片;而且单例非常容易引发内存泄露,最常见的,就是持有了非Application的context的强应用,导致context对象不能释放;而且在Android中,当进程被杀死时,单例的状态也可能不一致,比如在Application中存放的内存数据,在应用重启时会丢失。
单例 in java
线程安全的单例的实现主要有以下几种(图片照抄的):
此外,使用容器(常见的是Map)管理单例也是一种方式。
/**
* java单例模式,只考虑线程安全的
* Created by lk on 2018/9/26.
*/
public class SingleTonJava implements Parcelable, Serializable {
private String data;
private SingleTonJava() {
}
private SingleTonJava(String data) {
this.data = data;
}
//------------------------------
// 饿汉,类创建的同时就实例化了静态对象,适用于无参构造,如果一个类初始化需要耗费很多时间,
// 或应用程序总是会使用到该单例,那建议使用饿汉模式,
// 比如各种补丁加载、缓存信息、系统配置信息等
//------------------------------
private static SingleTonJava INSTANCE = new SingleTonJava();
public static SingleTonJava getInstance() {
return INSTANCE;
}
//------------------------------
// 懒汉双重校验锁(synchronized效率差,不考虑),延迟初始化,节省资源。对于不需一直使用、不一定会用到、
// 或资源敏感的单例,如相机管理、音频管理等类,可以使用此种方式。
// 此种方式可以支持参数化初始化,但只有第一个参数起效...
//------------------------------
private volatile static SingleTonJava INSTANCE;
public static SingleTonJava getInstance(String data) {
if (INSTANCE == null) {
synchronized (SingleTonJava.class) {
if (INSTANCE == null) {
INSTANCE = new SingleTonJava(data);
}
}
}
return INSTANCE;
}
//------------------------------
// 内部静态类,也是无法使用参数化构造初始化。
// classloader会在实际使用到SingleTonJavaHolder类时才去加载SingleTonJavaHolder
//------------------------------
private static class SingleTonJavaHolder {
private static SingleTonJava INSTANCE = new SingleTonJava();
}
public static SingleTonJava getInstance(String data) {
return SingleTonJavaHolder.INSTANCE;
}
//------------------------------
// 枚举单例,最简单,线程安全+防反射+防序列化+防克隆+懒加载,同样不能参数化构造
//------------------------------
enum SingleTonJav8a {
INSTANCE;
public void fun() {
}
}
//------------------------------
// 容器单例,将多种单例模式注入到一个统一的管理类中,在使用时根据key获取对应类型的对象。
// Android系统的各种Service就是这模式
//------------------------------
private static Map<String, Object> map = new ConcurrentHashMap<>();
public static void registerInstance(String key, Object instance) {
map.put(key, instance);
}
public static Object getInstance(String key) {
return map.get(key);
}
}
除必须使用饿汉式的情况,懒加载无参构造时,推荐使用枚举或内部静态类单例,有参构造时也可以使用DCL单例,我们项目中常用的EventBus用的就是DCL的单例模式:
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
单例保证
java中,使用克隆、反序列化以及反射,可以绕过私有构造方法,创建出新的对象。鉴于要保持单例唯一,除使用枚举,其他模式都必须防止创建新的单例对象。
枚举单例
摘自深入理解Java枚举类型(enum)。
通过反编译枚举生成的class枚举,可以发现枚举就是继承Enum类的final类:
// 声明枚举
enum EXAMPLE {
ENUM1, ENUM2
}
// 反编译生成的枚举类
final class EXAMPLE extends Enum
{
//编译器为我们添加的静态的values()方法,用于class的getEnumConstantsShared反射获取所有枚举值数组
public static EXAMPLE [] values()
{
return (EXAMPLE [])$VALUES.clone();
}
//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
public static EXAMPLE valueOf(String s)
{
return (EXAMPLE )Enum.valueOf(xxx.EXAMPLE.class, s);
}
//私有构造函数
private EXAMPLE(String s, int i)
{
super(s, i);
}
// 前面定义的2种枚举实例
public static final EXAMPLE ENUM1;
public static final EXAMPLE ENUM2;
private static final EXAMPLE $VALUES[];
static
{
//实例化枚举实例
ENUM1 = new EXAMPLE("ENUM1", 0);
ENUM2 = new EXAMPLE("ENUM2", 1);
$VALUES = (new Day[] {
ENUM1, ENUM2
});
}
}
这不就是类似于容器单例了么…而在Enum的源码中,重写了clone:
// Enum.java节选
//--------------------------------------------
// 防止克隆
//--------------------------------------------
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
编译器不允许任何对这种序列化机制的定制,并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
在序列化时,Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象,防止反序列化:
public static <T extends Enum<T>> T valueOf(Class<T> var0, String var1) {
Enum var2 = (Enum)var0.enumConstantDirectory().get(var1);
if(var2 != null) {
return var2;
} else if(var1 == null) {
throw new NullPointerException("Name is null");
} else {
throw new IllegalArgumentException("No enum constant " + var0.getCanonicalName() + "." + var1);
}
}
// class.java节选
Map<String, T> enumConstantDirectory() {
if(this.enumConstantDirectory == null) {
// getEnumConstantsShared最终通过反射调用生成的枚举类的values方法
Object[] var1 = this.getEnumConstantsShared();
if(var1 == null) {
throw new IllegalArgumentException(this.getName() + " is not an enum type");
}
HashMap var2 = new HashMap(2 * var1.length);
Object[] var3 = var1;
int var4 = var1.length;
// var2存放了当前enum类的所有枚举实例变量,以name为key值
for(int var5 = 0; var5 < var4; ++var5) {
Object var6 = var3[var5];
var2.put(((Enum)var6).name(), var6);
}
this.enumConstantDirectory = var2;
}
return this.enumConstantDirectory;
}
而对于反射,使用Constructor.newInstance方法构造Enum时,会抛IllegalArgumentException,查看newInstance方法:
@CallerSensitive
public T newInstance(Object... var1) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if(!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
Class var2 = Reflection.getCallerClass();
this.checkAccess(var2, this.clazz, (Object)null, this.modifiers);
}
// 判断是否为枚举修饰符,如果是就抛异常,反射gg
if((this.clazz.getModifiers() & 16384) != 0) {
throw new IllegalArgumentException("Cannot reflectively create enum objects");
} else {
ConstructorAccessor var4 = this.constructorAccessor;
if(var4 == null) {
var4 = this.acquireConstructorAccessor();
}
Object var3 = var4.newInstance(var1);
return var3;
}
}
枚举单例确实能简单快捷地实现线程安全,延迟加载,序列化与反序列化、反射安全,但在java中,枚举占的内存通常是静态变量的两倍以上,所以通常是建议使用DCL或内部静态类单例代替枚举单例。
防克隆
单例类不应实现Cloneable接口,若非得继承实现了Cloneable的父类,重写吧:
//------------------------------
// 克隆处理
//------------------------------
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
防反序列化
若实现了Parcelable:
//------------------------------
// Parcelable反序列化处理
//------------------------------
private SingleTonJava(Parcel in) {
data = in.readString();
}
public static final Creator<SingleTonJava> CREATOR = new Creator<SingleTonJava>() {
@Override
public SingleTonJava createFromParcel(Parcel in) {
if (INSTANCE == null) {
synchronized (SingleTonJava.class) {
if (INSTANCE == null) {
INSTANCE = new SingleTonJava(in);
}
}
}
return INSTANCE;
}
@Override
public SingleTonJava[] newArray(int size) {
throw new Error("不能创建数组");
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(data);
}
若实现了Serializable:
//------------------------------
// Serializable反序列化处理
//------------------------------
public Object readResolve() throws ObjectStreamException {
if (INSTANCE == null) {
synchronized (SingleTonJava.class) {
if (INSTANCE == null) {
INSTANCE = new SingleTonJava(in);
}
}
}
return INSTANCE;
}
防反射
反射无效不就行了:
//------------------------------
// 反射处理
//------------------------------
private static volatile boolean flag = true;
private SingleTonJava() {
if (flag) {
flag = false;
} else {
throw new RuntimeException("已存在单例");
}
// 其他初始化工作
...
}
分布式/多类加载器
分布式中的“单例“必须使用数据库等方式来实现唯一性,而对于多class loader的情况,则必须手动设置单例类的“唯一指定”类加载器了。
单例 in Kotlin
单例保证
Kotlin保证单例的方式跟java基本一致:
/**
* 单例模式
*/
open class Singleton : Serializable, Cloneable, Parcelable {
var data: String? = null
//--------------------------------
// 防反射
//--------------------------------
@Throws(RuntimeException::class)
private constructor () {
if (flag) {
flag = false
} else {
throw RuntimeException("已存在单例")
}
// 其他初始化工作
}
private constructor(parcel: Parcel) : this(parcel.readString())
private constructor(data: String) : this() {
this.data = data
}
//--------------------------------
// 防反射
//--------------------------------
@Throws(CloneNotSupportedException::class)
override fun clone(): Any {
throw CloneNotSupportedException()
}
//--------------------------------
// 防Serializable反序列化
//--------------------------------
@Throws(ObjectStreamException::class)
fun readResolve(): Any {
if (INSTANCE == null) {
synchronized(SingleTonJava::class.java) {
if (INSTANCE == null) {
INSTANCE = Singleton()
}
}
}
return INSTANCE
}
//--------------------------------
// 防Parcelable反序列化
//--------------------------------
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(data)
}
override fun describeContents() = 0
companion object {
/**
* 在JVM平台,使用 @JvmStatic注解,可以将伴生对象的成员生成为真正的静态方法和字段
*/
@JvmField
val CREATOR: Parcelable.Creator<Singleton> = object : Parcelable.Creator<Singleton> {
override fun createFromParcel(parcel: Parcel): Singleton {
if (INSTANCE == null) {
synchronized(Singleton::class.java) {
if (INSTANCE == null) {
INSTANCE = Singleton(parcel)
}
}
}
return INSTANCE
}
override fun newArray(size: Int): Array<Singleton?> {
throw Error("不能创建数组")
}
}
@Volatile
var flag = true
//------------------------------
// 饿汉
//------------------------------
private var INSTANCE = Singleton()
fun getInstance() = INSTANCE
}
}
// 饿汉的最简形式,一行搞定
object Singleton
lazy&lateinit
Kotlin的延迟初始化主要依靠lazy或lateinit实现。
非抽象属性在声明时就必须先赋值,而lateinit可以使属性在声明后赋值:
companion object {
//------------------------------
// 懒汉DCL
//------------------------------
// lateinit用于变量var,在使用前必须手动赋值,控制权交给了自己,比by lazy灵活,性能也好一点
private lateinit var INSTANCE:Singleton
fun getInstance(data:String?): Singleton {
if (INSTANCE == null) {
synchronized(SingleTonJava::class.java) {
if (INSTANCE == null) {
INSTANCE = Singleton()
}
}
}
return INSTANCE
}
}
by lazy本质是一种属性委托,通俗理解就是在val常量第一次被调用getter时,执行自定义的线程安全的唯一一次初始化,详细分析可见玩转 Kotlin 委托属性:
companion object {
val INSTANCE:Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Singleton()
}
}
kotlin单例实现
除去以上保证单例的代码,其实现跟java基本一致
//------------------------------
// 饿汉最简单方式
//------------------------------
object Singleton
//------------------------------
// 枚举
//------------------------------
enum class Singleton{
INSTANCE;
fun fun0() = 1
}
class Singleton {
var data: String? = null
private constructor () {
...
}
private constructor(data: String) : this() {
this.data = data
}
companion object {
//------------------------------
// 饿汉
//------------------------------
private val INSTANCE = Singleton()
fun getInstance() = INSTANCE
//------------------------------
// 懒汉DCL
//------------------------------
private lateinit var INSTANCE:Singleton
fun getInstance(data:String): Singleton {
if (INSTANCE == null) {
synchronized(SingleTonJava::class.java) {
if (INSTANCE == null) {
INSTANCE = Singleton(data)
}
}
}
return INSTANCE
}
//------------------------------
// 懒汉by lazy
//------------------------------
val INSTANCE:Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Singleton()
}
//------------------------------
// 内部静态类
//------------------------------
fun getInstance() = Holder.INSTANCE
}
private object Holder{
val INSTANCE = Singleton()
}
}
单例 in Android Framework——LayoutInflater
Android开发中,经常用到Context的getSystemService方法,如ActivityManagerService、WindowManagerService等,而我们经常使用的LayoutInflater也是其中之一。系统服务需要供所有app使用,因此必定是单例,此处就以LayoutInflater为例进行说明:
// activity中使用setContentView设置layout
setContentView(R.layout.activity_main)
// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// Activity的getWindow其实是PhoneWindow
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 重点关注,使用了LayoutInflater类型的mLayoutInflater初始化mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
// PhoneWindow在Activity初始化时,一并初始化了mLayoutInflater
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
此处的LayoutInflater.from在使用ListView、RecycleView的Adapter时,也经常用到,其最终调用的是context.getSystemService:
// LayoutInflater.java
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
Context具体的
// Activity.java
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
...
return super.getSystemService(name);
}
// Activity的父类ContextThemeWrapper.java
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
// 使用context包装类ContextWrapper的getSystemService
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
// ContextWrapper.java
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
首先我们需要先了解一下Android Context的构成,系统调用attachBaseContext时,会把ContextImpl对象作为参数赋值给mBase对象,之后ContextWrapper中的所有方法,最终都是通过这种委托机制交由ContextImpl去实现的:
// ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry是获取系统服务的最终节点,它使用了容器单例的方式,提供了系统服务:
// SystemServiceRegistry.java(final类)
// 容器单例
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
/**
* Statically registers a system service with the context.
* This method must be called during static initialization only.
* 在静态代码块中被调用,注册服务
*/
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
// 静态初始化注册系统服务
static {
...
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
/**
* Gets a system service from a given context.
* 获取系统服务
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
单例 in Use
- 对于无需长期存在内存的类,如工具方法、常量类等,一般使用静态类(kotlin中的object其实是饿汉单例,companion object才是static)。
- 在使用单例时,需要特别注意尽量不要持有非Application的context,内存泄漏很多时候都是这样发生的。
- 单例尽量懒加载,需要注意及时释放不需要的资源,毕竟一直会在内存。
- 不能使用Application的单例来存放、传递组件数据,因为进程被杀死时,Application会重建。
- 单例是测试不友好的,尽量不要过多修改。
参考资料
这篇比较深入哦,强烈建议阅读
https://blog.youkuaiyun.com/happy_horse/article/details/50908439
https://blog.youkuaiyun.com/happy_horse/article/details/51164262
https://www.jianshu.com/p/91397bebe2c1