【翻译】Realm , ObjectBox ,还是 Room ,哪个适合你?

2017-09-30 by Liuqingwen | Tags: Kotlin Android 翻译

一、前言

原文标题:Realm, ObjectBox or Room. Which one is for you?
原文地址:https://notes.devlabs.bg/realm-objectbox-or-room-which-one-is-for-you-3a552234fd6e

二、正文

选择,选择,还是选择。当面对数据存储的时候,安卓开发者有太多的类库可选。不论是处理少量对象,还是构建一个大型的数集,这些工具都能完成使命,等着我们去使用。其中有一些是我们经常打交道的,比如 shared preferences 和 纯 SQL 语句,其他的则需要额外的一些依赖。庆幸的是,我保证,在这里我并不会去讨论如何写一个很长的又合理的查询语句。相反,接下来我将会对这些大联盟玩家进行类比,包括:最新发布的 Room 持久化类库 ,年老健壮的 Realm ,以及鲜为人知的新秀 ObjectBox ,它最近才发布 beta 版本。至于最终的选择权,由你决定,当然到最后你也会非常(或多或少吧)清楚地知道应该选择哪一个。毫无例外,在我们开始进入泰坦大战之前,让我先来给它们作一下介绍。

自从它的理念发布( 2011 年左右,原名 “ TightDB ”)以来 Realm 就自然而然地成为了许多开发者的开发首选。为什么呢?你会这么问。因为简单(几乎完全使用最标准的 Java 对象),速度快(大部分是采用 C++ 编写),并且由于 SQL (因为没有使用它)。无需深入太多细节,你就能轻松地创建一个 Realm 数据库并使用它——甚至还能做的更多。这个库无需太多配置,而且官方文档也能手把手地教会你如何一步一步的完成。

存储数据对象所需要做的第一件事就是建立一个数据模型:

1
2
3
4
5
open class Box(
@PrimaryKey var size: Long = 0,
var name: String = "",
@Ignore var tempReference: Int = 0
) : RealmObject()

这里如果你使用 Kotlin 的话,唯一值得注意的是,所有的变量必须都要有默认值。注解和继承于 RealmObject 的必要性,都能很好地解释这些代码(希望如此),那么我们继续下一步。

Realm 使用起来如下面的代码一样,非常简单:

1
2
3
4
5
6
7
8
9
10
11
Realm.init(context)
val realm = Realm.getDefaultInstance()
val box = realm.where(Box::class.java).findFirst()
realm.executeTransaction {
//modifying an existing object
box.size = 20
box.name = "John"
//creating a new object
val secondBox = realm.createObject(Box::class.java)
secondBox.size = 30
}

完整的例子在此

注意:基于以数据库为中心的原则,我把多线程的任务交给你了。

注意2:的确,这个 box 的名字就是 John 。

进入房间( Room )!一个最新的,光环最闪耀的谷歌官方类库。 Room 在官方的架构指南中占据着一个中心位置,它提供在 SQLite 上的一个抽象层,允许在充分利用 SQLite 的强大基础上进行流畅地数据库访问开发。它完美地剥离开了 SQL 层,并向开发者展示出清晰、易懂的 Java 语法方法。所以,还记得我保证过没有查询语句吗?但是现在我要写一些查询语句了!不过不要担心, Room 包含的一些安全特性,能够提示你万一出现的那些令人讨厌的错误。

当然,至少在我写这篇文章的时候是这样(或许在很长一段时间之后也是这样), Room 是城里最受欢迎的那个孩子,但是我将会尽量保持对他做一个简短的介绍。

在 Room 中有 3 个主要的组件,都是使用注解来展示说明:

Database :你可以使用这个组件来创建数据库的持有者。这个注解定义了一系列的实体,以及类的相关内容——数据中一系列的数据访问对象( 一些 DAO )。同时它也是底层数据连接访问的枢纽。这个注解标记的类必须是一个抽象类并且继承于 RoomDatabase 。你可以使用 Room.databaseBuilder() 或者 Room.inMemoryDatabaseBuilder()获取到它的一个实例。

Entity :这个组件代表了数据库中一行数据的类。对于各个实体,数据库中的表的创建就是为了存储它们的实例。你必须通过数据库类中的实体数组来引用实体类。

DAO :这个组件代表一个数据访问对象的类或者接口。 DAO 负责定义数据库访问的方法。用 @Database 注解的类必须包含一个抽象的方法,它含有 0 个参数,并返回一个使用 @Dao 注解的类。

下面是上面所提到过的组件的 3 个实现(羞愧地从这篇精彩的文章中复制了过来):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity(tableName = “task”)
data class Task(@ColumnInfo(name = “completed_flag”) var completed: Boolean = false,
@ColumnInfo(name = “task_desciption”) var description: String) {
@ColumnInfo(name = “id”)
@PrimaryKey(autoGenerate = true) var id: Long = 0
}
@Dao interface TaskDao {
@Query(“select * from task”)
fun getAllTasks(): List<Task>
@Query(“select * from task where id = :p0”)
fun findTaskById(id: Long): Task
@Insert(onConflict = REPLACE)
fun insertTask(task: Task)
@Delete
fun deleteTask(task: Task)
}
@Database(entities = arrayOf(Task::class), version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
}

创建一个数据库并访问它的方法非常简单:

1
2
3
4
var database = Room.databaseBuilder(context, AppDatabase::class.java,”db”)
.allowMainThreadQueries().build()
database.taskDao().insertTask(Task(description = “simple!”))

作为最新的成员, ObjectBox 给数据库带来了一大堆功能。但是在如此高的门槛面前,这个崭新的无 SQL 技术库能够和其他的大牌分庭对抗吗?毫无疑问,如果和 Realm 以及 Room 面对面肉搏的话,它必须重拳出击。结果的确如此,它不止一次重锤出击,而是出了一系列的重锤对抗。这里有这个新生库所突出的一些亮点:

速度:如同 Realm , ObjectBox 一样,能够提供卓越的性能,甚至某些时候它能够完全碾压其竞争对手(稍后再谈这个)。

查询生成器:使用 ObjectBox 查询对象和编译时错误检查都非常简单。

数据关联:对象的引用/关联是内建的内部类型,它们都属于原生本地引用。

无需手动数据模式迁移:它能够自动处理新版本对象所新加的、删除的、重命名的属性。

等等,等等

那么实际代码中它是怎样的呢?

它必须有定义的模型,至少目前来说你应该很熟悉了:

1
2
@Entity
data class Note(@Id var id: Long = 0, val text: String)

ObjectBox 使用叫做 Boxes (猜一下)的对象来存储并操作数据。只需 2 行代码就能把数据库和操作分离:

确切地说,“数据存储盒子”对象,应该放置在你的 Application 类中:

1
MyObjectBox.builder().androidContext(App.this).build()

每个“盒子”对应你的数据库中的数据模型。这些盒子作为一个交互点服务于你和你的数据库之间。

1
var notesBox = boxStore.boxFor(Note::class.java)

一个很重要的细节是:这些盒子类型都是自动生成的,这意味着你所需要担心的事情变得更少。

一旦完成了这些,你就可以准备下一步行动了,这里有一些可用的方法提供给你使用:

1
2
3
notesBox.put(note)
notesBox.remove(note)
notesBox.count()

完整的 Box 类方法列表可以查看它的 Java 文档。这里有一件事需要提醒注意的是名叫 DaoCompat 的兼容层,允许使用像 greenDAO 一样的 API 来操作 ObjectBox 。

  • 比较

到目前为止,所有的类库都做到了差不多相同的事情,有些需要,有些则不需要 SQL 语句。然而,我们更感兴趣的是它们的区别。如下图,我通过 3 种不同的方式分别测试了它们的性能,测试过程使用的是这个开源的性能测试应用程序

realm_objectbox_room_comparison
realm_objectbox_room_comparison
realm_objectbox_room_comparison
realm_objectbox_room_comparison
realm_objectbox_room_comparison

测试 100k/10k 个元素的性能,以毫秒为单位

非常漂亮而又很有意思的结果,你觉得呢?从这个测试中可以很清晰地看到,大多数情况下 ObjectBox 都能碾压所有其他竞争对手。并且,当然随着元素的数量级的增加,差距变得越来越大!对于一个新成员来说,表现得还行。可以说非常好。

realm_objectbox_room_comparison

查询同样看上去是 ObjectBox 的一个强项。测试中采用了字符串和索引,结果不言自明。

那么 apk 的大小又如何呢?这几个类库对我们项目的整体速度影响又有几何?好吧,我们可以使用最新发布的 apk 分析工具来精确地查看一下每一个类库其影响程度如何。

ObjectBox 和 Realm 分别占用空间高达 1-1.5MB3-4MB (这个大小取决于手机的框架),而 Room ,作为一个 SQL 包装工具,仅仅占用了 50KB 的大小。但是为秉承安卓开发者一贯作风,我们还必须针对那烦人的方法数量限制进行一下统计。在这个方法层面上, Room 那谦虚的 300 个方法数 看起来又要再次领跑比赛了。接着是有着 1300 个方法的 ObjectBox 和 2000 个方法的 realm。

明智的是,这几个竞争者都各自提供了一些额外的特性。 Room 提供了 SQLite 所能做的一切,并附加了一些其他的功能。比如迁移机制,而且这完全是可以进行测试的。相反, ObjectBox 甚至都不需要这个,因为它会自动处理大部分情况下的迁移(尽管对于某些改变,它需要额外的信息来明确目标)。 Realm 则装备了最多的令人惊奇的武器,它的这些特性包括自定义配置,加密和更多其他功能(这也是它尺寸比较大的原因之一)。

  • 结论

我们可以看到,不论你选择了哪条路,它都有着自己的长处和短处。如果你需要速度和效率,很明显 ObjectBox 是一个不错的选择。然而,如果你被应用的大小所限制,被那 64k 个方法所限制,同时你也愿意去使用 SQL 语句,那么 Room 是一个很好的解决方案。另一方面,针对 Realm ,可能不是最快的,也不是最小的,但是在它们背后,在经历了超过 7 年的问题反馈检测和改进之后,它给大家提供的是最稳定的、无 bug 的、理智的解决方案。

至于选择哪一个,这取决于你,但是请记住,一个应用程序只要你选择对了就足够了(当然也取决于你的代码,但是那又是另一个话题了)

三、完

作者:Radoslav Yankov
平台:Dev Labs
标签: Android Objectbox Realm Room Comparison


Comments: