单例模式是开发中常用的设计模式,在Kotlin中,创建单例非常简单,只需要使用object
来声明即可,比如
object SomeSingleton
普通的类class
可以有构造方法,而object
不能有构造方法,但是init代码块还是可以使用,当object
被实例化第一次调用时,init代码块会以线程安全的方式执行,比如
object SomeSingleton {
init {
println("init complete")
}
}
我们知道kotlin最后会编译成class字节码,它最终的实现其实等同下面的Java代码
public final class SomeSingleton {
public static final SomeSingleton INSTANCE;
private SomeSingleton() {
INSTANCE = (SomeSingleton)this;
System.out.println("init complete");
}
static {
new SomeSingleton();
}
}
其实这也是Java中实现单例的一种方式,并且是比较推荐的方式,因为它不仅满足懒加载,而且还保证了线程安全,并且没有一些复杂的锁操作。
另外还可以通过by lazy
的方式去实现单例,比如在使用retrofit时,为service创建单例。
interface ApiService {
companion object {
val instance: ApiService by lazy {
Retrofit.Builder()....build().create(ApiService::class.java)
}
}
}
因为object
申明的对象是不能有构造方法,那么如果在单例初始化时需要参数,我们该怎么设计呢?比如说在Android开发中,我们经常需要传入一个Context
的实例,一般我们有以下三种方式来实现:
一、在Application.onCreate()的时候,将applicationContext保存在一个全局静态变量中,然后在object的init代码块中拿到这个context实例进行初始化,这样的好处是:
但是缺点是需要将Context保存到静态变量中。参考代码实现如下:
1object GlobalConfig {
2 lateinit var context: Context
3}
4
5class App : Application() {
6 override fun onCreate() {
7 super.onCreate()
8 GlobalConfig.context = applicationContext
9 }
10}
11
12object Singleton {
13 init {
14 initialize(GlobalConfig.context)
15 }
16 private fun initialize(context: Context) {
17 //...
18 }
19}
在Application.onCreate()的时候,拿到context实例,并初始化单例,这样的好处是:
但是缺点是在Application.onCreate()时就实例化单例了,无法实现懒加载。参考代码实现如下:
1class App : Application() {
2 override fun onCreate() {
3 super.onCreate()
4 Singleton.initialize(applicationContext)
5 }
6}
7
8object Singleton {
9 fun initialize(context: Context) {
10 //...
11 }
12}
在获取单例时传递Context参数,然后通过双重锁来实现创建单例,这样的好处是:
但是缺点是后续获取单例需要传参,这种方式是比较推荐的,在Android官方代码中也有使用到,比如WorkManager、WallpaperManager,参考代码实现如下:
1class Singleton private constructor(context: Context){
2 companion object {
3 @Volatile private var instance: Singleton? = null
4
5 fun getInstance(context: Context): Singleton {
6 val i = instance
7 if (i != null) {
8 return i
9 }
10 return synchronized(this) {
11 val i2 = instance
12 if (i2 != null) {
13 i2
14 } else {
15 val created = Singleton(context)
16 instance = created
17 created
18 }
19 }
20 }
21 }
22
23 init {
24 initialize(context)
25 }
26
27 private fun initialize(context: Context) {
28 //...
29 }
30}
对于第三种方式,我们可以通过封装来复用代码,首先封装一个通用的类SingletonHolder
来实现双重锁创建单例。
1open class SingletonHolder<out T: Any, in A>(creator: (A) -> T) {
2 private var creator: ((A) -> T)? = creator
3 @Volatile private var instance: T? = null
4
5 fun getInstance(arg: A): T {
6 val i = instance
7 if (i != null) {
8 return i
9 }
10 return synchronized(this) {
11 val i2 = instance
12 if (i2 != null) {
13 i2
14 } else {
15 val created = creator!!(arg)
16 instance = created
17 creator = null
18 created
19 }
20 }
21 }
22}
然后在需要的地方调用,这样代码就清爽多了。
class Singleton private constructor(context: Context) {
companion object : SingletonHolder<Singleton, Context>(::Singleton)
init {
initialize(context)
}
private fun initialize(context: Context) {
//...
}
}
1@Database(entities = arrayOf(User::class), version = 1)
2abstract class UsersDatabase : RoomDatabase() {
3
4 abstract fun userDao(): UserDao
5
6 companion object : SingletonHolder<UsersDatabase, Context>({
7 Room.databaseBuilder(it.applicationContext,
8 UsersDatabase::class.java, "Sample.db")
9 .build()
10 })
11}