Room 是 SQLite 的封装,它使 Android 对数据库的操作变得非常简单,也是迄今为止我最喜欢的 Jetpack 库。在本文中我会告诉大家如何使用并且测试 Room Kotlin API,同时在介绍过程中,我也会为大家分享其工作原理。

我们将基于 Room with a view codelab 为大家讲解。这里我们会创建一个存储在数据库的词汇表,然后将它们显示到屏幕上,同时用户还可以向列表中添加单词。

定义数据库表

在我们的数据库中仅有一个表,就是保存词汇的表。Word 类代表表中的一条记录,并且它需要使用注解 @Entity。我们使用 @PrimaryKey 注解为表定义主键。然后,Room 会生成一个 SQLite 表,表名和类名相同。每个类的成员对应表中的列。列名和类型与类中每个字段的名称和类型一致。如果您希望改变列名而不使用类中的变量名称作为列名,可以通过 @ColumnInfo 注解来修改。

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

@Entity(tableName = "word_table")
data class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)

我们推荐大家使用 @ColumnInfo 注解,因为它可以使您更灵活地对成员进行重命名而无需同时修改数据库的列名。因为修改列名会涉及到修改数据库模式,因而您需要实现数据迁移。

访问表中的数据

如需访问表中的数据,需要创建一个数据访问对象 (DAO)。也就是一个叫做 WorkDao 的接口,它会带有 @Dao 注解。我们希望通过它实现表级别的数据插入、删除和获取,所以数据访问对象中会定义相应的抽象方法。操作数据库属于比较耗时的 I/O 操作,所以需要在后台线程中完成。我们将把 Room 与 Kotlin 协程和 Flow 相结合来实现上述功能。

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

@Dao
interface WordDao {
    @Query("SELECT * FROM word_table ORDER BY word ASC")
    fun getAlphabetizedWords(): Flow<List<Word>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(word: Word)
}

我们在视频 Kotlin Vocabulary 中介绍了 协程的相关基本概念, 在 Kotlin Vocabulary 另一个视频中则介绍了 Flow 相关的内容。

插入数据

要实现插入数据的操作,首先创建一个抽象的挂起函数,需要插入的单词作为它的参数,并且添加 @Insert 注解。Room 会生成将数据插入数据库的全部操作,并且由于我们将函数定义为可挂起,所以 Room 会将整个操作过程放在后台线程中完成。因此,该挂起函数是主线程安全的,也就是在主线程可以放心调用而不必担心阻塞主线程。

@Insert
suspend fun insert(word: Word)

在底层 Room 生成了 Dao 抽象函数的实现代码。下面代码片段就是我们的数据插入方法的具体实现:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

@Override
public Object insert(final Word word, final Continuation<? super Unit> p1) {
    return CoroutinesRoom.execute(__db, true, new Callable<Unit>() {
      @Override
      public Unit call() throws Exception {
          __db.beginTransaction();
          try {
              __insertionAdapterOfWord.insert(word);
              __db.setTransactionSuccessful();
          return Unit.INSTANCE;
          } finally {
              __db.endTransaction();
          }
      }
    }, p1);
}

CoroutinesRoom.execute() 函数被调用,里面包含三个参数: 数据库、一个用于表示是否正处于事务中的标识、一个 Callable 对象。Callable.call() 包含处理数据库插入数据操作的代码。

如果我们看一下 CoroutinesRoom.execute() 的 实现,我们会看到 Room 将 callable.call() 移动到另外一个 CoroutineContext。该对象来自构建数据库时您所提供的执行器,或者默认使用 Architecture Components IO Executor。

查询数据

为了能够查询表数据,我们这里创建一个抽象函数,并且为其添加 @Query 注解,注解后紧跟 SQL 请求语句: 该语句从单词数据表中请求全部单词,并且以字母顺序排序。

我们希望当数据库中的数据发生改变的时候,能够得到相应的通知,所以我们返回一个 Flow<List<Word>>。由于返回类型是 Flow,Room 会在后台线程中执行数据请求。

@Query(“SELECT * FROM word_table ORDER BY word ASC”)
fun getAlphabetizedWords(): Flow<List<Word>>

在底层,Room 生成了 getAlphabetizedWords():

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

@Override
public Flow<List<Word>> getAlphabetizedWords() {
  final String _sql = "SELECT * FROM word_table ORDER BY word ASC";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
  return CoroutinesRoom.createFlow(__db, false, new String[]{"word_table"}, new Callable<List<Word>>() {
    @Override
    public List<Word> call() throws Exception {
      final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
      try {
        final int _cursorIndexOfWord = CursorUtil.getColumnIndexOrThrow(_cursor, "word");
        final List<Word> _result = new ArrayList<Word>(_cursor.getCount());
        while(_cursor.moveToNext()) {
        final Word _item;
        final String _tmpWord;
        _tmpWord = _cursor.getString(_cursorIndexOfWord);
        _item = new Word(_tmpWord);
        _result.add(_item);
        }
        return _result;
      } finally {
        _cursor.close();
      }
    }
    @Override
    protected void finalize() {
      _statement.release();
    }
  });
}

我们可以看到代码里调用了 CoroutinesRoom.createFlow(),它包含四个参数: 数据库、一个用于标识我们是否正处于事务中的变量、一个需要监听的数据库表的列表 (在本例中列表里只有 word_table) 以及一个 Callable 对象。Callable.call() 包含需要被触发的查询的实现代码。

如果我们看一下 CoroutinesRoom.createFlow() 的 实现代码,会发现这里同数据请求调用一样使用了不同的 CoroutineContext。同数据插入调用一样,这里的分发器来自构建数据库时您所提供的执行器,或者来自默认使用的 Architecture Components IO 执行器。

创建数据库

我们已经定义了存储在数据库中的数据以及如何访问他们,现在我们来定义数据库。要创建数据库,我们需要创建一个抽象类,它继承自 RoomDatabase,并且添加 @Database 注解。将 Word 作为需要存储的实体元素传入,数值 1 作为数据库版本。

我们还会定义一个抽象方法,该方法返回一个 WordDao 对象。所有这些都是抽象类型的,因为 Room 会帮我们生成所有的实现代码。就像这里,有很多逻辑代码无需我们亲自实现。

最后一步就是构建数据库。我们希望能够确保不会有多个同时打开的数据库实例,而且还需要应用的上下文来初始化数据库。一种实现方法是在类中添加伴生对象,并且在其中定义一个 RoomDatabase 实例,然后在类中添加 getDatabase 函数来构建数据库。如果我们希望 Room 查询不是在 Room 自身创建的 IO Executor 中执行,而是在另外的 Executor 中执行,我们需要通过调用 setQueryExecutor() 将新的 Executor 传入 builder。

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

companion object {
  @Volatile
  private var INSTANCE: WordRoomDatabase? = null
  fun getDatabase(context: Context): WordRoomDatabase {
    return INSTANCE ?: synchronized(this) {
      val instance = Room.databaseBuilder(
        context.applicationContext,
        WordRoomDatabase::class.java,
        "word_database"
        ).build()
      INSTANCE = instance
      // 返回实例
      instance
    }
  }
}

测试 Dao

为了测试 Dao,我们需要实现 AndroidJUnit 测试来让 Room 在设备上创建 SQLite 数据库。

当实现 Dao 测试的时候,在每个测试运行之前,我们创建数据库。当每个测试运行后,我们关闭数据库。由于我们并不需要在设备上存储数据,当创建数据库的时候,我们可以使用内存数据库。也因为这仅仅是个测试,我们可以在主线程中运行请求。

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

@RunWith(AndroidJUnit4::class)
class WordDaoTest {
  
  private lateinit var wordDao: WordDao
  private lateinit var db: WordRoomDatabase

  @Before
  fun createDb() {
      val context: Context = ApplicationProvider.getApplicationContext()
      // 由于当进程结束的时候会清除这里的数据,所以使用内存数据库
      db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java)
          // 可以在主线程中发起请求,仅用于测试。
          .allowMainThreadQueries()
          .build()
      wordDao = db.wordDao()
  }

  @After
  @Throws(IOException::class)
  fun closeDb() {
      db.close()
  }
...
}

要测试单词是否能够被正确添加到数据库,我们会创建一个 Word 实例,然后插入数据库,然后按照字母顺序找到单词列表中的第一个,然后确保它和我们创建的单词是一致的。由于我们调用的是挂起函数,所以我们会在 runBlocking 代码块中运行测试。因为这里仅仅是测试,所以我们无需关心测试过程是否会阻塞测试线程。

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

@Test
@Throws(Exception::class)
fun insertAndGetWord() = runBlocking {
    val word = Word("word")
    wordDao.insert(word)
    val allWords = wordDao.getAlphabetizedWords().first()
    assertEquals(allWords[0].word, word.word)
}

除了本文所介绍的功能,Room 提供了非常多的功能性和灵活性,远远超出本文所涵盖的范围。比如您可以指定 Room 如何处理数据库冲突、可以通过创建 TypeConverters 存储原生 SQLite 无法存储的数据类型 (比如 Date 类型)、可以使用 JOIN 以及其它 SQL 功能实现复杂的查询、创建数据库视图、预填充数据库以及当数据库被创建或打开的时候触发特定动作。

更多相关信息请查阅我们的 Room 官方文档,如果想通过实践学习,可以访问 Room with a view codelab。

以上就是Room Kotlin API使用入门教程的详细内容,更多关于Room Kotlin API使用的资料请关注Devmax其它相关文章!

Room Kotlin API的使用入门教程的更多相关文章

  1. HTML5之消息通知的使用(Web Notification)

    通知可以说是web中比较常见且重要的功能,私信、在线提问、或者一些在线即时通讯工具我们总是希望第一时间知道对方有了新的反馈。本篇文章主要介绍了HTML5之消息通知的使用(Web Notification),感兴趣的小伙伴们可以参考一下

  2. ios – 我可以使用哪些iPhone OS API来实现类似于iBook页面翻转过渡的过渡动画?

    >UIKitAPI中的某个地方是否可以使用该动画,还是我必须自己实现?它肯定有3D感觉,他们可以使用OpenGLESAPI吗?解决方法Apple当然使用OpenGLES来实现它.Apple使用的实际API是私有的,但thisblogger具有示例代码的实现的开始.

  3. iOS 7,用于断开调用的私有API CTCallDisconnect不起作用

    谢谢!

  4. 我应该使用哪个高级API来管理iOS上的UDP套接字?

    在“NetworkProgrammingTopicsConceptualGuide”的“UsingSocketsandStreams”一章中,Apple说:Note:POSIXnetworkingdoesnotactivatethecellularradiooniOS.Forthisreason,thePOSIXnetworkingAPIisgenerallydiscouragediniOS.同样

  5. 保护MY REST API仅用于MY IOS APP

    我在Laravel中设计一个RESTAPI,用于我的ios应用程序.目前我被困在以下几点:如何保护我的RESTAPI只允许访问我的ios应用程序?听起来我需要通过向我的IOSAPP授予一个私钥来将类似于HMAC方法的内容合并到我的IOSAPP代码中.当从iosapp中运行请求时,我传递带有私钥和其他数据的哈希,然后当在服务器上收到请求时,我通过重新计算哈希来检测请求是否来自应用程序内的用户.我不知道这是否安全&我会认为不是吗?

  6. ios – 尝试向我们分配IP而不是localhost或home时,NSURLSession失败

    我有一台本地运行的服务器(我的IP是192.168.0.98),并且已经尝试使用一些网络代码来访问它.最初这是通过AFNetworking完成的,但我现在用这样的NSURLSession完成了它:然后我用这3个URL运行它:>http://localhost:8080/api–>作品.>http://127.0.0.1:8080/api–>作品.>http://192.168.0.98:8080/

  7. 适用于iOS的Google云端硬盘实时API

    我想使用GoogleDrive和新的实时API在我的iOS应用中实现实时协作.我知道我可以在Objective-C中设置一个Web视图,并在Web视图和我的本机应用程序之间建立双向通信,因此使用javascript库,但我担心这对于高容量来说效率低下数据流量.我希望可能会有一个原生的解决方案即将出现.有关Objective-C的GoogleApi客户端库是否会更新以包含Google云端硬盘实时API的任何消息?

  8. ios – 如何通过iPhone中的Graph API在Facebook上“喜欢”和“评论”?

    我正在使用图形api显示新闻源.我对以下问题有疑问.>我想为每个新闻提要帖子提供“赞”功能.>我想为每个新闻提要帖子提供“评论”功能.有人可以帮助我如何使用iPhone中的图形API来解决这个问题.解决方法请参考我的答案:HowtocommentorlikeaphotoinfacebookthroughFBconnectorGraphAPIiniPhoneSDK?只需将您的访问令牌发送到https

  9. ios – 使用带有OAuth 2.0的Google API在iPhone中登录Gmail

    我找到了Google提供的服务,可以访问各种Google服务的GoogleApi.我可以在iPhone中设置一个项目,并为iOS应用程序和本机应用程序创建API访问.我想为我的iPhone应用程序使用本机API.它API为我提供了电子邮件,全名,名字,姓氏,google_id,性别,dob,profile_image.如何在我的iPhone应用程序,任何示例应用程序,可用的代码段中使用这些?

  10. ios – 如何使用YouTube API V3?

    我想知道如何在iOS应用中使用新的YouTubeAPI(第3版),但我不知道如何做.我做了很多关于它的研究,但是我发现所有的例子和老API的代码,所以它们是无效的.现在,我明白了,使用新的API你必须在Google开发者控制台中创建一个项目…使用API2很简单它…

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部