在科特林中使用房间作为单例
Using room as singleton in kotlin
我正在尝试将 Room 用作单例,因此我不必调用 Room.databaseBuilder()
- 这很昂贵 - 不止一次。
@Database(entities = arrayOf(
Price::class,
StationOrder::class,
TicketPrice::class,
Train::class,
TrainCategory::class
), version = 2)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): TrainDao
companion object {
fun createDatabase(context: Context): AppDatabase
= Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
}
注:
- 无法使用 Object,因为 Room 需要使用
abstract class
。
- 单例必须是线程安全的,因为多个线程可能同时访问它。
- 必须能够将
Context
作为参数。
我查看了所有类似的 Whosebug 问题,其中 none 满足我的要求
不是线程安全的
不是线程安全的
使用对象
经过一些研究,我发现我有两个选择。
我考虑过实施其中之一,但这对 Kotlin 来说并不合适 - 样板代码太多。
经过更多的研究,我偶然发现了 this great article,它提供了一个很好的解决方案,它使用了双重检查锁定,但以一种优雅的方式。
companion object : SingletonHolder<AppDatabase, Context>({
Room.databaseBuilder(it.applicationContext, AppDatabase::class.java, "train.db").build()
})
来自文章:
A reusable Kotlin implementation:
We can encapsulate the logic to
lazily create and initialize a singleton with argument inside a
SingletonHolder
class. In order to make that logic thread-safe, we
need to implement a synchronized algorithm and the most efficient
one — which is also the hardest to get right — is the double-checked
locking algorithm.
open class SingletonHolder<T, A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
额外:
如果你想要带有两个参数的 Singleton
open class SingletonHolder2<out T, in A, in B>(creator: (A, B) -> T) {
private var creator: ((A, B) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg0: A, arg1: B): T {
val i = instance
if (i != null) return i
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg0, arg1)
instance = created
creator = null
created
}
}
}
}
您可以使用 Kotlin 标准库的
fun <T> lazy(LazyThreadSafetyMode.SYNCHRONIZED, initializer: () -> T): Lazy<T>
companion object {
private lateinit var context: Context
private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
fun getDatabase(context: Context): AppDatabase {
this.context = context.applicationContext
return database
}
}
但就我个人而言,我通常会在应用程序中添加依赖于 ApplicationContext 的单例,例如
<!-- AndroidManifest.xml -->
<manifest>
<application android:name="MyApplication">
...
class MyApplication : Application() {
val database: AppDatabase by lazy {
Room.databaseBuilder(this, AppDatabase::class.java, "train.db").build()
}
}
您甚至可以定义一个扩展方法以便于访问 context.database
。
val Context.database
get() =
generateSequence(applicationContext) {
(it as? ContextWrapper)?.baseContext
}.filterIsInstance<MyApplication>().first().database
在这种特殊情况下,我会使用 Dagger 2, or some other dependency injection library like Koin or Toothpick。所有三个库都允许将依赖项作为单例提供。
这是 Dagger 2 模块的代码:
@Module
class AppModule constructor(private val context: Context) {
@Provides
@Singleton
fun providesDatabase(): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"train.db")
.build()
}
}
应用程序组件:
@Singleton
@Component(modules = arrayOf(
AppModule::class
))
interface AppComponent {
fun inject(viewModel: YourViewModel)
fun inject(repository: YourRepository)
}
申请class提供注入:
class App : Application() {
companion object {
private lateinit var appComponent: AppComponent
val component: AppComponent get() = appComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
component = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
然后将您的数据库作为单例注入到您需要的任何地方(例如在您的应用 repository 中):
@Inject lateinit var appDatabase: AppDatabase
init {
App.component.inject(this)
}
我是这样想的...
@Database(entities = [MyEntity::class], version = dbVersion, exportSchema = true)
abstract class AppDB : RoomDatabase() {
// First create a companion object with getInstance method
companion object {
fun getInstance(context: Context): AppDB =
Room.databaseBuilder(context.applicationContext, AppDB::class.java, dbName).build()
}
abstract fun getMyEntityDao(): MyEntityDao
}
// This is the Singleton class that holds the AppDB instance
// which make the AppDB singleton indirectly
// Get the AppDB instance via AppDBProvider through out the app
object AppDBProvider {
private var AppDB: AppDB? = null
fun getInstance(context: Context): AppDB {
if (appDB == null) {
appDB = AppDB.getInstance(context)
}
return appDB!!
}
}
kotlin 中的单例真的很简单,只需这样做
companion object {
@JvmStatic
val DATABASE_NAME = "DataBase"
@JvmField
val database = Room.databaseBuilder(App.context(), DataBase::class.java, DataBase.DATABASE_NAME).build()
}
使用 @Volatile 实现线程安全。
public abstract class AppDatabase : RoomDatabase() {
abstract fun trainDao(): trainDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): Db = INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase ::class.java,
"train-db"
).build()
INSTANCE = instance
instance
}
}
}
取自:https://developer.android.com/codelabs/android-room-with-a-view-kotlin#7
我正在尝试将 Room 用作单例,因此我不必调用 Room.databaseBuilder()
- 这很昂贵 - 不止一次。
@Database(entities = arrayOf(
Price::class,
StationOrder::class,
TicketPrice::class,
Train::class,
TrainCategory::class
), version = 2)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): TrainDao
companion object {
fun createDatabase(context: Context): AppDatabase
= Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
}
注:
- 无法使用 Object,因为 Room 需要使用
abstract class
。 - 单例必须是线程安全的,因为多个线程可能同时访问它。
- 必须能够将
Context
作为参数。
我查看了所有类似的 Whosebug 问题,其中 none 满足我的要求
经过一些研究,我发现我有两个选择。
我考虑过实施其中之一,但这对 Kotlin 来说并不合适 - 样板代码太多。
经过更多的研究,我偶然发现了 this great article,它提供了一个很好的解决方案,它使用了双重检查锁定,但以一种优雅的方式。
companion object : SingletonHolder<AppDatabase, Context>({
Room.databaseBuilder(it.applicationContext, AppDatabase::class.java, "train.db").build()
})
来自文章:
A reusable Kotlin implementation:
We can encapsulate the logic to lazily create and initialize a singleton with argument inside a
SingletonHolder
class. In order to make that logic thread-safe, we need to implement a synchronized algorithm and the most efficient one — which is also the hardest to get right — is the double-checked locking algorithm.
open class SingletonHolder<T, A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
额外: 如果你想要带有两个参数的 Singleton
open class SingletonHolder2<out T, in A, in B>(creator: (A, B) -> T) {
private var creator: ((A, B) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg0: A, arg1: B): T {
val i = instance
if (i != null) return i
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg0, arg1)
instance = created
creator = null
created
}
}
}
}
您可以使用 Kotlin 标准库的
fun <T> lazy(LazyThreadSafetyMode.SYNCHRONIZED, initializer: () -> T): Lazy<T>
companion object {
private lateinit var context: Context
private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
fun getDatabase(context: Context): AppDatabase {
this.context = context.applicationContext
return database
}
}
但就我个人而言,我通常会在应用程序中添加依赖于 ApplicationContext 的单例,例如
<!-- AndroidManifest.xml -->
<manifest>
<application android:name="MyApplication">
...
class MyApplication : Application() {
val database: AppDatabase by lazy {
Room.databaseBuilder(this, AppDatabase::class.java, "train.db").build()
}
}
您甚至可以定义一个扩展方法以便于访问 context.database
。
val Context.database
get() =
generateSequence(applicationContext) {
(it as? ContextWrapper)?.baseContext
}.filterIsInstance<MyApplication>().first().database
在这种特殊情况下,我会使用 Dagger 2, or some other dependency injection library like Koin or Toothpick。所有三个库都允许将依赖项作为单例提供。
这是 Dagger 2 模块的代码:
@Module
class AppModule constructor(private val context: Context) {
@Provides
@Singleton
fun providesDatabase(): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"train.db")
.build()
}
}
应用程序组件:
@Singleton
@Component(modules = arrayOf(
AppModule::class
))
interface AppComponent {
fun inject(viewModel: YourViewModel)
fun inject(repository: YourRepository)
}
申请class提供注入:
class App : Application() {
companion object {
private lateinit var appComponent: AppComponent
val component: AppComponent get() = appComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
component = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
然后将您的数据库作为单例注入到您需要的任何地方(例如在您的应用 repository 中):
@Inject lateinit var appDatabase: AppDatabase
init {
App.component.inject(this)
}
我是这样想的...
@Database(entities = [MyEntity::class], version = dbVersion, exportSchema = true)
abstract class AppDB : RoomDatabase() {
// First create a companion object with getInstance method
companion object {
fun getInstance(context: Context): AppDB =
Room.databaseBuilder(context.applicationContext, AppDB::class.java, dbName).build()
}
abstract fun getMyEntityDao(): MyEntityDao
}
// This is the Singleton class that holds the AppDB instance
// which make the AppDB singleton indirectly
// Get the AppDB instance via AppDBProvider through out the app
object AppDBProvider {
private var AppDB: AppDB? = null
fun getInstance(context: Context): AppDB {
if (appDB == null) {
appDB = AppDB.getInstance(context)
}
return appDB!!
}
}
kotlin 中的单例真的很简单,只需这样做
companion object {
@JvmStatic
val DATABASE_NAME = "DataBase"
@JvmField
val database = Room.databaseBuilder(App.context(), DataBase::class.java, DataBase.DATABASE_NAME).build()
}
使用 @Volatile 实现线程安全。
public abstract class AppDatabase : RoomDatabase() {
abstract fun trainDao(): trainDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): Db = INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase ::class.java,
"train-db"
).build()
INSTANCE = instance
instance
}
}
}
取自:https://developer.android.com/codelabs/android-room-with-a-view-kotlin#7