【问题标题】:Android Room - Avoid passing Context into SingletonAndroid Room - 避免将 Context 传递给 Singleton
【发布时间】:2019-05-08 07:21:18
【问题描述】:

我正在尝试将项目迁移到 Android Room。在阅读了 Android Room 文档后,我注意到 Singleton 适合访问我的数据库。

来自 Android 开发者的引述:

注意:如果您的应用在单个进程中运行,则在实例化 AppDatabase 对象时应遵循单例设计模式。每个 RoomDatabase 实例都相当昂贵,您很少需要在单个进程中访问多个实例。

我写了以下代码:

@Database(entities = {Category.class, News.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static final String DB_NAME = "database.db";
    private static AppDatabase instance;

    public abstract CategoryDao categoryDao();
    public abstract NewsDao newsDao();

    private AppDatabase () {}

    public static AppDatabase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDatabase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, DB_NAME).build();
                }
            }
        }
        return instance;
    }
}

只是一个简单的双重检查锁定单例。

我已经阅读了一些指南/教程,几乎每个人都有类似的方法,但是我可以看到这种方法存在一些问题:

  • 每次都需要传递一个Context,即使你只需要一次来初始化Singleton。
  • 如果我需要在没有Context 可用的情况下访问数据库怎么办?
  • 甚至可以向 Singleton 发送参数?

任何想法如何实现一个房间数据库单例来解决这些问题?

如果可能的话,我想避免像 Dagger2 这样的 DI 库。

【问题讨论】:

  • “来自 Android 开发者的报价:”来自哪里?请具体说明,添加姓名、姓氏、排名和引用来源的链接。
  • 我认为您的“双重检查锁定 Singleton”是错误的,请参阅“使用此模式要记住的一件事是该字段需要是可变的以防止缓存不连贯问题。” -> baeldung.com/java-singleton-double-checked-locking

标签: android singleton android-room android-jetpack


【解决方案1】:

你可以初始化你的数据库并将它的一个实例保存到你的应用程序类中。

public class MyApplication extends Application {

    public AppDatabase database;

    @Override
    public void onCreate() {
        super.onCreate();

        database = AppDatabase.getInstance(this)
    }
}

晚了你可以访问你的参考像

((MyApplication)activity).dabase

希望这会有所帮助。

【讨论】:

  • 使用这种方法,我无法从 Activity 之外的任何地方调用它,对吧?像 Service、BroadcastReceivers 等...
  • 是的,你可以。 Kotlin 示例(context as MyApplication).database
【解决方案2】:
@Database(entities = {Category.class, News.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

   private static final String DB_NAME = "database.db";
   private static AppDatabase instance;
   public abstract CategoryDao categoryDao();
   public abstract NewsDao newsDao();

   private AppDatabase () {}

   // Use this to call on any place
   public static AppDatabase getInstance() {
      return instance;
   }
   // Use once to Create and setup the object
   public static AppDatabase setInstance(Context context) {
      if (instance == null) {
         synchronized (AppDatabase.class) {
            if (instance == null) {
                instance = Room.databaseBuilder(context.getApplicationContext(),
                        AppDatabase.class, DB_NAME).build();
            }
        }
     }
     return instance;
   }
}

// Second you need to set instance on Application Class which create and make your DB 
  //Ready for Use Before anything perform
public class MyApplication extends Application {
   @Override
   public void onCreate() {
      super.onCreate();
      AppDatabase.setInstance(this)
   }
}

使用

//Need to call
AppDatabase.getInstance().categoryDao();

【讨论】:

    【解决方案3】:

    您可以实例化数据库然后锁定实例。

    另外,调用context.applicationContext 返回当前进程的单个全局应用程序对象的上下文。此 Context 的生命周期与当前的 context 是分开的,后者与进程的生命周期相关联,而不是与当前组件相关联。

    /**
     * The Room Database that contains the item table.
     */
    @Database(entities = arrayOf(Item::class), version = 1, exportSchema = false)
    abstract class ItemDb : RoomDatabase() {
        abstract fun itemDao(): ItemDao
    
        companion object {
            private var INSTANCE: ItemDb? = null
    
            private val lock = Any()
    
            @JvmStatic
            fun getInstance(context: Context): ItemDb {
    
                // When calling this instance concurrently from multiple threads we're in serious trouble:
                // So we use this method, 'synchronized' to lock the instance
                synchronized(lock) {
                    if (INSTANCE == null) {
                        INSTANCE = Room.databaseBuilder(context.applicationContext, ItemDb::class.java, "items.db").build()
                    }
                    return INSTANCE!!
                }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      这里有很多事情需要考虑。这完全取决于您的用例 - 因为您的应用程序可能仅在单线程上接触数据库,因此您根本不需要任何同步。

      但是,如果您想要一些补充解决方案(在我上面提到的情况下也可能称其为“矫枉过正”),请查看https://github.com/android/architecture-components-samples,我在此处粘贴仅供参考(有关此方法的讨论是here

      /*
       * Copyright (C) 2017 The Android Open Source Project
       *
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *      http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      
      package com.example.android.observability.persistence
      
      import androidx.room.Database
      import androidx.room.Room
      import androidx.room.RoomDatabase
      import android.content.Context
      
      /**
       * The Room database that contains the Users table
       */
      @Database(entities = [User::class], version = 1)
      abstract class UsersDatabase : RoomDatabase() {
      
          abstract fun userDao(): UserDao
      
          companion object {
      
              @Volatile private var INSTANCE: UsersDatabase? = null
      
              fun getInstance(context: Context): UsersDatabase =
                      INSTANCE ?: synchronized(this) {
                          INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
                      }
      
              private fun buildDatabase(context: Context) =
                      Room.databaseBuilder(context.applicationContext,
                              UsersDatabase::class.java, "Sample.db")
                              .build()
          }
      }
      

      一般来说 - 您可能想看看 Kotlin double checking singleton 和/或 Java Double-Checked Locking with Singleton

      【讨论】:

        【解决方案5】:

        你可以使用这个功能

        public class DatabaseClient {
        
        private Context mCtx;
        private static DatabaseClient mInstance;
        
        //our app database object
        private final AppDatabase appDatabase;
        private DatabaseClient(Context mCtx) {
            this.mCtx = mCtx;
        
            //creating the app database with Room database builder
            //alldata is the name of the database
            appDatabase = Room.databaseBuilder(mCtx, AppDatabase.class, "alldata").build();
        }
        
        public static synchronized DatabaseClient getInstance(Context mCtx) {
            if (mInstance == null) {
                mInstance = new DatabaseClient(mCtx);
            }
            return mInstance;
        }
        
        public AppDatabase getAppDatabase() { return appDatabase; }
        
        }
        

        并使用它来调用 getInstance()

        DatabaseClient.getInstance(MainActivity.this).getAppDatabase().memberDao();
        

        AppDatabase 类如下所示:

        @Database(entities = {Member.class} ,  version = 1, exportSchema = false)
        public abstract class AppDatabase extends RoomDatabase {
        public abstract MemberDao memberDao();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多