Realm Java
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/11166066.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
入门
先决条件
- Android Studio 1.5.1或更高版本
- JDK 7.0或更高版本
- 最新版本的Android SDK
- Android API等级9或更高(Android 2.3及更高版本)
注意: Realm不支持Android之外的Java。我们不再支持Eclipse作为IDE; 请迁移到Android Studio。
安装
将Realm安装为Gradle插件。
步骤1:将类路径依赖项添加到项目级build.gradle
文件。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:5.12.0"
}
}
在build.gradle
此处查找项目级别文件:
第2步:将realm-android
插件应用到应用程序级build.gradle
文件的顶部。
apply plugin: 'realm-android'
在build.gradle
此处查找应用程序级别文件:
完成这两项更改后,只需刷新gradle依赖项即可。如果您从早期版本的Realm升级v0.88
,则可能还需要清理gradle项目(./gradlew clean
)。
build.gradle
在此处查找两个已修改文件的示例:
您是否希望使用Realm Mobile Platform同步所有Realm数据库?所有与同步相关的文档已移至我们的平台文档中
其他构建系统
不支持Maven和Ant构建系统。我们正在跟踪在GitHub上支持它们的兴趣:
ProGuard配置作为Realm库的一部分提供。这意味着您无需向ProGuard配置添加任何Realm特定规则。
样品
Realm Java允许您以安全,持久和快速的方式有效地编写应用程序的模型层。这是它的样子:
// Define your model class by extending RealmObject
public class Dog extends RealmObject {
private String name;
private int age;
// ... Generated getters and setters ...
}
public class Person extends RealmObject {
@PrimaryKey
private long id;
private String name;
private RealmList<Dog> dogs; // Declare one-to-many relationships
// ... Generated getters and setters ...
}
// Use them like regular java objects
Dog dog = new Dog();
dog.setName("Rex");
dog.setAge(1);
// Initialize Realm (just once per application)
Realm.init(context);
// Get a Realm instance for this thread
Realm realm = Realm.getDefaultInstance();
// Query Realm for all dogs younger than 2 years old
final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => 0 because no dogs have been added to the Realm yet
// Persist your data in a transaction
realm.beginTransaction();
final Dog managedDog = realm.copyToRealm(dog); // Persist unmanaged objects
Person person = realm.createObject(Person.class); // Create managed objects directly
person.getDogs().add(managedDog);
realm.commitTransaction();
// Listeners will be notified when data changes
puppies.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Dog>>() {
@Override
public void onChange(RealmResults<Dog> results, OrderedCollectionChangeSet changeSet) {
// Query results are updated in real time with fine grained notifications.
changeSet.getInsertions(); // => [0] is added.
}
});
// Asynchronously update objects on a background thread
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
Dog dog = bgRealm.where(Dog.class).equalTo("age", 1).findFirst();
dog.setAge(3);
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// Original queries and Realm objects are automatically updated.
puppies.size(); // => 0 because there are no more puppies younger than 2 years old
managedDog.getAge(); // => 3 the dogs age is updated
}
});
浏览Realm数据库
如果您在查找应用程序的Realm文件时需要帮助,请查看此StackOverflow答案以获取详细说明。
Realm Studio
Realm Studio是我们的首选开发人员工具,可以轻松管理Realm数据库和Realm平台。使用Realm Studio,您可以打开和编辑本地和同步的域,并管理任何Realm Object Server实例。它支持Mac,Windows和Linux。
Stetho Realm
您还可以使用Stetho的Stetho-Realm插件,这是由Facebook创建的Chrome浏览器的Android调试桥。
Stetho-Realm并非由Realm正式维护。
初始化领域
在应用程序中使用Realm之前,必须先将其初始化。这只需要做一次。
Realm.init(context);
您必须提供Android context
。初始化Realm的好地方是onCreate
应用程序子类:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
}
}
如果您创建自己的应用程序子类,则必须将其添加到应用程序AndroidManifest.xml
:
<application
android:name=".MyApplication"
...
/>
三界
一个境界是一种境界移动数据库容器的一个实例。领域可以是本地的或同步的。同步的Realm使用Realm Object Server透明地将其内容与其他设备同步。当您的应用程序继续使用同步Realm时,就像它是本地文件一样,该Realm中的数据可能会被具有该Realm写入权限的任何设备更新。实际上,您的应用程序可以以同样的方式使用任何Realm,本地或同步。
您是否希望使用Realm Mobile Platform同步所有Realm数据库?所有与同步相关的文档已移至我们的平台文档中
有关Realms的更详细讨论,请阅读Realm Data Model。
开放的领域
通过实例化一个新Realm
对象来打开一个领域。我们已经在示例中看到过这种情况:
// Initialize Realm
Realm.init(context);
// Get a Realm instance for this thread
Realm realm = Realm.getDefaultInstance();
该getDefaultInstance
方法使用默认值实例化Realm RealmConfiguration
。
配置领域
要控制如何创建领域,请使用RealmConfiguration
对象。Realm可用的最小配置是:
RealmConfiguration config = new RealmConfiguration.Builder().build();
该配置 - 没有选项 - 使用default.realm
位于的Realm文件Context.getFilesDir
。要使用其他配置,您需要创建一个新RealmConfiguration
对象:
// The RealmConfiguration is created using the builder pattern.
// The Realm file will be located in Context.getFilesDir() with name "myrealm.realm"
RealmConfiguration config = new RealmConfiguration.Builder()
.name("myrealm.realm")
.encryptionKey(getKey())
.schemaVersion(42)
.modules(new MySchemaModule())
.migration(new MyMigration())
.build();
// Use the config
Realm realm = Realm.getInstance(config);
您可以拥有多个RealmConfiguration
对象,因此您可以独立控制每个Realm的版本,架构和位置。
RealmConfiguration myConfig = new RealmConfiguration.Builder()
.name("myrealm.realm")
.schemaVersion(2)
.modules(new MyCustomSchema())
.build();
RealmConfiguration otherConfig = new RealmConfiguration.Builder()
.name("otherrealm.realm")
.schemaVersion(5)
.modules(new MyOtherSchema())
.build();
Realm myRealm = Realm.getInstance(myConfig);
Realm otherRealm = Realm.getInstance(otherConfig);
使用Realm.getPath获取Realm的绝对路径。
重要的是要注意Realm
实例是线程单例,这意味着静态构造函数将返回相同的实例以响应来自给定线程的所有调用。
默认领域
该RealmConfiguration
可以保存为默认配置。在自定义Application类中设置默认配置使其在其余代码中可用。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// The default Realm file is "default.realm" in Context.getFilesDir();
// we'll change it to "myrealm.realm"
Realm.init(this);
RealmConfiguration config = new RealmConfiguration.Builder().name("myrealm.realm").build();
Realm.setDefaultConfiguration(config);
}
}
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Realm realm = Realm.getDefaultInstance(); // opens "myrealm.realm"
try {
// ... Do something ...
} finally {
realm.close();
}
}
}
打开同步领域
您是否希望使用Realm Mobile Platform同步所有Realm数据库?所有与同步相关的文档已移至我们的平台文档中
只读领域
readOnly
仅在当前流程中强制执行。其他进程或设备仍可以写入readOnly
Realms。此外,任何针对只读Realm的写入事务都将抛出IllegalStateException
。这包括尝试编写模式,因此必须首先由其他来源提供。
使用您的应用程序发送准备好的Realm文件有时很有用 - 您可能希望将一些共享数据与您的应用程序捆绑在一起。在许多情况下,您不希望意外地修改该Realm,因为数据纯粹是只读的。您可以通过在资产中捆绑Realm文件并使用readOnly
配置来执行此操作:
RealmConfiguration config = new RealmConfiguration.Builder()
.assetFile("my.realm")
.readOnly()
// It is optional, but recommended to create a module that describes the classes
// found in your bundled file. Otherwise if your app contains other classes
// than those found in the file, it will crash when opening the Realm as the
// schema cannot be updated in read-only mode.
.modules(new BundledRealmModule())
.build();
内存领域
使用inMemory
配置,您可以创建一个完全在内存中运行而不会持久保存到磁盘的Realm。
RealmConfiguration myConfig = new RealmConfiguration.Builder()
.name("myrealm.realm")
.inMemory()
.build();
如果内存不足,内存领域仍可能使用磁盘空间,但在关闭领域时,内存领域创建的所有文件都将被删除。不允许创建与持久Realm同名的内存域 - 名称仍然必须是唯一的。
当具有特定名称的所有内存中Realm实例超出范围而没有引用时,这将释放所有Realm的数据。要在应用程序执行期间保持内存中的Realm“活着”,请保留对它的引用。
动态领域
使用常规时Realm
,使用RealmObject
子类定义模型类。这在类型安全方面具有很多好处。但有时,类型在运行时才可用,例如,在迁移期间或使用基于字符串的数据(如CSV文件)时。动态领域的救援!
甲DynamicRealm是以往的变体Realm
,使得它能够以域数据,而无需使用工作RealmObject
子类。相反,所有访问都是使用字符串而不是类来完成的。
打开Dynamic Realm使用与传统Realm相同的配置,但Dynamic Realm忽略任何已配置的架构,迁移和架构版本。
RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);
// In a DynamicRealm all objects are DynamicRealmObjects
realm.beginTransaction();
DynamicRealmObject person = realm.createObject("Person");
realm.commitTransaction();
// All fields are accessed using strings
String name = person.getString("name");
int age = person.getInt("age");
// An underlying schema still exists, so accessing a field that does not exist
// will throw an exception
person.getString("I don't exist");
// Queries still work normally
RealmResults<DynamicRealmObject> persons = realm.where("Person")
.equalTo("name", "John")
.findAll();
一个DynamicRealm
在两个类型安全和性能为代价的收益灵活性; 一般来说,你应该使用普通的领域。只有在需要灵活性时才使用Dynamic Realms。
关闭领域
Realm
实现Closeable
以处理本机内存释放和文件描述符,因此在完成它们时总是关闭它们。
Realm
实例是引用计数 - 如果你getInstance
在一个线程中调用两次,你也需要调用close
两次。这允许您实现Runnable
类而不必担心哪个线程将执行它们:只需启动它getInstance
并以结束它close
。
对于UI线程,最简单的方法是realm.close
在拥有组件的onDestroy
方法中执行。如果需要创建Looper
UI以外的线程,可以使用以下模式:
public class MyThread extends Thread {
private Realm realm;
@Override
public void run() {
Looper.prepare();
realm = Realm.getDefaultInstance();
try {
//... Setup the handlers using the Realm instance ...
Looper.loop();
} finally {
realm.close();
}
}
}
对于AsyncTask
这是一个很好的模式:
protected Void doInBackground(Void... params) {
Realm realm = Realm.getDefaultInstance();
try {
// ... Use the Realm instance ...
} finally {
realm.close();
}
return null;
}
如果您正在使用Thread
或Runnable
用于短期任务:
// Run a non-Looper thread with a Realm instance.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Realm realm = Realm.getDefaultInstance();
try {
// ... Use the Realm instance ...
} finally {
realm.close();
}
}
});
thread.start();
如果您正在使用minSdkVersion >= 19
和Java >= 7
使用应用程序,那么您可以使用try-with-resources:
try (Realm realm = Realm.getDefaultInstance()) {
// No need to close the Realm instance manually
}
自动刷新
如果从与Looper关联的线程获取Realm实例,则Realm实例会附带自动刷新功能。(Android的UI线程是一个Looper。)这意味着Realm实例将定期更新到最新版本。这使您可以毫不费力地使用最新内容不断更新UI!
如果你从没有一个线程获得一个领域实例不是有一个Looper
连接,从该实例对象将不会被直到调用更新waitForChange
方法。坚持使用旧版本的数据在内存和磁盘空间方面是昂贵的,并且成本会随着保留和最新版本之间的版本数量而增加。这就是为什么在线程中完成它后立即关闭Realm实例很重要的原因。
如果要检查Realm实例是否已激活自动刷新,请使用该isAutoRefresh
方法。
楷模
通过扩展RealmObject基类来创建Realm模型:
public class User extends RealmObject {
private String name;
private int age;
@Ignore
private int sessionId;
// Standard getters & setters generated by your IDE…
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public int getSessionId() { return sessionId; }
public void setSessionId(int sessionId) { this.sessionId = sessionId; }
}
一个领域模型类支持public
,protected
和private
领域,以及自定义方法。
public class User extends RealmObject {
public String name;
public boolean hasLongName() {
return name.length() > 7;
}
@Override
public boolean equals(Object o) {
// Custom equals comparison
}
}
字段类型
境界支持boolean
,byte
,short
,int
,long
,float
,double
,String
,Date
和byte[]
字段类型。整数类型byte
,short
,int
,并且long
都被映射到long
领域内。除了那些标准字段类型之外,Realm还支持子类RealmObject
和RealmList<? extends RealmObject>
模型关系。
盒装类型Boolean
,Byte
,Short
,Integer
,Long
,Float
和Double
也可以在模型中的类使用。这些类型可能具有价值null
。
必填字段
该@Required
注释可以用来告诉境界不允许null
在一个字段的值,使之需要,而不是可选的。只有Boolean
,Byte
,Short
,Integer
,Long
,Float
,Double
,String
,byte[]
并且Date
可以进行注释@Required
。如果将其添加到其他字段类型,编译将失败。
RealmList
隐式地需要具有基本类型和类型的字段。RealmObject
类型的字段始终可以为空。
主键
要将字段标记为模型的主键,请使用注释@PrimaryKey
。字段类型必须是一个字符串(String
)或整数(byte
,short
,int
,long
,Byte
,Short
,Integer
,和Long
)。使用字符串字段作为主键会自动为字段编制索引:@PrimaryKey
字符串上的注释会隐式设置注释@Index
。Realm不支持复合键,即使用多个字段作为单个主键。
使用主键可以使用copyToRealmOrUpdate
或insertOrUpdate
方法。它们查找具有给定主键的对象,并更新它(如果具有该键的对象已存在)或创建它(如果该键不存在)。如果您在没有主键的情况下调用copyToRealmOrUpdate
或insertOrUpdate
上课,则会抛出异常。
当您使用主键时,读取(查询)会稍微快一些,但写入(创建和更新对象)会慢一些。性能的变化取决于Realm数据集的大小。
请注意,Realm.createObject
返回一个新对象,其所有字段都设置为其默认值。如果对象是具有主键的类,则可能会产生冲突 - 可能存在具有该主键集的对象。为避免这种情况,您可以创建一个非托管对象,设置其字段值,然后使用copyToRealm
或将其添加到Realm insert
:
final MyObject obj = new MyObject();
obj.setId(42);
obj.setName("Fish");
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// This will create a new object in Realm or throw an exception if the
// object already exists (same primary key)
// realm.copyToRealm(obj);
// This will update an existing object with the same primary key
// or create a new object if an object with no primary key = 42
realm.copyToRealmOrUpdate(obj);
}
});
主键是String
类型或盒装整数(Byte
,Short
,Integer
,和Long
)的值可以是null
,除非@PrimaryKey
注释与组合@Required
。
索引属性
要索引字段,请使用注释@Index
。与主键一样,这会使写入速度稍慢,但会使读取速度更快。(它还会使您的Realm文件略大,以存储索引。)最好只在优化特定情况下的读取性能时添加索引。
你可以索引String
,byte
,short
,int
,long
,boolean
和Date
领域。
忽略属性
如果您不想将模型中的字段保存到其Realm,请使用注释@Ignore
。例如,如果您的输入包含的字段多于模型,并且您不希望有许多特殊情况来处理这些未使用的数据字段,则可以执行此操作。
字段标static
和transient
总是被忽略,并且不需要@Ignore
注释。
计数器
Realm提供MutableRealmInteger作为特殊整数类型。MutableRealmInteger
公开了一个额外的API,可以更清楚地表达意图,并在使用Synchronized Realms时生成更好的冲突解决步骤。
传统上,计数器将通过读取值,递增并设置(myObj.counter += 1
)来实现。这在异步情况下无法正常工作 - 例如,当两个客户端处于脱机状态时 - 因为双方都会读取一个值,比如10
增加它,并将值存储为11
。最终,当他们重新获得连接并尝试合并他们的更改时,他们会同意计数器处于11
预期状态而不是预期状态12
。
MutableRealmInteger
S被传统的整数类型的支持,所以从改变字段时就不需要进行迁移byte
,short
,int
或long
到MutableRealmInteger
。
MutableRealmInteger
不是像Java中的原始数字类型那样的不可变类型标准。这是一个活生生的物体一样RealmObject
,RealmResults
和RealmList
。这意味着MutableRealmInteger
当写入Realm时,包含在其中的值会发生变化。因此,MutableRealmInteger
必须标记字段final
。
public class Party extends RealmObject {
{
public final MutableRealmInteger guests = MutableRealmInteger.valueOf(0);
}
要更改计数器值,只需调用counter.increment()
或counter.decrement()
。
Party party = realm.where(Party.class).findFirst();
realm.beginTransaction();
party.guests.get(); // 0
party.guests.increment(1); // 1
party.guests.decrement(1); // 0
party.guests.increment(5); // 5
party.guests.decrement(1); // 4
realm.commitTransaction();
要重置计数器,您可以使用分配新值counter.set()
。
调用set()
有可能覆盖increment()
和decrement()
业务来自其他设备来公关。正常的last-write-wins合并规则,因此只有在有损计数器可以接受的情况下才能进行混合操作。
Party party = realm.where(Party.class).findFirst();
realm.beginTransaction();
party.guests.set(0);
realm.commitTransaction();
覆盖属性名称
默认行为是Realm将使用Java模型类中定义的名称作为名称来表示Realm文件内部的类和字段。在某些情况下,您可能希望更改此行为:
- 支持具有相同简单名称但在不同包中的两个模型类。
- 为了更容易使用跨平台模式,因为命名约定是不同的。
- 使用长度超过Realm强制执行的57个字符限制的Java类名。
- 在Java中更改字段名称而不强制应用程序用户完成迁移过程。
在这些情况下,您可以覆盖被定义使用不同的名称在内部使用的名称@RealmModule
,@RealmClass
或@RealmField
注解。
您可以在模块级别定义命名策略,这将影响模块的所有类部分:
@RealmModule(
allClasses = true,
classNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES,
fieldNamingPolicy = RealmNamingPolicy.LOWER_CASE_WITH_UNDERSCORES
)
public class MyModule {
}
您可以为类或字段命名策略定义将影响该类中所有字段的自定义名称。这将覆盖任何模块级别设置:
@RealmClass(name = "__Person", fieldNamingPolicy = RealmNamingPolicy.PASCAL_CASE)
public class Person extends RealmObject {
public String name;
}
您可以为字段定义自定义名称,这将覆盖任何类和模块级别设置:
public class extends RealmObject {
@RealmField(name = "person_name")
public String name;
}
选择与Java模型类中使用的名称不同的内部名称具有以下含义:
- DynamicRealm上的查询必须使用内部名称。普通Realm实例上的查询必须继续使用Java类中定义的名称。
- 创建类和字段时,迁移必须使用内部名称。
- 报告的架构错误将使用内部名称。
请注意,更改内部名称不会不影响从JSON数据导入。JSON数据仍必须遵循Realm Java类中定义的名称。
在使用Moshi,GSON或Jackson等标准库解析JSON时。然后重要的是要记住,这些库定义了从JSON到Java的转换,同时设置内部Realm名称定义了从Java到Realm文件的转换。这意味着如果要使用这些库从JSON将数据导入Realm,您仍需要提供JSON解析器库和Realm的注释。
使用Moshi,它看起来像这样:
public class Person extends RealmObject {
@Json(name = "first_name") // Name used in JSON input.
@RealmField(name = "first_name") // Name used internally in the Realm file.
public string firstName; // name used in Java
}
有关详细信息,请参阅RealmNamingPolicy。
使用RealmObjects
自动更新对象
RealmObject
s是对基础数据的实时,自动更新视图; 你永远不必刷新对象。对象的更改会立即反映在查询结果中。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Dog myDog = realm.createObject(Dog.class);
myDog.setName("Fido");
myDog.setAge(1);
}
});
Dog myDog = realm.where(Dog.class).equalTo("age", 1).findFirst();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Dog myPuppy = realm.where(Dog.class).equalTo("age", 1).findFirst();
myPuppy.setAge(2);
}
});
myDog.getAge(); // => 2
这不仅可以保持Realm的快速和高效,还可以使您的代码更简单,更具反应性。如果您的Activity或Fragment依赖于特定RealmObject
或RealmResults
实例,则在更新UI之前无需担心刷新或重新获取它。
您可以订阅Realm通知以了解Realm数据何时更新。
自定义对象
可以使用RealmObject
几乎像POJO。扩展您的课程RealmObject
。您可以让字段公开,并且可以使用简单的分配而不是setter和getter。
public class Dog extends RealmObject {
public String name;
public int age;
}
您可以Dog
像使用任何其他类一样使用:您可以为getter和setter方法添加逻辑(例如,用于验证),并且可以添加任何您想要的自定义方法。
要将Dog
对象添加到Realm,请使用createObject
或copyToRealm
方法:
realm.executeTransaction(new Realm.Transaction() {
@Overrride
public void execute(Realm realm) {
Dog dog = realm.createObject(Dog.class);
dog.name = "Fido";
dog.age = 5;
}
};
RealmModel接口
RealmObject
您的类可以实现RealmModel接口,而不是扩展,添加@RealmClass
注释:
@RealmClass
public class User implements RealmModel {
}
使用此接口,RealmObject
可通过静态方法获得所有可用的方法。请注意,扩展的类RealmObject
不需要@RealmClass
注释或实现RealmModel
。
// With RealmObject
user.isValid();
user.addChangeListener(listener);
// With RealmModel
RealmObject.isValid(user);
RealmObject.addChangeListener(user, listener);
JSON
您可以添加映射RealmObject
到Realm 的JSON对象。JSON对象可以是a String
,JSONObject或InputStream。Realm将忽略未定义的JSON中的任何属性RealmObject
。通过Realm.createObjectFromJson添加单个对象,并通过[Realm.createAllFromJson] [api / io / realm / Realm.html#createAllFromJson-java.lang.Class-java.lang.String-)添加对象列表。
// A RealmObject that represents a city
public class City extends RealmObject {
private String city;
private int id;
// getters and setters left out ...
}
// Insert from a string
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");
}
});
// Insert multiple items using an InputStream
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
try {
InputStream is = new FileInputStream(new File("path_to_file"));
realm.createAllFromJson(City.class, is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
如果JSON对象中null
的字段是,并且Realm模型需要该字段,则Realm将抛出异常。如果该字段是可选字段,则在创建对象时以及null
更新对象时,其值将设置为字段默认值。如果Realm模型具有JSON对象中不存在的字段,则该值将在Realm模型中保持不变。
适配器
领域提供抽象的实用工具类,帮助绑定的数据来自何处OrderedRealmCollection
S(两者RealmResults
并RealmList
实现该接口)标准的UI部件。
- 使用RealmBaseAdapter用
ListView
。看一个例子。 - 使用RealmRecyclerViewAdapter用
RecyclerView
。看一个例子。
要使用适配器,请将依赖项添加到应用程序级别build.gradle
:
dependencies {
compile 'io.realm:android-adapters:2.1.1'
}
适配器的Javadoc可以在这里找到,可以在这里找到它们的使用示例。
意图
由于RealmObject
s不是Parcelable
也不能直接传递,因此必须为您正在使用的对象传递标识符。例如,如果对象具有主键,则传递Intent extras包中的主键值:
// Assuming we had a person class with a @PrimaryKey on the 'id' field ...
Intent intent = new Intent(getActivity(), ReceivingService.class);
intent.putExtra("person_id", person.getId());
getActivity().startService(intent);
从接收端的bundle(Activity,Service,IntentService,BroadcastReceiver等)中检索主键值,然后打开一个Realm并查询RealmObject
:
// in onCreate(), onHandleIntent(), etc.
String personId = intent.getStringExtra("person_id");
Realm realm = Realm.getDefaultInstance();
try {
Person person = realm.where(Person.class).equalTo("id", personId).findFirst();
// do something with the person ...
} finally {
realm.close();
}
在另一个线程上重新打开Realm的开销非常小。
您可以Object Passing
在线程示例的部分中找到工作示例。该示例向您展示如何传递id并检索RealmObject
常见的Android用例。
关系
您可以将任意两个RealmObject链接在一起。Realm中的关系很便宜:遍历链接在速度或内存方面并不昂贵。让我们探索不同类型的关系,Realm允许您在对象之间进行定义。
许多到一
要设置多对一或一对一关系,请为模型提供其类型为您的RealmObject
子类之一的属性:
public class Email extends RealmObject {
private String address;
private boolean active;
}
public class Contact extends RealmObject {
private String name;
private Email email;
}
Contact bob = realm.createObject(Contact.class);
bob.name = "Bob Newhart";
Email email1 = realm.createObject(Email.class);
email1.address = "bob@example.com";
bob.email = email1;
每个Contact
都有零个或一个Email
实例。什么都不会阻止你使用多个相同的Email
对象Contact
; 多对一和一对一关系之间的区别取决于您的应用程序。
设置关系字段null
将清除引用:
bob.email = null;
这将删除之间的关系bob
和email1
,但email1
仍处于境界。
许多一对多
您可以通过RealmList<T>
字段声明从单个对象创建与任意数量对象的关系。让我们重写我们的示例以支持多个电子邮件地址:
public class Contact extends RealmObject {
public String name;
public RealmList<Email> emails;
}
public class Email extends RealmObject {
public String address;
public boolean active;
}
RealmList
s是s的容器RealmObject
; 一个RealmList
行为就像一个普通的Java List
。您可以在不同的RealmList
s中使用相同的对象,并且可以使用它来模拟一对多和多对多关系。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
Contact contact = realm.createObject(Contact.class);
contact.name = "John Doe";
Email email1 = realm.createObject(Email.class);
email1.address = "john@example.com";
email1.active = true;
contact.emails.add(email1);
Email email2 = realm.createObject(Email.class);
email2.address = "jd@example.com";
email2.active = false;
contact.emails.add(email2);
}
});
可以声明递归关系,这在建模某些类型的数据时非常有用。
public class Person extends RealmObject {
public String name;
public RealmList<Person> friends;
// Other fields...
}
将值设置null
为RealmList
字段将清除列表。列表将为空(长度为零),但列表中的对象不会从Realm中删除。RealmList
永远不会返回a的getter null
:返回的对象始终是一个列表。长度可能为零。
反向关系
关系是单向的。就拿两个类Person
,并Dog
作为一个例子:
public class Dog extends RealmObject {
private String name;
private int age;
}
public class Person extends RealmObject {
@PrimaryKey
private long id;
private String name;
private RealmList<Dog> dogs;
}
您可以按照从a Person
到a 的链接Dog
,但无法从a Dog
到其Person
对象。您可以通过为Dog添加@LinkingObjects
注释来解决此问题。
public class Person extends RealmObject {
private String id;
private String name;
private RealmList<Dog> dogs;
// getters and setters
}
public class Dog extends RealmObject {
private String id;
private String name;
private String color;
@LinkingObjects("dogs")
private final RealmResults<Person> owners;
// getters and setters
}
我们给Dog
了一个owners
字段,并指定它应该包含在其字段Person
中具有此Dog
对象的所有对象dogs
。
必须声明带注释的字段final
,并且必须是类型RealmResults<T>
,其中T
是关系的另一端的类型/类。由于关系是多对一或多对多,因此遵循反向关系可能会产生0,1个或更多对象。
与任何其他RealmResults
集合一样,您可以查询反向关系。
原始列表
领域模型类可以包含原始数据类型的列表。这必须通过建模RealmList<T>
,其中T
:可以是以下类型String
,Integer
,Boolean
,Float
,Double
,Short
,Long
,Byte
,byte[]
和Date
。
public class Person extends RealmObject {
public String name;
public RealmList<String> children = new RealmList<>();
}
与RealmModel列表不同,基元列表可以包含空值。如果不允许使用空值,请使用@Required
注释:
public class Person extends RealmObject {
public String name;
@Required
public RealmList<String> children = new RealmList<>();
}
原语列表不支持列表列表和查询。
在Realm Java 4.0.0之前,使用特殊Realm<String/Int>
类对基元列表进行建模是很常见的。您可以使用以下迁移代码从此方法迁移到基元列表:
// Model classes
public class RealmString extends RealmObject {
public String value;
}
public class Person extends RealmObject {
public String name;
@Required
public RealmList<String> children = new RealmList<>();
}
// Migration code
RealmObjectSchema objSchema = realmSchema.get("Person");
objSchema.addRealmListField("children_tmp", String.class)
.setRequired("children_tmp", true)
.transform(new RealmObjectSchema.Function() {
@Override
public void apply(DynamicRealmObject obj) {
RealmList<DynamicRealmObject> children = obj.getList("children");
RealmList<String> migratedChildren = obj.getList("children_tmp", String.class);
for (DynamicRealmObject child : children) {
migratedChildren.add(child.getString("value"));
}
}
})
.removeField("children")
.renameField("children_tmp", "children");
架构
Realm的默认模式只是项目中的所有Realm模型类。但是,您可以更改此行为 - 例如,您可能希望将Realm限制为仅包含类的子集。为此,请创建自定义RealmModule。
// Create the module
@RealmModule(classes = { Person.class, Dog.class })
public class MyModule {
}
// Set the module in the RealmConfiguration to allow only classes defined by the module.
RealmConfiguration config = new RealmConfiguration.Builder()
.modules(new MyModule())
.build();
// It is possible to combine multiple modules to one schema.
RealmConfiguration config = new RealmConfiguration.Builder()
.modules(new MyModule(), new MyOtherModule())
.build();
对于库开发人员:包含Realm的库必须通过RealmModule公开和使用他们的模式。这样做可以防止RealmModule
为库项目生成默认值,这会违反RealmModule
应用程序使用的默认值。该库RealmModule
也是库如何将其Realm类暴露给应用程序。
// A library must create a module and set library = true. This will prevent the default
// module from being created.
// allClasses = true can be used instead of listing all classes in the library.
@RealmModule(library = true, allClasses = true)
public class MyLibraryModule {
}
// Library projects are therefore required to explicitly set their own module.
RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
.name("library.realm")
.modules(new MyLibraryModule())
.build();
// Apps can add the library RealmModule to their own schema.
RealmConfiguration config = new RealmConfiguration.Builder()
.name("app.realm")
.modules(Realm.getDefaultModule(), new MyLibraryModule())
.build();
您不能RealmModule
在单个文件中包含多个声明。如果您有两个或更多个RealmModule
s,则必须将声明拆分为多个文件,每个文件只有一个声明。
在此处查看 RealmModules如何在库和应用程序项目之间工作的完整示例。
写
与读取操作不同,Realm中的写入操作必须包含在事务中。在写入操作结束时,您可以提交事务或取消它。提交事务会将所有更改写入磁盘(如果同步了Realm,则将其排队以与Realm Object Server同步)。如果取消写入事务,则会丢弃所有更改。事务是“全有或全无”:事务中的所有写入都成功,或者它们都不生效。这有助于保证数据的一致性,并提供线程安全性。
// Obtain a Realm instance
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
//... add or update objects here ...
realm.commitTransaction();
或者通过取消交易来放弃更改:
realm.beginTransaction();
User user = realm.createObject(User.class);
// ...
realm.cancelTransaction();
写事务相互阻塞。如果同时在UI和后台线程上创建写入事务,则可能导致ANR错误。要避免这种情况,请在UI线程上创建写入事务时使用异步事务。
如果事务内发生异常,您将丢失该事务中的更改,但Realm本身不会受到影响(或损坏)。如果您捕获异常并且应用程序继续,则您需要取消该事务。如果使用executeTransaction,则会自动执行此操作。
由于Realm的MVCC架构,在写事务打开时不会阻止读取。除非您需要同时从多个线程同时进行事务,否则您可以支持更大的事务,这些事务可以在许多细粒度事务上完成更多工作。当您向Realm提交写入事务时,该Realm的所有其他实例将被通知并自动更新。
Realm中的读写访问权限是ACID。
创建对象
将createObject
方法包装在写入事务中。
realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("john@corporation.com");
realm.commitTransaction();
如果首先创建对象实例并使用copyToRealm
它将其添加到Realm,则应将复制操作包装在事务中。Realm支持任意数量的自定义构造函数,只要其中一个是公共无参数构造函数即可。
User user = new User("John");
user.setEmail("john@corporation.com");
// Copy the object to Realm. Any further changes must happen on realmUser
realm.beginTransaction();
User realmUser = realm.copyToRealm(user);
realm.commitTransaction();
请记住,Realm只管理返回的对象(realmUser
在本例中),而不是最初复制的对象(user
)。要更改数据库中的对象,请更改返回的副本,而不是原始副本。
如果您只是插入对象而不是立即使用托管副本,则可以使用insert
。这种方式与此类似,copyToRealm
但速度要快得多,因为不返回对象可以更好地对其进行优化。
如果要插入许多对象,建议的方法是使用insert
或insertOrUpdate
。
List<User> users = Arrays.asList(new User("John"), new User("Jane"));
realm.beginTransaction();
realm.insert(users);
realm.commitTransaction();
交易块
不用手动保持的跟踪beginTransaction
,commitTransaction
以及cancelTransaction
,你可以使用executeTransaction方法,它会自动处理开始/提交,如果发生错误的时候取消。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
});
异步事务
由于事务被其他事务阻止,您可能希望在后台线程上编写以避免阻塞UI线程。通过使用异步事务,Realm将在后台线程上运行该事务。
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
// Transaction was a success.
}
}, new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
}
});
OnSuccess
和OnError
回调都是可选的,但如果提供,它们将分别在事务成功或失败时被调用。回调由the控制Looper
,因此只允许在Looper线程上使用。
RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
User user = bgRealm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
}, null);
RealmAsyncTask
如果您需要在事务完成之前退出Activity / Fragment,该对象可以取消任何挂起的事务。如果回调更新UI,忘记取消事务可能会导致应用程序崩溃!
public void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}
更新字符串和字节数组
由于Realm作为一个整体在字段上运行,因此无法直接更新字符串或字节数组的各个元素。相反,您需要读取整个字段,对单个元素进行修改,然后在事务块中再次将其写回。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
bytes[] bytes = realmObject.binary;
bytes[4] = 'a';
realmObject.binary = bytes;
}
});
批量更新
如果需要一次更新多个对象,那么最有效的方法是创建查找对象并使用RealmResults.setX()
方法之一的查询,其中X
是要更新的字段类型。
// Updating a boolean field
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
RealResults<Person> persons = realm.where(Person.class).equalTo("invited", false).findAll();
persons.setBoolean("invited", true);
}
});
也可以使用调用的通用set方法RealmResults.setValue(String fieldName, Object value)
,该方法自动检测输入的类型并根据需要进行转换。这类似于行为DynamicRealmObject.setX()
和DynamicRealmObject.setValue()
行为。使用RealmResults.setValue()
比使用特定类型的方法慢。
// Updating a boolean field using automatic input conversion as needed.
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
RealResults<Person> persons = realm.where(Person.class).equalTo("invited", false).findAll();
persons.setValue("invited", "true");
}
});
只能通过这种方式直接在对象上更新字段。无法使用这些方法更新链接的字段或列表元素。
查询
所有提取(包括查询)在Realm中都是惰性的,并且永远不会复制数据。
Realm的查询引擎使用Fluent接口来构造多子句查询。
public class User extends RealmObject {
@PrimaryKey
private String name;
private int age;
@Ignore
private int sessionId;
// Standard getters & setters generated by your IDE…
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public int getSessionId() { return sessionId; }
public void setSessionId(int sessionId) { this.sessionId = sessionId; }
}
要查找名为John或Peter的所有用户,您需要写:
// Build the query looking at all users:
RealmQuery<User> query = realm.where(User.class);
// Add query conditions:
query.equalTo("name", "John");
query.or().equalTo("name", "Peter");
// Execute the query:
RealmResults<User> result1 = query.findAll();
// Or alternatively do the same all at once (the "Fluent interface"):
RealmResults<User> result2 = realm.where(User.class)
.equalTo("name", "John")
.or()
.equalTo("name", "Peter")
.findAll();
这将为您提供该类的新实例RealmResults
,其中包含名为John或Peter的用户。
该方法findAll
执行查询; [ RealmQuery ] []包含一系列findAll
方法:
findAll
查找满足查询条件的所有对象findAllAsync
在后台线程上异步操作findFirst
(和findFirstAsync
)找到满足查询条件的第一个对象
有关完整的详细信息,请深入了解RealmQuery API参考。
查询返回匹配对象的引用列表,因此您可以直接使用与查询匹配的原始对象。RealmResults
从AbstractList
类似的方式继承和行为。例如,按RealmResults
顺序排列,您可以通过索引访问各个对象。如果查询没有匹配项,则返回的RealmResults
对象将是size(0)
(不null
)的列表。
如果要修改或删除集合中的对象RealmResults
,则必须在写入事务中执行此操作。
请注意,您还可以查询关系:阅读有关链接查询的信息。
过滤
该where
方法RealmQuery
通过指定模型来启动。过滤条件使用谓词方法指定,其中大多数具有不言自明的名称(例如,equalTo
)。谓词始终将字段名称作为其第一个参数。
并非所有谓词都可以与所有字段类型一起使用; 有关详细信息,请参阅[ RealmQuery ] [] API参考。
对于所有数据类型,您具有以下谓词:
equalTo
notEqualTo
in
要将字段与值列表进行匹配,请使用in
。例如,要查找名称“Jill”,“William”或“Trillian”,您可以使用in("name", new String[]{"Jill", "William", "Trillian"})
。的in
谓词是适用于字符串,二进制数据,和数字字段(包括日期)。
数字数据类型(包括Date
)允许使用以下附加谓词:
between
(包括两个终点,即它是一个有界区间)greaterThan
lessThan
greaterThanOrEqualTo
lessThanOrEqualTo
字符串字段允许这些附加谓词:
contains
beginsWith
endsWith
like
所有四个字符串谓词都有一个可选的第三个参数来控制区分大小写:Case.INSENSITIVE
和Case.SENSITIVE
。默认是Case.SENSITIVE
。
谓词like
执行glob样式的通配符匹配。匹配模式由字符和一个或多个通配符组成:
*
匹配0个或更多Unicode字符?
匹配单个Unicode字符
例如,考虑一个带有四个对象的Realm,其中一个字段name
的值为William,Bill,Jill和Trillian。谓词like("name", "?ill*")
将匹配前三个对象,并like("name", "*ia?")
匹配第一个和最后一个对象。
二进制数据,字符串和RealmObject
s(RealmList
)列表可以是空的,即长度为零。您可以通过以下方式检查空虚:
isEmpty
isNotEmpty
如果不需要字段,则该值可以具有该值null
(回想一下,RealmObject
永远不需要s的字段,值可以是null
)。你可以检查null
:
isNull
isNotNull
逻辑运算符
条件与逻辑和隐式连接。必须使用明确应用逻辑或联接or
。
public class User extends RealmObject {
@PrimaryKey
private String name;
private int age;
@Ignore
private int sessionId;
// Standard getters & setters generated by your IDE…
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public int getSessionId() { return sessionId; }
public void setSessionId(int sessionId) { this.sessionId = sessionId; }
}
您还可以使用beginGroup
和endGroup
指定评估顺序对条件进行分组:
RealmResults<User> r = realm.where(User.class)
.greaterThan("age", 10) // implicit AND
.beginGroup()
.equalTo("name", "Peter")
.or()
.contains("name", "Jo")
.endGroup()
.findAll();
否定条件not
。您可以使用not
运算符和beginGroup
/ endGroup
仅取消子条件。如果您想要找到未命名为“Peter”或“Jo”的用户,则查询可能是:
RealmResults<User> r = realm.where(User.class)
.not()
.beginGroup()
.equalTo("name", "Peter")
.or()
.contains("name", "Jo")
.endGroup()
.findAll();
但是,使用此特定查询,它更容易使用in
:
RealmResults<User> r = realm.where(User.class)
.not()
.in("name", new String[]{"Peter", "Jo"})
.findAll();
排序
您可以定义在使用该sort
方法执行查询时应如何对结果进行排序。
RealmResults<User> result = realm.where(User.class).sort("age").findAll();
或者,您可以对Realm已检索的任何结果进行排序:
result = result.sort("age"); // Sort ascending
result = result.sort("age", Sort.DESCENDING);
排序默认为升序; 改变它,Sort.DESCENDING
用作第二个参数。可以同时使用多个字段进行排序。
限制结果
大多数其他数据库技术提供了从查询中“分页”结果的能力(例如SQLite中的'LIMIT'关键字)。这通常是为了避免从磁盘中读取太多内容,或者一次将太多结果拉入内存中。
由于Realm中的查询是惰性的,因此在使用本地Realm文件时通常不需要执行此类分页,因为Realm只会在显式访问后从查询结果中加载对象。
但是,在某些情况下限制结果可能是有益的:
-
使用基于查询的域时,将在查询时从服务器获取数据。在这种情况下,限制结果的数量是有意义的,因为它直接影响从服务器传输的数据量。
-
创建依赖于有限查询结果的UI时,如“前10名列表”。在这种情况下,创建有限的查询结果将降低创建此类屏幕所需的代码的复杂性。
在这些情况下,可以通过将limit()
关键字应用于查询来限制查询结果。
RealmResults<Person> people = realm.where(Person.class)
.sort("name")
.limit(10)
.findAllAsync();
与任何其他查询结果一样,有限查询结果会自动更新。这意味着如果在查询首次返回时返回10个元素,则可以在基础数据集更改时替换或删除这10个对象。
使用细粒度通知时,停止作为其一部分的对象RealmResults
将被报告为已删除。这并不一定意味着它们会从底层Realm中删除,只是它们不再是查询结果的一部分。
关键字distinct()
,sort()
并limit()
会在他们指定的顺序来应用。根据数据集,这可能会对查询结果产生影响。一般来说,limit()
应该最后应用。
尚不支持抵消有限的查询结果。此功能正在此处进行跟踪。
独特的价值观
要仅返回唯一值,请使用distinct
谓词。例如,要了解您的Realm中有多少个不同的名称:
RealmResults<Person> unique = realm.where(Person.class).distinct("name").findAll();
你只能调用distinct
整数和字符串字段; 其他字段类型将引发异常。与排序一样,您可以指定多个字段。
链接查询
您可以对结果集运行其他查询:
RealmResults<Person> teenagers = realm.where(Person.class).between("age", 13, 20).findAll();
Person firstJohn = teenagers.where().equalTo("name", "John").findFirst();
您也可以在子对象上链接查询。假设上面的Person
对象有一个Dog
对象列表。
public class Dog extends RealmObject {
private int age;
// getters & setters ...
}
public class Person extends RealmObject {
private int age;
private RealmList<Dog> dogs;
// getters & setters ...
}
您可以查询年龄在13到20岁之间且至少有一只一岁的狗的所有人:
RealmResults<Person> teensWithPups = realm.where(Person.class).between("age", 13, 20).equalTo("dogs.age", 1).findAll();
请注意,查询链是基于RealmResults
而不是构建的RealmQuery
。向RealmQuery
对象添加更多条件时,您正在修改查询本身。
链接查询
可以查询链接或关系。考虑下面的模型:
public class Person extends RealmObject {
private String id;
private String name;
private RealmList<Dog> dogs;
// getters and setters
}
public class Dog extends RealmObject {
private String id;
private String name;
private String color;
// getters and setters
}
每个Person
对象都有多个狗关系,如下表所示:
现在,我们可以找到具有链接查询的特定人员:
// persons => [U1,U2]
RealmResults<Person> persons = realm.where(Person.class)
.equalTo("dogs.color", "Brown")
.findAll();
字段名称equalTo
是通过关系的路径,使用句点(.
)作为分隔符。上面的查询显示“查找所有拥有颜色为棕色的狗的人。”请注意,结果将包含至少有一个匹配的对象的所有 Dog
对象:Person
Dog
persons.get(0).getDogs(); // => [A,B]
persons.get(1).getDogs(); // => [B,C,D]
请记住,我们正在寻找拥有特定种类狗的人,而不是真正的狗。
让我们深入挖掘一下:
// r1 => [U1,U2]
RealmResults<Person> r1 = realm.where(Person.class)
.equalTo("dogs.name", "Fluffy")
.equalTo("dogs.color", "Brown")
.findAll();
// r2 => [U2]
RealmResults<Person> r2 = realm.where(Person.class)
.equalTo("dogs.name", "Fluffy")
.findAll()
.where()
.equalTo("dogs.color", "Brown")
.findAll()
.where()
.equalTo("dogs.color", "Yellow")
.findAll();
第一个问题是:“找到所有有狗名为'蓬松' 并且有颜色为'布朗'的狗的人。”第二个问题是:“找到所有有狗名为'蓬松'的人。” 在该结果集中,找到所有拥有颜色为“棕色”的狗的人。然后,在该结果集中,找到所有拥有颜色为“黄色”的狗的人。“因此,第一个查询找到两组人员并返回这些集合的交集; 第二个查询以不同的方式运行,通过获取每个查询的结果集findAll
并将其提供给下一个where
查询以连续缩小结果范围。您可以通过链接重写第二个查询:
RealmResults<Person> set1 = realm.where(Person.class).equalTo("dogs.name", "Fluffy").findAll();
RealmResults<Person> set2 = set1.where(Person.class).equalTo("dogs.color", "Brown").findAll();
RealmResults<Person> set3 = set2.where(Person.class).equalTo("dogs.color", "Yellow").findAll();
使用反向关系,您可以扩展查询的可能性。让我们考虑相同的两个模型类,Person
和Dog
。Person
您可以先查询狗,而不是使用反向关系,而不是启动查询。
RealmResults<Dog> brownFluffies = realm.where(Dog.class).equalTo("color", "Brown").equalTo("name", "Fluffy").findAll();
for (Dog brownFluffy : brownFluffies) {
RealmResults<Person> owners = brownFluffy.getOwners();
// ...
}
您还可以使用具有反向关系的链接查询:
RealmResults<Dog> dogs = realm.where(Dog.class).equalTo("persons.name", "Jane").findAll();
自动更新结果
RealmResults
是实时的,自动更新基础数据的视图。如果另一个线程,进程甚至设备修改RealmResults
集合中的对象,则立即反映更改。您的代码无需重新运行查询或手动刷新数据。
final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => 0
realm.executeTransaction(new Realm.Transaction() {
@Override
void public execute(Realm realm) {
Dog dog = realm.createObject(Dog.class);
dog.setName("Fido");
dog.setAge(1);
}
});
puppies.addChangeListener(new RealmChangeListener() {
@Override
public void onChange(RealmResults<Dog> results) {
// results and puppies point are both up to date
results.size(); // => 1
puppies.size(); // => 1
}
});
这适用于所有RealmResults
:所有对象,已过滤和链接。
这种属性RealmResults
不仅可以保持Realm的快速和高效,而且可以使您的代码更简单,更具反应性。例如,如果Activity或Fragment依赖于查询结果,则可以只存储Realm对象或RealmResults
在字段中。访问时,其数据始终是最新的。
即使您不必刷新您RealmResults
的应用程序,您的应用程序也可能需要更新其UI或在数据更改时运行其他任务。您可以在Realm数据更新时订阅通知。由于结果是自动更新的,因此不要依赖索引和计数保持不变是很重要的。
聚合
RealmResults
为结果集中的总和和平均值等操作提供聚合便捷方法。
RealmResults<User> results = realm.where(User.class).findAll();
long sum = results.sum("age").longValue();
long min = results.min("age").longValue();
long max = results.max("age").longValue();
double average = results.average("age");
long matches = results.size();
迭代和快照
所有Realm系列都是实时的。这意味着它们总能反映最新状态。在大多数情况下,这是可取的,但是如果你为了修改元素而迭代一个集合呢?例如:
RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll();
realm.beginTransaction();
for (int i = 0; guests.size(); i++) {
guests.get(i).setInvited(true);
}
realm.commitTransaction();
通常,您会期望这个简单的循环邀请所有客人。因为RealmResults
立即更新,只有一半的客人最终被邀请!邀请客人将立即从集合中删除,这将移动所有元素。当i
参数递增时,它将错过一个元素。
为防止这种情况,您可以拍摄集合数据的快照。快照可确保元素的顺序不会更改,即使删除或修改了元素也是如此。
Iterator
从中创建的RealmResults
将自动使用快照,而不会Iterator
创建RealmList
。要在迭代时删除元素RealmList
,Iterator.remove()
应该使用代替RealmList.remove()
或其他API来RealmList
间接删除元素以避免ConcurrentModificationException
。RealmResults
并RealmList
有createSnapshot
一个手动创建一个方法。
RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll();
// Use an iterator to invite all guests
realm.beginTransaction();
for (Person guest : guests) {
guest.setInvited(true);
}
realm.commitTransaction();
// Use a snapshot to invite all guests
realm.beginTransaction();
OrderedRealmCollectionSnapshot<Person> guestsSnapshot = guests.createSnapshot();
for (int i = 0; guestsSnapshot.size(); i++) {
guestsSnapshot.get(i).setInvited(true);
}
realm.commitTransaction();
删除
您可以从Realm中删除查询的结果:
// obtain the results of a query
final RealmResults<Dog> results = realm.where(Dog.class).findAll();
// All changes to data must happen in a transaction
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// remove single match
results.deleteFirstFromRealm();
results.deleteLastFromRealm();
// remove a single object
Dog dog = results.get(5);
dog.deleteFromRealm();
// Delete all matches
results.deleteAllFromRealm();
}
});
异步查询
Realm中的大多数查询都足够快,即使在UI线程上也可以同步运行。但是,对于大型数据集上的复杂查询或查询,在后台线程上运行查询可能是一个优势。
RealmResults<User> result = realm.where(User.class)
.equalTo("name", "John")
.or()
.equalTo("name", "Peter")
.findAllAsync();
请注意,查询未阻止 - 它会立即返回a RealmResults<User>
。这是一个类似于标准Java中Future的概念的承诺。查询将继续在后台线程中运行,一旦完成就更新返回的实例。RealmResults
如果您希望在查询完成并RealmResults
更新对象时收到通知,则可以注册a RealmChangeListener
。每次RealmResults
更新时都会调用此侦听器以反映Realm中的最新更改(通常在提交之后)。
private OrderedRealmCollectionChangeListener<RealmResults<User>> callback = new OrderedRealmCollectionChangeListener<>() {
@Override
public void onChange(RealmResults<User> results, OrderedCollectionChangeSet changeSet) {
if (changeSet == null) {
// The first time async returns with an null changeSet.
} else {
// Called on every update.
}
}
};
private RealmResults<User> result;
public void onStart() {
result = realm.where(User.class).findAllAsync();
result.addChangeListener(callback);
}
请记住在退出活动或片段时取消注册任何侦听器以避免内存泄漏。
public void onStop () {
result.removeChangeListener(callback); // remove a particular listener
// or
result.removeAllChangeListeners(); // remove all registered listeners
}
使用isLoaded
来检查,如果查询已完成:
RealmResults<User> result = realm.where(User.class).findAllAsync();
if (result.isLoaded()) {
// Results are now available
}
调用同步获得isLoaded
的RealmResults
对象将始终返回true
。
您也可以等到查询完成。这将阻止线程,使查询再次同步。
RealmResults<User> result = realm.where(User.class).findAllAsync();
result.load() // be careful, this will block the current thread until it returns
注意:您只能在Looper线程上使用异步查询。异步查询需要使用Realm的Handler才能始终如一地提供结果。尝试使用在没有Looper的线程内打开的Realm调用异步查询将抛出一个IllegalStateException
。
迁移
使用任何数据库时,您的模型类(即数据库模式)可能会随着时间的推移而发生变化。由于Realm中的模型类被定义为标准对象,因此更改模式就像更改相应RealmObject子类的接口一样简单。
本地迁移
对于未同步到Realm Object Server的领域,执行迁移需要对RealmConfiguration进行两处更改:设置新的架构版本,并编写代码以执行迁移。
RealmConfiguration config = new RealmConfiguration.Builder()
.schemaVersion(2) // Must be bumped when the schema changes
.migration(new MyMigration()) // Migration to run instead of throwing an exception
.build()
使用此选项,将根据需要自动运行迁移代码。我们提供内置方法,以便您可以升级磁盘上的架构,以及为先前版本的架构存储的数据。
// Example migration adding a new class
public class MyMigration implements RealmMigration {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
// DynamicRealm exposes an editable schema
RealmSchema schema = realm.getSchema();
// Migrate to version 1: Add a new class.
// Example:
// public Person extends RealmObject {
// private String name;
// private int age;
// // getters and setters left out for brevity
// }
if (oldVersion == 0) {
schema.create("Person")
.addField("name", String.class)
.addField("age", int.class);
oldVersion++;
}
// Migrate to version 2: Add a primary key + object references
// Example:
// public Person extends RealmObject {
// private String name;
// @PrimaryKey
// private int age;
// private Dog favoriteDog;
// private RealmList<Dog> dogs;
// // getters and setters left out for brevity
// }
if (oldVersion == 1) {
schema.get("Person")
.addField("id", long.class, FieldAttribute.PRIMARY_KEY)
.addRealmObjectField("favoriteDog", schema.get("Dog"))
.addRealmListField("dogs", schema.get("Dog"));
oldVersion++;
}
}
}
有关更多详细信息,请参阅迁移示例应用
如果Realm启动时磁盘上没有文件,则不需要迁移,Realm将.realm
根据代码中定义的最新模型创建新文件和模式。这意味着,如果您处于开发阶段并经常更改架构 - 并且可以丢失所有数据 - 您可以删除.realm
磁盘上的文件而不是编写迁移。在应用程序开发周期的早期修补模型时,这会很有帮助。
RealmConfiguration config = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
通知
可以注册侦听器以接收有关Realm或其实体的更改的通知。当Realm作为一个整体被更改时发送领域通知; 更改,添加或删除单个对象时会发送收集通知。
通过调用removeChangeListener
或removeAllChangeListeners
方法停止通知传递。如果注册侦听器的对象被垃圾回收,或者其Realm实例已关闭,则通知也将停止。只要您需要通知,就应该保留对您正在收听的对象的强引用。
// Wrong way to register for notifications. Query result will
// be GC'ed when the method exits causing the listener to stop
// emitting notifications.
public void runQuery() {
realm.where(Person.class)
.findAllAsync()
.addChangeListener(new RealmChangeListener() {
public void onChange(RealmResults<Person> persons) {
// Persons was updated
}
};
}
// Right way to register for notifications. The listener will
// continue to emit notifications after the method exits.
RealmResults<Person> persons;
public void runQuery() {
persons = realm.where(Person.class)
.findAllAsync()
.addChangeListener(new RealmChangeListener() {
public void onChange(RealmResults<Person> persons) {
// Persons was updated
}
};
}
通知始终在最初注册的线程上提供。该线程必须有一个正在运行的Looper。如果相关的写入事务发生在另一个线程上,则在提交事务后将异步调用该侦听器。
如果写入事务发生在同一个线程上,则在提交事务时将同步调用侦听器。但是,在某些情况下,可以在事务开始时调用侦听器- 如果Realm进入最新版本,或者观察到的Realm实体以触发通知的方式被修改或删除。在这些情况下,侦听器在当前写入事务的上下文中运行,因此尝试在通知处理程序中开始新的写入事务将引发异常。您可以使用该Realm.isInTransaction
方法确定您的代码是否在写入事务中执行。
由于异步通知是通过looper事件传递的,因此looper队列中的其他事件可能会延迟通知的传递。当无法立即传递通知时,多个写入事务的更改可能会合并为单个通知。
领域通知
您可以通过添加一个侦听器来通知您的UI或其他循环线程,以便在Realm发生更改时执行该侦听器:
public class MyActivity extends Activity {
private Realm realm;
private RealmChangeListener realmListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
realmListener = new RealmChangeListener<Realm>() {
@Override
public void onChange(Realm realm) {
// ... do something with the updates (UI, etc.) ...
}
};
realm.addChangeListener(realmListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove the listener.
realm.removeChangeListener(realmListener);
// Close the Realm instance.
realm.close();
}
}
Realm上的监听器接收整个已更改的Realm。
收集通知
收集通知不是整个领域,而是细粒度的更改描述。它们包括自上次通知以来已添加,删除或修改的对象索引。收集通知是异步传递的,首先是初始结果,然后是每次写入事务后再次发送,这会改变集合中的任何对象(或添加新对象)。
可以通过OrderedCollectionChangeSet
传递给更改侦听器的参数来访问这些更改。此对象包含有关受删除,插入和更改影响的索引的信息。
前两个,删除和插入,记录已添加到集合或从集合中删除的对象的索引。这会将对象添加到Realm或从Realm中删除它们时考虑在内。为此,RealmResults
当您筛选特定值并更改对象以使其现在与查询匹配或不再匹配时也适用。
每当对象的字段发生更改时,您都会收到有关更改的通知,这些更改以前是集合的一部分,并且仍然是其中的一部分。当一对多关系发生变化时,也会发生这种情况。
public class Dog extends RealmObject {
public String name;
public int age;
}
public class Person exteds RealmObject {
public String name;
public RealmList<Dog> dogs;
}
我们假设您正在观察上面的模型代码给出的狗主人名单。您将收到有关匹配的Person对象的修改的通知,例如:
- 你修改了
Person
这个名字。 - 您可以
Dog
在属于a的狗列表中添加或删除aPerson
。 - 你修改的年龄
Dog
属于该Person
。
这使得可以离散地控制对UI内部内容进行的动画和视觉更新,而不是每次发生通知时任意重新加载所有内容。
private final OrderedRealmCollectionChangeListener<RealmResults<Person>> changeListener = new OrderedRealmCollectionChangeListener<RealmResults<Person>>() {
@Override
public void onChange(RealmResults<Person> collection, OrderedCollectionChangeSet changeSet) {
// `null` means the async query returns the first time.
if (changeSet == null) {
notifyDataSetChanged();
return;
}
// For deletions, the adapter has to be notified in reverse order.
OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges();
for (int i = deletions.length - 1; i >= 0; i--) {
OrderedCollectionChangeSet.Range range = deletions[i];
notifyItemRangeRemoved(range.startIndex, range.length);
}
OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
for (OrderedCollectionChangeSet.Range range : insertions) {
notifyItemRangeInserted(range.startIndex, range.length);
}
OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges();
for (OrderedCollectionChangeSet.Range range : modifications) {
notifyItemRangeChanged(range.startIndex, range.length);
}
}
};
在RealmRecyclerViewAdapter
提供这种开箱即用的。
对象通知
Realm支持对象级通知。您可以在特定项目上注册通知RealmObject
,以便在删除对象时或在对象上的任何管理字段修改其值时通知您。
只有托管RealmObject
s可以在其上注册监听器。
可以通过ObjectChangeSet
传递给更改侦听器的参数来访问这些更改。该ObjectChangeSet
持有哪些领域发生了变化信息,如果RealmObject
被删除。
如果删除了对象,ObjectChangeSet.isDeleted
则返回true
。之后,不会再次调用监听器。
该ObjectChangeSet.getChangedFields
如有对象的托管字段被改变将返回改变字段的名称。您还可以使用它ObjectChangeSet.isFieldChanged
来测试给定字段是否刚刚更改。
private final RealmObjectChangeListener<Dog> listener = new RealmObjectChangeListener<Dog>() {
@Override
public void onChange(Dog dog, ObjectChangeSet changeSet) {
if (changeSet.isDeleted()) {
Log.i(TAG, "The dog was deleted");
return;
}
for (String fieldName : changeSet.getChangedFields()) {
Log.i(TAG, "Field " + fieldName + " was changed.");
}
}
};
相同值的通知
Realm会将所有更改视为会引发通知的内容。但是,在许多情况下,如果值未更改,则不希望刷新UI。
如果要更新单个字段,可以在覆盖它之前检查Realm文件中的值,以避免触发通知:
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
String newName = "Jane";
Person managedPerson = getPerson();
if (!newName.equals(managedPerson.getName())) {
managedPerson.setName(newName);
}
}
});
如果要使用插入对象,Realm.copyToRealm()
或者Realm.copyToRealmOrUpdate()
可以使用a ImportFlag
来指示只应更新实际更改的字段:
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
int id = 42;
Person person = new Person(id, "Jane");
realm.copyToRealmOrUpdate(person, ImportFlag.CHECK_SAME_VALUES_BEFORE_SET);
}
});
这样,只会针对实际更改的字段引发通知。使用此标志时,null
输入对象中的值将被视为正常值,因此如果Realm中的值为非null,则将覆盖该值null
并触发通知。
加密
请注意我们许可证的出口合规部分,因为如果您位于有美国出口限制或禁运的国家/地区,它会对使用Realm进行限制。
通过将512位加密密钥(64字节)传递给配置,可以在磁盘上对Realm文件进行加密RealmConfiguration.Builder.encryptionKey
:
byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
RealmConfiguration config = new RealmConfiguration.Builder()
.encryptionKey(key)
.build();
Realm realm = Realm.getInstance(config);
当给出加密密钥时,Realm使用标准AES-256加密透明地加密和解密数据。每次打开Realm时都必须提供相同的加密密钥。有关如何在Android KeyStore中的运行之间安全存储密钥以便其他应用程序无法读取它们的示例,请参阅examples / encryptionExample。
使用同步领域
您是否希望使用Realm Mobile Platform同步所有Realm数据库?所有与同步相关的文档已移至我们的平台文档中
穿线
Realm可以毫不费力地处理多个线程上的数据,而不必担心一致性或性能,因为对象和查询始终是自动更新的。您可以对不同线程中的活动对象进行操作,读取和写入它们,而不必担心其他线程正在对这些对象执行的操作。如果需要更改数据,可以使用事务。另一个线程中的其他对象将近乎实时更新(更新将被安排为事件上的事件Looper
,因此Looper线程将在事件处理后立即更新)。
唯一的限制是您不能在线程之间随机传递Realm对象。如果您需要在另一个线程上使用相同的数据,则需要在另一个线程上查询该数据。此外,您可以使用Realms反应体系结构观察更改。请记住,所有对象在线程之间保持最新 - Realm会在数据更改时通知您。
线程示例
假设我们有一个显示客户列表的应用程序。在后台线程(Android IntentService)中,我们为新客户轮询远程端点,然后将它们保存到Realm。当后台线程添加新客户时,UI线程中的数据将自动更新。UI线程通过a获得通知RealmChangeListener
,此时我们告诉UI小部件自我更新。无需重新查询,因为Realm使所有内容保持最新。
// in a Fragment or Activity, etc
// Listeners will only be triggered as long as the query result is
// not garbage collected, so keep a strong class reference to it.
private RealmResults<Customer> customers;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// ... boilerplate omitted for brevity
realm = Realm.getDefaultInstance();
// get all the customers
customers = realm.where(Customer.class).findAllAsync();
// ... build a list adapter and set it to the ListView/RecyclerView/etc
// set up a Realm change listener
changeListener = new RealmChangeListener() {
@Override
public void onChange(RealmResults<Customer> results) {
// This is called anytime the Realm database changes on any thread.
// Please note, change listeners only work on Looper threads.
// For non-looper threads, you manually have to use Realm.waitForChange() instead.
updateUi();
}
};
// Tell Realm to notify our listener when the customers results
// have changed (items added, removed, updated, anything of the sort).
customers.addChangeListener(changeListener);
}
// In a background service, in another thread
public class PollingService extends IntentService {
@Override
public void onHandleIntent(Intent intent) {
Realm realm = Realm.getDefaultInstance();
try {
// go do some network calls/etc and get some data and stuff it into a 'json' var
String json = customerApi.getCustomers();
realm.beginTransaction();
realm.createObjectFromJson(Customer.class, json); // Save a bunch of new Customer objects
realm.commitTransaction();
// At this point, the data in the UI thread is already up to date.
// ...
} finally {
realm.close();
}
}
// ...
}
一旦后台服务将新客户添加到领域,该customers
列表将在UI中自动更新,而无需您进行任何其他干预。这同样适用于单个对象。假设您只管理一个对象。只需在一个线程上更改它,UI线程就会自动拥有新数据。如果您需要响应该更改,只需像我们上面所做的那样添加一个监听器。
跨线程使用领域
跨线程使用Realm的唯一规则是记住Realm,RealmObject和RealmResults实例不能跨线程传递。而是使用异步查询或异步事务将操作卸载到后台线程,并将任何结果返回给原始线程。
当你想从不同的线程访问相同的数据,你可以得到一个新的境界实例(即Realm.getInstance(RealmConfiguration config)
或它的表兄弟),并通过查询获取你的对象。
对象将映射到磁盘上的相同数据,并且可以从任何线程读取和写入。
Android框架线程
使用这些类时要小心:
的AsyncTask
类包含doInBackground
其执行后台线程方法。本IntentService
类包含onHandleIntent(Intent intent)
其执行工作线程的方法。
如果您需要在这两种方法中使用Realm,您应该打开Realm,执行您的工作,然后在退出之前关闭Realm。以下是几个例子。
的AsyncTask
在doInBackground
方法中打开和关闭Realm ,如下所示。
private class DownloadOrders extends AsyncTask<Void, Void, Long> {
protected Long doInBackground(Void... voids) {
// Now in a background thread.
// Open the Realm
Realm realm = Realm.getDefaultInstance();
try {
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
return orderId;
} finally {
realm.close();
}
}
protected void onPostExecute(Long orderId) {
// Back on the Android mainThread
// do something with orderId such as query Realm
// for the order and perform some operation with it.
}
}
IntentService
ChangeListener
s不适用于IntentService
。即使它是一个Looper
线程,每次调用onHandleIntent
都是一个不“循环”的独立事件。这意味着可以注册更改侦听器,但永远不会触发它们。
在onHandleIntent
方法中打开和关闭Realm ,如下所示。
public class OrdersIntentService extends IntentService {
public OrdersIntentService(String name) {
super("OrdersIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// Now in a background thread.
// Open the Realm
Realm realm = Realm.getDefaultInstance();
try {
// Work with Realm
realm.createAllFromJson(Order.class, api.getNewOrders());
Order firstOrder = realm.where(Order.class).findFirst();
long orderId = firstOrder.getId(); // Id of order
} finally {
realm.close();
}
}
}
多流程支持
可以从多个进程访问领域,但有一些限制。当从同一个APK中的不同进程访问同一个Realm时,包括通知在内的所有内容都应该正常工作。
例子
看看我们的示例,看看Realm在应用程序中的实践中使用。有关如何运行示例的更多详细信息,请参见此处。
该introExample包含如何使用API王国简单的例子。
该gridViewExample是一个微不足道的应用程序,展示了如何使用领域作为后备存储的GridView控件。它还展示了如何使用GSON使用JSON填充数据库以及如何使用ABI拆分来最小化最终APK的大小。
该threadExample是一个简单的应用程序,展示了如何在多线程环境中使用领域。
该adapterExample展示了如何使用RealmBaseAdapter
和RealmRecyclerViewAdapter
制作Realm
与Android作业的ListView和RecyclerView以一种优雅的方式。
该jsonExample演示领域的JSON设施。
该encryptionExample展示了如何使用加密的国度工作。
该rxJavaExamples显示领域如何与RxJava在一起。
该unitTestExample显示了域工作时如何编写单元测试。
该multiProcessExample展示了如何使用来自不同进程的域在同一个APK。
其他图书馆
本节介绍如何将Realm与其他常用的Android库集成。
GSON
GSON是由Google创建的用于反序列化和序列化JSON的库。GSON应与开箱即用的Realm合作。
// Using the User class
public class User extends RealmObject {
private String name;
private String email;
// getters and setters left out ...
}
Gson gson = new GsonBuilder().create();
String json = "{ name : 'John', email : 'john@corporation.com' }";
User user = gson.fromJson(json, User.class);
您还可以在GridViewExample中看到GSON如何与Realm一起使用的示例。
序列化
为了与Retrofit等库完全兼容,您通常希望能够反序列化和序列化对象。将Realm对象序列化为JSON不适用于GSON的默认行为,因为GSON将使用字段值而不是getter和setter。
要使GSON序列化与Realm一起使用,您需要为每个可以序列化的对象编写自定义JsonSerializer并将其注册为TypeAdapter。
这个要点展示了如何做到这一点。
原始列表
虽然Realm支持将JSON中的数组本机导入到基元列表中,但缺少对基元列表的查询支持可能仍然是一个问题。您可能希望将原始类型的JSON数组导入为列表RealmObject
。如果无法更改JSON API,则可以为GSON 编写自定义TypeAdapter,以自动映射JSON中的基元类型和Realm使用的包装器对象。
在这个Gist中是一个使用Integers的包装器对象的例子,但是该模板可以用于Realm支持的数据类型的所有原始数组。
故障排除
Realm对象可以包含内部包含循环引用的字段。当发生这种情况时,GSON可以抛出一个StackOverflowError
。我们已经看到当Realm对象有一个Drawable
字段时会发生这种情况:
public class Person extends RealmObject {
@Ignore
Drawable avatar;
// other fields, etc
}
Person
上面的类包含一个应用了注释的Android Drawable@Ignore
。在GSON序列化期间,正在检查Drawable并导致StackOverflowError(GitHub问题)。要缓解此问题,请将以下代码添加到您的shouldSkipField
方法中。
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class) || f.getDeclaringClass().equals(Drawable.class);
}
请注意Drawable.class
评估。这告诉GSON在序列化期间跳过此字段。添加这将减轻StackOverflowError
。
杰克逊达比林德
Jackson Databind是一个用于将JSON数据绑定到Java类的库。
杰克逊使用反射来执行数据绑定。这与Realm对RxJava的支持相冲突,因为RxJava可能无法用于类加载器。这可能会导致异常,如下所示:
java.lang.NoClassDefFoundError: rx.Observable
at libcore.reflect.InternalNames.getClass(InternalNames.java:55)
...
这可以通过将RxJava添加到项目中来修复,也可以创建两个如下所示的空虚拟文件。
// File 1
package io.reactivex;
public class Flowable {
}
// File 2
package io.reactivex;
public class Observable {
}
// File 3
package io.reactivex;
enum BackpressureStrategy {
LATEST;
}
这个问题也在杰克逊项目中报告过。
科特林
Realm与Kotlin编程语言完全兼容,但有一些需要注意的注意事项:
- 该
realm-android
插件有后应用kotlin-android
和kotlin-kapt
这样的:
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
-
您的模型类是开放的很重要。
-
甲的科特林注解处理器的限制指示添加注释
@RealmClass
在某些情况下是必需的。 -
在Kotlin中的Realm Model类中存储枚举应该使用以下模式完成:
open class EnumTest: RealmObject() {
// Beware that enums stored in Realm must not be obfuscated by ProGuard
class enum MyEnum {
Value1, Value2
}
// Custom private backing field representing the enum
private var strField: String = MyEnum.Value1.name
// Public field exposing setting/getting the enum
var enumField: MyEnum
get() = MyEnum.values().first { it.name == strField }
set(value) {
strField = value.name
}
}
// Queries
val results = realm.where<EnumTest>().equalTo("strField", MyEnum.Value1.name).findAll();
请注意,查询需要在strField
with String值上完成,而不是enumField
由Realm忽略,因为它没有后备字段。
- 在科特林
Long::class.java
实际上返回一个Class引用long
不Long
。这同样适用于其他原始类型像真正的Integer
,Float
,Double
和Boolean
。在迁移期间选择正确的类会产生影响:
schema
.addField("field", Long::class.java) // Non-nullable
.addField("field", Long::class.javaObjectType) // Nullable
.addField("field", Long::class.javaPrimitiveType) // Non-nullable
如果在项目中使用Kotlin,Realm会自动检测到这一点并添加一些扩展方法,这使得与Kotlin的协作变得更加容易。其中包括:
-
接受Java中的类参数的所有方法现在在Kotlin中都有一个具体化的变体,例如
realm.where(Person.class).findAll()
变为realm.where<Person>().findAll()
-
如果模型类实现了
RealmModel
接口,则现在会自动注入默认方法,这意味着无论是扩展基类RealmObject
还是实现接口,都可以使用完全相同的调用模式RealmModel
。 -
查询谓词
in()
现在有一个Kotlin别名,命名anyOf()
为Kotlinin
中的关键字。
可以使用realm
闭包手动禁用此扩展库:
android {
...
}
realm {
kotlinExtensionsEnabled = false // Disable extensions if needed
}
有关Realm和Kotlin结合使用的应用程序,请参阅此示例。
Kotlin 1.3.0有一个错误导致某些Kotlin代码kapt失败。这可能导致编译时出现“未找到符号”等错误。目前唯一的解决方案是降低Kotlin的价格。这个问题正在跟踪这里。
Parceler
Parceler是一个库,它自动生成使对象尊重Parcelable接口所需的样板。由于Realm使用代理类,Parceler需要以下设置才能使用Realm的模型类。
Realm中的代理类使用模型类的完全限定名称加上RealmProxy
后缀,例如io.realm.model.Person
变为io_realm_model_PersonRealmProxy.class
// All classes that extend RealmObject will have a matching RealmProxy class created
// by the annotation processor. Parceler must be made aware of this class. Note that
// the class is not available until the project has been compiled at least once.
@Parcel(implementations = { some_package_PersonRealmProxy.class },
value = Parcel.Serialization.BEAN,
analyze = { Person.class })
public class Person extends RealmObject {
// ...
}
如果您使用Gradle获取Parceler,请确保以下行(请参阅此处了解更多详细信息):
compile "org.parceler:parceler-api:1.0.3"
apt "org.parceler:parceler:1.0.3"
使用Parceler时需要注意一些重要的限制:
- 如果您的模型包含
RealmList
您需要注册特殊适配器。 - 一旦对象被分区,它就会与Realm分离,此时的行为就像一个包含数据快照的非托管对象。Realm中不会持续对此对象进行进一步更改。
改造
Retrofit是Square的一个库,它使得以类型安全的方式使用REST API变得容易。
Realm将同时使用Retrofit 1. *和2. *,但请注意Retrofit不会自动向Realm添加对象,而是必须使用realm.copyToRealm或realm.copyToRealmOrUpdate方法手动添加它们。
GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");
// Copy elements from Retrofit to Realm to persist them.
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();
Robolectric
Robolectric是一个库,允许您直接在JVM中而不是在手机或模拟器中运行JUnit测试。目前,Robolectrics不支持与Realm捆绑在一起的本机库。这意味着目前无法使用Robolectric测试Realm。
您可以在此处按照功能请求进行操作:https://github.com/robolectric/robolectric/issues/1389
RxJava
RxJava是Netflix 的Reactive Extensions库,它扩展了Observer模式。它使得可以将数据的变化视为可组合序列。
Realm拥有对RxJava 2 Flowable
和RxJava 2的一流支持Observable
- 领域
- RealmResults
- RealmList
- RealmObject
- DynamicRealm
- DynamicRealmObject
- RealmResults变更集
- RealmList变更集
- RealmObject变更集
- DynamicRealmObject变更集
// Combining Realm, Retrofit and RxJava (Using Retrolambda syntax for brevity)
// Load all persons and merge them with their latest stats from GitHub (if they have any)
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
realm.where(Person.class).isNotNull("username").findAllAsync().asFlowable()
.filter(persons.isLoaded)
.flatMap(persons -> Observable.from(persons))
.flatMap(person -> api.user(person.getGithubUserName())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> showUser(user));
异步查询是非阻塞的 - 上面的代码将立即返回一个RealmResults
实例。如果您想确保只在加载列表上运行,请Flowable
通过过滤器运算符过滤并通过调用[ RealmResults]检查列表.isLoaded](api / io / realm / RealmResults.html#isLoaded--)方法。通过检查是否加载了“RealmResults”,您可以确定您的查询是否已完成。
有关更多示例,请参阅RxJava示例项目。
RxJava是一个可选的依赖项,这意味着Realm不会自动包含它。这样做的好处是,您可以选择使用哪个版本的RxJava,以及避免不使用RxJava的项目中的方法计数膨胀。必须手动将RxJava添加到build.gradle
文件中。
dependencies {
compile 'io.reactivex:rxjava:2.1.4'
}
可以通过创建自定义来配置Realm如何创建流RxObservableFactory
。这是使用配置RealmConfiguration
。
RealmConfiguration config = new RealmConfiguration.Builder()
.rxFactory(new MyRxFactory())
.build()
如果RxObservableFactory
未定义,则Realm默认为Realm RealmObservableFactory
提供的类,它支持RxJava <= 2. *。
如果您使用的是RxJava 1,则可以使用David Karnok的此库在RxJava 2和RxJava 1类型之间进行转换。
测试和调试
有关Realm如何与JUnit3,JUnit4,Robolectric,Mockito和PowerMock结合使用的信息,请参阅我们的unitTestExample。
Android Studio调试
在使用Android Studio或IntelliJ工作时需要注意一点“问题”:调试器可能会根据您使用的调试视图提供误导性值。
例如,在Android Studio中添加手表RealmObject
会显示字段的值。不幸的是,这些值是错误的,因为不使用字段值。Realm在幕后创建一个代理对象,并覆盖getter和setter以访问Realm中的持久数据。为任何访问者添加监视将产生正确的值。见下图:
在上图中,调试器已在第113行停止。有三个监视值,即person
变量person.getName
和person.getAge
访问器。第107到111行的代码person
通过更改名称和年龄来改变实例。然后,这些值将保留在事务中。在第113行,调试器当前处于暂停状态,person
监视实例正在报告字段值,但它们不正确。使用访问者的监视值person.getName
并person.getAge
报告正确的值。
请注意,该.toString
方法将输出正确的值,但是监视面板不会(当观察变量时是a RealmObject
)。
NDK调试
Realm是一个包含本机代码的库。我们建议您使用崩溃报告工具(例如Crashlytics)来跟踪本机错误,以便在出现问题时我们能够更好地帮助您。
调试NDK崩溃通常很麻烦,因为默认堆栈跟踪提供了可用的最少信息。Crashlytics将允许您捕获有价值的NDK崩溃信息。要在Crashlytics中启用NDK崩溃报告,请按照本指南中列出的步骤操作。
要为项目启用NDK崩溃报告,请将其添加到build.gradle文件的根目录。请注意,值androidNdkOut
和androidNdkLibsOut
是不需要的。
crashlytics {
enableNdk true
}
目前的局限
Realm通常会尝试尽可能少的约束,并且我们会根据社区的反馈不断添加新功能。但是,Realm仍有一些局限性。有关已知问题的更全面列表,请参阅我们的GitHub问题。
楷模
领域模型不支持final
和volatile
字段。这主要是为了避免对象在Realm或非托管方面的行为方式之间存在差异。
不允许领域模型类扩展任何其他对象RealmObject
。如果声明,则默认构造函数(不带参数的构造函数)必须始终为空。原因是默认的构造函数将调用假定存在Realm实例的方法。但是这个实例不是在构造函数返回之前创建的。为方便起见,您可以添加其他构造函数。
一般
Realm旨在在灵活性和性能之间取得平衡。为了实现这一目标,对在Realm中存储信息的各个方面施加了现实限制。例如:
- 类名的上限为57个字符。Realm Java
class_
以所有名称为前缀,浏览器将其显示为名称的一部分。 - 字段名称的长度上限为63个字符。
- 在不同的包中不可能有两个具有相同名称的模型类。
- 不支持嵌套事务,如果检测到异常,则抛出异常。
String
s和字节数组(byte[]
)不能大于16 MB。- 领域模型不支持
final
和volatile
字段。这主要是为了避免对象在Realm或非托管方面的行为方式之间存在差异。 - 如果提供了自定义构造函数,则还必须存在公共无参数构造函数。
- Realm模型类不允许扩展任何其他类
RealmObject
。
对字符串进行排序和查询
只有Latin Basic,Latin Supplement,Latin Extended A和Latin Extended B(UTF-8范围0-591)中的字符集才支持查询中的排序和不区分大小写的字符串匹配。此外,使用时设置区分大小写标志查询equalTo
,notEqualTo
,contains
,endsWith
,beginsWith
,或like
只从英语语言环境的字符工作。
Realm对大写和小写字母使用非标准排序,将它们排序在一起而不是先排序大写。这意味着它'- !"#0&()*,./:;?_+<=>123aAbBcC...xXyYzZ
是Realm中的实际排序顺序。了解更多关于这些限制在这里。
主题
尽管Realm文件可以由多个线程同时访问,但您无法在线程之间移交Realms,Realm对象,查询和结果。该线程示例演示如何在多线程环境中使用Realm。阅读有关Realm线程的更多信息。
尽管Realm文件可以由多个线程同时访问,但它们一次只能由一个进程访问。不同的进程应该复制Realm文件或创建自己的文件。
RealmObject的hashCode
A RealmObject
是活动对象,可能会通过其他线程的更改进行更新。虽然回国2个境界对象true
为RealmObject.equals
必须有相同的值RealmObject.hashCode
,这个值是不是稳定,既不应该被用作一个键HashMap
,也没有保存在HashSet
。
多进程
- 不支持同时访问不同进程的加密域。有一个Realm Core问题(#1845)跟踪此问题。
- 不支持从不同APK中的不同进程访问相同的域。这样做是安全的,但通知等内容无法按预期工作。
- 不支持从不同进程访问同步的域。
增量构建
领域字节码转换器支持增量构建,但在少数情况下需要完整构建。变压器本身无法检测到这些情况。
@Ignore
在模型类的字段中添加或删除注释。static
在模型类的字段中添加或删除关键字。transient
在模型类的字段中添加或删除关键字。
在这些情况下未能执行完整构建将导致应用程序崩溃或导致字段返回数据类型的默认值(例如0或null
),而不是返回存储在Realm中的真值。
最佳做法
开箱即用,Realm可以与Android无缝协作。你必须记住的主要事情是RealmObject
s是线程限制的。当您想要在活动,后台服务,广播接收器等之间开始传递Realm对象时,理解这一点的重要性就会发挥作用。
防止“应用程序无响应”(ANR)错误
通常,Realm足够快,可以在Android主线程上读写数据。但是,写入事务在线程之间是阻塞的,因此为了防止意外的ANR,我们建议您在后台线程(而不是Android的主线程)上执行所有Realm写操作。了解如何通过Realms Asynchronous Transactions在后台线程上执行操作。
控制Realm实例的生命周期
为Realm实例选择合适的生命周期是一种平衡行为。因为RealmObjects
并且RealmResults
通过惰性缓存访问,所以尽可能长时间地保持Realm实例打开不仅可以避免打开和关闭它所产生的开销,而且可能允许对它的查询更快地运行。另一方面,开放的Realm实例拥有大量资源,其中一些资源不受Java内存管理器控制。Java无法自动管理这些资源。打开Realm实例的代码必须在不再需要时关闭它。
Realm使用内部引用计数缓存,以便在获取第一个Realm实例后,在同一个线程上获取后续实例是免费的。但是,只有当该线程上的所有实例都关闭时,才会释放底层资源。
一个合理的选择是使Realm实例的生命周期与观察它的视图的生命周期一致。下面的示例演示了使用a Fragment
和an Activity
,每个示例都RecyclerView
显示从Realm实例检索的数据。在这两个示例中,Realm实例和RecyclerView适配器在create方法中初始化,并在相应的destroy方法中关闭。请注意,即使对于Activity,这也是安全的:即使从未调用onDestroy
和close
方法,数据库也将保持一致状态。
显然,如果与活动相关联的大多数片段需要访问同一数据集,那么控制实例生命周期的活动(而不是单个片段)是有意义的。
// Setup Realm in your Application
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}
// onCreate()/onDestroy() overlap when switching between activities.
// Activity2.onCreate() will be called before Activity1.onDestroy()
// so the call to getDefaultInstance in Activity2 will be fast.
public class MyActivity extends Activity {
private Realm realm;
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setAdapter(
new MyRecyclerViewAdapter(this, realm.where(MyModel.class).findAllAsync()));
// ...
}
@Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
}
// Use onCreateView()/onDestroyView() for Fragments.
// Note that if the db is large, getting the Realm instance may, briefly, block rendering.
// In that case it may be preferable to manage the Realm instance and RecyclerView from
// onStart/onStop instead. Returning a view, immediately, from onCreateView allows the
// fragment frame to be rendered while the instance is initialized and the view loaded.
public class MyFragment extends Fragment {
private Realm realm;
private RecyclerView recyclerView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
View root = inflater.inflate(R.layout.fragment_view, container, false);
recyclerView = (RecyclerView) root.findViewById(R.id.recycler_view);
recyclerView.setAdapter(
new MyRecyclerViewAdapter(getActivity(), realm.where(MyModel.class).findAllAsync()));
// ...
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
realm.close();
}
}
重用RealmResults和RealmObjects
在UI线程和所有其他Looper线程上,当对Realm进行更改时,所有RealmObject
s和都会RealmResults
自动刷新。这意味着在对a作出反应时不必再次获取这些对象RealmChangedListener
。对象已更新,可以在屏幕上重新绘制。
public class MyActivity extends Activity {
private Realm realm;
private RealmResults<Person> allPersons;
private RealmChangeListener realmListener = new RealmChangeListener() {
@Override
public void onChange(Realm realm) {
// Just redraw the views. `allPersons` already contain the
// latest data.
invalidateView();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
realm.addRealmChangeListener(listener);
allPerson = realm.where(Person.class).findAll(); // Create the "live" query result
setupViews(); // Initial setup of views
invalidateView(); // Redraw views with data
}
// ...
}
自动增量ID
Realm不支持自动增量ID。这主要是因为不可能在分布式环境中生成这样的密钥,并且存储在本地Realm和同步Realm中的数据之间的兼容性是高优先级。需要注意的是境界并不需要为了创建关系的主键。
它仍然是可以有效地创建境界是满足自增的ID提供的用例的主键,而是确定是很重要的东西的自增ID用于:
1)提供唯一标识符以识别对象。这可以用GUID代替,它可以保证唯一性,即使在设备离线时也可以由设备创建:
```java
public class Person extends RealmObject {
@PrimaryKey
private String id = UUID.randomUUID().toString();
private String name;
}
```
2)提供宽松的下单。一个例子是排序推文。这可以由一个createdAt
字段替换,该字段不需要是主键:
```java
public class Person extends RealmObject {
@PrimaryKey
private String id = UUID.randomUUID().toString();
private Date createdAt = new Date();
private String name;
}
```
3)提供严格的下单。一个例子是任务列表。RealmList
即使设备已脱机,也可以使用保证插入顺序的方式对其进行建模。
```java
public class SortedPeople extends RealmObject {
@PrimaryKey
private int id = 0
private RealmList<Person> persons;
}
public class Person extends RealmObject {
private String name;
}
// Create wrapper object when creating object
RealmConfiguration config = new RealmConfiguration.Builder()
.initialData(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.insert(new SortedPeople());
}
});
// Insert objects through the wrapper
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
SortedPeople sortedPeople = realm.where(SortedPeople.class).findFirst();
sortedPeople.getPersons().add(new Person());
}
});
```
如果您有一个用例仍然认为自动增量ID更适合,您可以使用此助手类,但请注意,如果您:使用此类生成的密钥不可用:
1)在多个进程中创建领域对象。2)希望在将来的某个时刻在多个设备之间共享领域。
对于自动增量跨进程可安全创建的ID,每次开始事务时都需要查询最大值:
realm.beginTransaction();
Number maxValue = realm.where(MyObject.class).max("primaryKeyField");
long pk = (maxValue != null) ? maxValue + 1 : 0;
realm.createObject(MyObject.class, pk++);
realm.createObject(MyObject.class, pk++);
realm.commitTransaction();
食谱
我们已经汇总了一些显示如何使用Realm来完成一些特定任务的方法。我们会定期添加更多食谱,因此请经常查看。如果您想看一个例子,请在GitHub上打开一个问题。
常问问题
如何查找和查看我的Realm文件的内容?
这个SO问题描述了在哪里找到您的Realm文件。然后,您可以使用我们的Realm Studio查看内容。
Realm Base库有多大?
一旦您的应用程序构建为发布并拆分以进行分发,Realm在大多数情况下应仅向您的APK添加大约800KB。我们分发的版本要大得多,因为它们包括对更多架构(ARM7,ARMv7,ARM64,x86,MIPS)的支持。APK文件包含所有支持的体系结构,但Android安装程序将仅为设备的体系结构安装本机代码。因此,安装的应用程序小于APK文件的大小。
通过将APK拆分为每个架构的版本,可以减小Android APK本身的大小。使用Android Build Tool ABI Split支持,将以下内容添加到build.gradle
:
android {
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64'
}
}
}
选择您要包含的体系结构,并为每个体系结构构建单独的APK。见详细信息,有关ABI Splits Android工具文档。
一个例子也包含在GitHub中。
如果您不想处理多个APK,也可以限制单个APK中支持的体系结构数量。这是通过添加abiFilters
到您的build.gradle
:
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64'
}
}
}
有关ABI拆分和过滤器的更多详细信息,请阅读本文 Brijesh Masrani撰写的。
Realm开源吗?
是! Realm的内部C ++存储引擎及其上的语言SDK完全是开源的,并在Apache 2.0下获得许可。Realm还可选择包含一个闭源Realm Platform Extensions组件,但不需要将Realm用作嵌入式数据库。
普通Java对象和Realm对象有什么区别?
主要区别在于普通Java对象包含自己的数据,而Realm对象不包含数据,而是直接在数据库中获取或设置属性。
Realm对象的实例可以是托管实例,也可以是非托管实例。
- 托管对象在Realm中持久存在,始终是最新的并且线程受限。它们通常比非托管版本更轻量级,因为它们在Java堆上占用的空间更少。
- 非托管对象就像普通的Java对象一样,它们不会被持久化,也不会自动更新。它们可以跨线程自由移动。
可以使用Realm.copyToRealm
和在两种状态之间进行转换Realm.copyFromRealm
。
为什么模型类需要扩展RealmObject?
我们需要为您的模型类添加Realm特定功能。它还允许我们在API中使用泛型,使其更易于阅读和使用。如果您不想扩展基类,则可以改为实现RealmModel
接口。
什么是RealmProxy类?
RealmProxy类是我们确保Realm对象本身不包含任何数据,而是直接在数据库中访问数据的方法。
对于项目中的每个模型类,Realm注释处理器将生成相应的RealmProxy类。此类扩展了模型类,并且是在调用Realm.createObject()时返回的内容,但从代码的角度来看,您不会注意到任何差异。
为什么在编写Realm对象时需要使用事务?
需要事务来确保将多个字段更新为一个原子操作。它允许您定义必须完全完成或根本不完成的更新范围(如果出现错误或受控回滚)。通过指定事务的范围,您可以控制更新的持续频率(或快速)(即在一次操作中插入多个对象)。
在像SQLite这样的普通基于SQL的数据库中进行插入时,一次插入多个字段。这会自动包装在事务中,但通常对用户不可见。在Realm中,这些事务始终是明确的。
如何处理内存不足异常?
Realm for Android基于嵌入式存储引擎。存储引擎不在JVM堆上分配内存,而是在本机内存中分配内存。当存储引擎无法分配本机内存或文件系统已满时,Realm将抛出java.lang.OutOfMemoryError
异常。重要的是不要忽略此错误。如果您的应用程序继续运行,则访问Realm文件可能会使其处于已损坏或不一致的状态。最安全的是终止应用程序。
大领域文件大小
您应该期望Realm数据库在磁盘上占用的空间少于等效的SQLite数据库,但为了给您一致的数据视图,Realm可以在Realm的多个版本上运行。如果最旧版本和最新版本的数据之间的差异变得太大,这可能会导致Realm文件不成比例地增长。
如果不再使用它们,Realm将自动删除旧版本的数据,但实际文件大小不会减少。未来的写入将重用额外的空间。
如果需要,可以通过压缩Realm文件来删除额外的空间。这可以手动或自动完成在第一次打开所述境界时。
如果您遇到意外的文件大小增长,通常会出现以下两个原因之一:
1)你在后台线程上打开一个领域,忘记再次关闭它。
这将导致Realm保留对后台线程上的数据的引用,并且是导致Realm文件大小问题的最常见原因。解决方案是确保正确关闭您的Realm实例。在这里和这里阅读更多。Realm将检测您是否忘记正确关闭Realm实例并在Logcat中打印警告。带有loopers的线程(如UI线程)没有此问题。
2)您从Realm读取一些数据,然后在长时间运行的操作中阻塞该线程,同时在其他线程上向Realm写入多次。
这将导致Realm创建许多需要跟踪的中间版本。避免这种情况有点棘手,但通常可以通过批处理写入或避免让Realm打开而以其他方式阻止后台线程来完成。
我在运行应用程序时看到了对Mixpanel的网络调用
当您在源代码上运行Realm字节码转换器时,Realm会收集匿名分析。这是完全匿名的,可以通过标记您使用的Realm版本以及您使用的操作系统以及我们可以弃用支持的内容来帮助我们改进产品。当您的应用程序在用户的设备上运行时,此调用不会运行 - 仅在编译源代码时才会运行。您可以在我们的源代码中查看我们收集的具体方式和内容,以及它的基本原理。
无法加载“librealm-jni.so”
如果您的应用程序使用的其他本机库不支持64位体系结构,则Android将无法librealm-jni.so
在ARM64设备上加载Realm的文件。这是因为Android无法同时加载32位和64位本机库。最好的解决方案是让所有库提供相同的受支持的ABI集,但有时如果您使用第三方库则可能无法实现。请参阅VLC和Realm Library冲突。
此问题的解决方法是通过将以下代码添加到应用程序来从APK文件中排除Realm的ARM64库build.gradle
。您可以参考Android中的混合32位和64位依赖项以获取更多信息。
android {
//...
packagingOptions {
exclude "lib/arm64-v8a/librealm-jni.so"
}
//...
}
此外,Android Gradle Plugin 1.4.0测试版存在一个错误,导致它不正确地打包jar文件中包含的.so文件(参见Realm Java issue 1421))。要解决此问题,您可以恢复到Android Gradle Plugin 1.3.0或使用Android Gradle Plugin 1.5.0+。
我们知道许多第三方库,框架和管理应用程序还没有64位支持:
- 并行空间 - 但您可以建议您的用户安装64位版本。
- RenderScript -NDK r14可能支持64位。
- Unity3d。
如何备份和恢复领域?
领域存储在文件系统上的文件中。通过调用getPath您可以获得Realm文件的完整路径。如果您打算以这种方式备份或还原Realm文件,则应关闭Realm的所有实例。
也可以使用realm.writeCopyTo备份打开的Realm文件。
如果您要将文件备份到Google云端硬盘等外部位置。您可以阅读本教程:第1 部分,第2 部分和第3部分。
黑莓设备
一些Blackberry设备能够运行Android应用程序。遗憾的是,提供的运行时环境不完整,我们无法保证兼容性。已知的错误消息包括:
io.realm.exceptions.RealmFileException: Function not implemented in io_realm_internal_SharedRealm.cpp line 81 Kind: ACCESS_ERROR.
如果您发现Blackberry设备存在问题,请考虑提供修复,因为Realm Core和Realm Java都是开源项目。
如何存储和检索Realm使用的加密密钥
使用Android KeyStore可能是存储Realm加密密钥的最安全方式。以下是推荐使用它的方法。
- 使用Android的KeyStore,生成一个非对称RSA密钥,由Android安全存储/检索。在版本> =
M
系统需要用户PIN(或指纹)来解锁KeyStore,因此即使在有根设备上,您也有额外的安全层。 - 生成对称密钥(AES),用于加密Realm。
- 使用私有RSA密钥加密对称AES密钥。
- 现在将加密的AES密钥存储在文件系统上是安全的(
SharedPreferences
例如)。 - 当您需要使用加密的Realm时,检索加密的AES密钥,使用公共RSA密钥对其进行解密,然后在其中使用它
RealmConfiguration
来打开加密的Realm。
有关端到端的示例,请查看我们的演示存储库:
- https://github.com/realm/realm-android-user-store
- https://github.com/realm/realm-java/tree/feature/example/store_password/examples/StoreEncryptionPassword(使用指纹API)
如何在自定义ROM上的系统应用程序中使用Realm
Realm使用命名管道来支持通知和从多个进程访问Realm文件。虽然普通用户应用程序默认允许这样做,但系统应用程序不允许这样做。
系统应用程序是通过设置android:sharedUserId="android.uid.system"
Android清单中的设置来定义的,如果您正在创建这样的应用程序,您可能会在Logcat中看到类似这样的安全违规:
05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:99): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0
05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:100): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0
为了解决这个问题,您需要调整ROM中的SELinux安全规则。这可以通过使用工具来完成,该工具audit2allow
是作为AOSP的一部分提供的工具。
1)首先从设备中提取当前策略adb pull /sys/fs/selinux/policy
。2)将SELinux错误复制到名为的文本文件中input.txt
。3)运行audit2allow
工具:audit2allow -p policy -i input.txt
。4)该工具应输出您可以添加到现有策略中的规则,以允许您使用Realm。
下面提供了有关此类策略的外观的示例:
# Allow system_app to create named pipes required by Realm
# Credit: https://github.com/mikalackis/platform_vendor_ariel/blob/master_oreo/sepolicy/system_app.te
allow system_app fuse:fifo_file create;
allow system_app system_app_data_file:fifo_file create;
allow system_app system_app_data_file:fifo_file { read write };
allow system_app system_app_data_file:fifo_file open;
audit2allow
在编译AOSP / ROM时生成并且仅在Linux上运行。你可以在这里阅读更多相关信息。另请注意,自Android Oreo以来,Google改变了配置SELinux的方式,现在默认的安全策略更加模块化。在这里阅读更多相关信息。
如何自定义Realm Gradle插件定义的依赖项?
Realm使用Gradle插件,因为它可以更容易地设置大量的依赖项,但遗憾的是它也使得自定义更难,例如,如果你想忽略一些传递依赖。
如果您希望自定义Realm超出插件公开的范围,您可以手动设置所有依赖项并忽略Gradle插件。如何为Kotlin项目执行此操作如下所示:
使用gradle插件时的标准方法:
buildscript {
ext.kotlin_version = '1.2.41'
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:5.12.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
手动设置:
buildscript {
ext.kotlin_version = '1.2.41'
ext.realm_version = '5.12.0'
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath "io.realm:realm-transformer:$realm_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
import io.realm.transformer.RealmTransformer
android.registerTransform(new RealmTransformer(project))
dependencies {
api "io.realm:realm-annotations:$realm_version"
api "io.realm:realm-android-library:$realm_version"
api "io.realm:realm-android-kotlin-extensions:$realm_version"
kapt "io.realm:realm-annotations-processor:$realm_version"
}
如果您正在使用Realm对象服务器,realm-android-kotlin-extensions
并且realm-android-library
需要加上后缀,-object-server
那么它们将成为:realm-android-kotlin-extensions-object-server
和realm-android-library-object-server
。
如何从虚拟机调试?
Chrome调试器尝试连接到端口8083上运行的域服务器。
您必须将8083端口发送到主机的8083端口。
socat tcp-listen:8083,bind=localhost,reuseaddr,fork tcp:<VM address>.1:8083
然后,您需要重定向 到`localhost:8083`。
socat tcp-listen:8083,bind=<VM address>,reuseaddr,fork tcp:localhost:8083
为了到达Android端口,您可能需要重定向localhost:8083
到android的端口。如果您运行npm run android
自动运行,则会发生这种情况adb forward tcp:8083 tcp:8083
。这将使得localhost:8083
到达运行Realm服务器的android的8083端口。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具