在kotlin中使用房间作为单身人士

时间:2017-08-28 06:06:59

标签: android singleton kotlin android-room

我正在尝试使用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()
    }
}

注意:

  1. 无法使用对象,因为会议室需要使用abstract class
  2. singleton必须是线程安全的,因为多个线程可能同时访问它。
  3. 必须能够将Context作为参数。
  4. 我查看了所有类似的StackOverflow问题,但都没有满足我的要求

    Singleton with argument in Kotlin 不是线程安全的

    Kotlin - Best way to convert Singleton DatabaseController in Android 不是线程安全的

    Kotlin thread save native lazy singleton with parameter 使用对象

6 个答案:

答案 0 :(得分:5)

我找到了一个解决方案,所以这里是未来我和任何可能遇到同样问题的人的答案。

经过一番研究,我发现我有两种选择。

  1. 使用Double-checked locking
  2. 使用Initialization-on-demand holder idiom
  3. 所以我考虑实施其中一个,但是在kotlin太多的样板代码中感觉不对:p

    所以经过更多的研究后,我偶然发现了this great article,这提供了一个很好的解决方案,它使用双重检查锁定,但是以一种很好的方式。

    我的代码变成这样:

    companion object : SingletonHolder<AppDatabase, Context>({
           Room.databaseBuilder(it, AppDatabase::class.java, "train.db").build()
    })
    

    来自文章:

      

    可重复使用的Kotlin实施:

         

    我们可以将逻辑封装到   懒惰地创建并初始化一个带有参数的单例   SingletonHolder课程。为了使逻辑线程安全,我们   需要实现同步算法并且效率最高   一个 - 这也是最难做到的 - 是双重检查   锁定算法。

    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
                }
            }
        }
    }
    

答案 1 :(得分:4)

在这种特殊情况下,我会使用Dagger 2或其他依赖注入库,如KoinToothpick。所有三个图书馆都允许以单身人士的身份提供依赖。

以下是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()
    }
}

AppComponent:

@Singleton
@Component(modules = arrayOf(
        AppModule::class
))
interface AppComponent {
    fun inject(viewModel: YourViewModel)
    fun inject(repository: YourRepository)
}

提供注入的应用程序类:

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)
}

答案 2 :(得分:1)

您可以使用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
    }
}

就个人而言,我通常会在Application中添加依赖于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

答案 3 :(得分:1)

使用 @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

答案 4 :(得分:0)

这是我想出来的......

@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!!
    }

}

答案 5 :(得分:-1)

在科特林单人很容易做到这一点

boost-python