为有牺牲多壮志,敢教日月换新天。

Realm Java

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/11166066.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

入门

先决条件

  • 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此处查找项目级别文件:

项目级build.gradle文件

第2步:realm-android插件应用到应用程序级build.gradle文件的顶部

apply plugin: 'realm-android'

build.gradle此处查找应用程序级别文件:

应用程序级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。

Realm Studio

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仅在当前流程中强制执行。其他进程或设备仍可以写入readOnlyRealms。此外,任何针对只读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方法中执行如果需要创建LooperUI以外线程,可以使用以下模式:

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

如果您正在使用ThreadRunnable用于短期任务:

// 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 >= 19Java >= 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; }
}

一个领域模型类支持publicprotectedprivate领域,以及自定义方法。

public class User extends RealmObject {

    public String name;

    public boolean hasLongName() {
      return name.length() > 7;
    }

    @Override
    public boolean equals(Object o) {
      // Custom equals comparison
    }
}

字段类型

境界支持booleanbyteshortintlongfloatdoubleStringDatebyte[]字段类型。整数类型byteshortint,并且long都被映射到long领域内。除了那些标准字段类型之外,Realm还支持子类RealmObjectRealmList<? extends RealmObject>模型关系。

盒装类型BooleanByteShortIntegerLongFloatDouble也可以在模型中的类使用。这些类型可能具有价值null

必填字段

@Required注释可以用来告诉境界不允许null在一个字段的值,使之需要,而不是可选的。只有BooleanByteShortIntegerLongFloatDoubleStringbyte[]并且Date可以进行注释@Required如果将其添加到其他字段类型,编译将失败。

RealmList隐式地需要具有基本类型和类型的字段RealmObject类型的字段始终可以为空。

主键

要将字段标记为模型的主键,请使用注释@PrimaryKey字段类型必须是一个字符串(String)或整数(byteshortintlongByteShortInteger,和Long)。使用字符串字段作为主键会自动为字段编制索引:@PrimaryKey字符串上的注释会隐式设置注释@IndexRealm不支持复合键,即使用多个字段作为单个主键。

使用主键可以使用copyToRealmOrUpdateinsertOrUpdate方法。它们查找具有给定主键的对象,并更新它(如果具有该键的对象已存在)或创建它(如果该键不存在)。如果您在没有主键的情况下调用copyToRealmOrUpdateinsertOrUpdate上课,则会抛出异常。

当您使用主键时,读取(查询)会稍微快一些,但写入(创建和更新对象)会慢一些。性能的变化取决于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类型或盒装整数(ByteShortInteger,和Long)的值可以是null,除非@PrimaryKey注释与组合@Required

索引属性

要索引字段,请使用注释@Index与主键一样,这会使写入速度稍慢,但会使读取速度更快。(它还会使您的Realm文件略大,以存储索引。)最好只在优化特定情况下的读取性能时添加索引。

你可以索引StringbyteshortintlongbooleanDate领域。

忽略属性

如果您不想将模型中的字段保存到其Realm,请使用注释@Ignore例如,如果您的输入包含的字段多于模型,并且您不希望有许多特殊情况来处理这些未使用的数据字段,则可以执行此操作。

字段标statictransient总是被忽略,并且不需要@Ignore注释。

计数器

Realm提供MutableRealmInteger作为特殊整数类型。MutableRealmInteger公开了一个额外的API,可以更清楚地表达意图,并在使用Synchronized Realms时生成更好的冲突解决步骤

传统上,计数器将通过读取值,递增并设置(myObj.counter += 1)来实现。这在异步情况下无法正常工作 - 例如,当两个客户端处于脱机状态时 - 因为双方都会读取一个值,比如10增加它,并将值存储为11最终,当他们重新获得连接并尝试合并他们的更改时,他们会同意计数器处于11预期状态而不是预期状态12

MutableRealmIntegerS被传统的整数类型的支持,所以从改变字段时就不需要进行迁移byteshortintlongMutableRealmInteger

MutableRealmInteger不是像Java中的原始数字类型那样的不可变类型标准。这是一个活生生的物体一样RealmObjectRealmResultsRealmList这意味着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

自动更新对象

RealmObjects是对基础数据的实时,自动更新视图; 你永远不必刷新对象。对象的更改会立即反映在查询结果中。

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依赖于特定RealmObjectRealmResults实例,则在更新UI之前无需担心刷新或重新获取它。

您可以订阅Realm通知以了解Realm数据何时更新。

自定义对象

可以使用RealmObject几乎像POJO扩展您的课程RealmObject您可以让字段公开,并且可以使用简单的分配而不是setter和getter。

public class Dog extends RealmObject {
    public String name;
    public int age;
}

您可以Dog使用任何其他类一样使用:您可以为getter和setter方法添加逻辑(例如,用于验证),并且可以添加任何您想要的自定义方法。

要将Dog对象添加到Realm,请使用createObjectcopyToRealm方法:

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 StringJSONObjectInputStreamRealm将忽略未定义的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模型中保持不变。

适配器

领域提供抽象的实用工具类,帮助绑定的数据来自何处OrderedRealmCollectionS(两者RealmResultsRealmList实现该接口)标准的UI部件。

要使用适配器,请将依赖项添加到应用程序级别build.gradle

dependencies {
    compile 'io.realm:android-adapters:2.1.1'
}

适配器的Javadoc可以在这里找到,可以在这里找到它们的使用示例

意图

由于RealmObjects不是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;

这将删除之间的关系bobemail1,但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;
}

RealmLists是s的容器RealmObject一个RealmList行为就像一个普通的Java List您可以在不同的RealmLists中使用相同的对象,并且可以使用它来模拟一对多和多对多关系。

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

将值设置nullRealmList字段将清除列表。列表将为空(长度为零),但列表中的对象不会从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:可以是以下类型StringIntegerBooleanFloatDoubleShortLongBytebyte[]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在单个文件中包含多个声明。如果您有两个或更多个RealmModules,则必须将声明拆分为多个文件,每个文件只有一个声明。

在此处查看 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但速度要快得多,因为不返回对象可以更好地对其进行优化。

如果要插入许多对象,建议的方法是使用insertinsertOrUpdate

List<User> users = Arrays.asList(new User("John"), new User("Jane"));

realm.beginTransaction();
realm.insert(users);
realm.commitTransaction();

交易块

不用手动保持的跟踪beginTransactioncommitTransaction以及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.
            }
        });

OnSuccessOnError回调都是可选的,但如果提供,它们将分别在事务成功或失败时被调用。回调由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参考。

查询返回匹配对象的引用列表,因此您可以直接使用与查询匹配的原始对象。RealmResultsAbstractList类似的方式继承和行为。例如,按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.INSENSITIVECase.SENSITIVE默认是Case.SENSITIVE

谓词like执行glob样式的通配符匹配。匹配模式由字符和一个或多个通配符组成:

  • * 匹配0个或更多Unicode字符
  • ? 匹配单个Unicode字符

例如,考虑一个带有四个对象的Realm,其中一个字段name的值为William,Bill,Jill和Trillian。谓词like("name", "?ill*")将匹配前三个对象,并like("name", "*ia?")匹配第一个和最后一个对象。

二进制数据,字符串和RealmObjects(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; }
}

您还可以使用beginGroupendGroup指定评估顺序对条件进行分组

RealmResults<User> r = realm.where(User.class)
                            .greaterThan("age", 10)  // implicit AND
                            .beginGroup()
                                .equalTo("name", "Peter")
                                .or()
                                .contains("name", "Jo")
                            .endGroup()
                            .findAll();

否定条件not您可以使用not运算符和beginGroupendGroup仅取消子条件。如果您想要找到未命名为“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而不是构建的RealmQueryRealmQuery对象添加更多条件时,您正在修改查询本身。

可以查询链接或关系。考虑下面的模型:

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对象PersonDog

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();

使用反向关系,您可以扩展查询的可能性。让我们考虑相同的两个模型类,PersonDogPerson您可以先查询狗,而不是使用反向关系,而不是启动查询

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要在迭代时删除元素RealmListIterator.remove()应该使用代替RealmList.remove()或其他API来RealmList间接删除元素以避免ConcurrentModificationExceptionRealmResultsRealmListcreateSnapshot一个手动创建一个方法。

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
}

调用同步获得isLoadedRealmResults对象将始终返回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作为一个整体被更改时发送领域通知更改,添加或删除单个对象时会发送收集通知

通过调用removeChangeListenerremoveAllChangeListeners方法停止通知传递如果注册侦听器的对象被垃圾回收,或者其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的狗列表中添加或删除Person
  • 你修改的年龄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,以便在删除对象时或在对象上的任何管理字段修改其值时通知您。

只有托管RealmObjects可以在其上注册监听器。

可以通过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

ChangeListeners不适用于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展示了如何使用RealmBaseAdapterRealmRecyclerViewAdapter制作Realm与Android作业的ListViewRecyclerView以一种优雅的方式。

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-androidkotlin-kapt这样的:
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
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();

请注意,查询需要在strFieldwith String值上完成,而不是enumField由Realm忽略,因为它没有后备字段

  • 在科特林Long::class.java实际上返回一个Class引用longLong这同样适用于其他原始类型像真正的IntegerFloatDoubleBoolean在迁移期间选择正确的类会产生影响:
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()Kotlin in中的关键字。

可以使用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时需要注意一些重要的限制:

  1. 如果您的模型包含RealmList您需要注册特殊适配器
  2. 一旦对象被分区,它就会与Realm分离,此时的行为就像一个包含数据快照的非托管对象。Realm中不会持续对此对象进行进一步更改。

改造

RetrofitSquare的一个库,它使得以类型安全的方式使用REST API变得容易。

Realm将同时使用Retrofit 1. *和2. *,但请注意Retrofit不会自动向Realm添加对象,而是必须使用realm.copyToRealmrealm.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

RxJavaNetflix Reactive Extensions库,它扩展了Observer模式它使得可以将数据的变化视为可组合序列。

Realm拥有对RxJava 2 FlowableRxJava 2的一流支持Observable

// 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中的持久数据。为任何访问者添加监视将产生正确的值。见下图:Android Studio,IntelliJ Debug RealmObject

在上图中,调试器已在第113行停止。有三个监视值,即person变量person.getNameperson.getAge访问器。第107到111行的代码person通过更改名称和年龄来改变实例。然后,这些值将保留在事务中。在第113行,调试器当前处于暂停状态,person监视实例正在报告字段值,但它们不正确。使用访问者的监视值person.getNameperson.getAge报告正确的值。

请注意,该.toString方法将输出正确的值,但是监视面板不会(当观察变量时是a RealmObject)。

NDK调试

Realm是一个包含本机代码的库。我们建议您使用崩溃报告工具(例如Crashlytics)来跟踪本机错误,以便在出现问题时我们能够更好地帮助您。

调试NDK崩溃通常很麻烦,因为默认堆栈跟踪提供了可用的最少信息。Crashlytics将允许您捕获有价值的NDK崩溃信息。要在Crashlytics中启用NDK崩溃报告,请按照本指南中列出步骤操作

要为项目启用NDK崩溃报告,请将其添加到build.gradle文件的根目录。请注意,值androidNdkOutandroidNdkLibsOut是不需要的。

crashlytics {
  enableNdk true
}

目前的局限

Realm通常会尝试尽可能少的约束,并且我们会根据社区的反馈不断添加新功能。但是,Realm仍有一些局限性。有关已知问题的更全面列表,请参阅我们的GitHub问题。

楷模

领域模型不支持finalvolatile字段。这主要是为了避免对象在Realm或非托管方面的行为方式之间存在差异。

不允许领域模型类扩展任何其他对象RealmObject如果声明,则默认构造函数(不带参数的构造函数)必须始终为空。原因是默认的构造函数将调用假定存在Realm实例的方法。但是这个实例不是在构造函数返回之前创建的。为方便起见,您可以添加其他构造函数。

一般

Realm旨在在灵活性和性能之间取得平衡。为了实现这一目标,对在Realm中存储信息的各个方面施加了现实限制。例如:

  1. 类名的上限为57个字符。Realm Java class_以所有名称为前缀,浏览器将其显示为名称的一部分。
  2. 字段名称的长度上限为63个字符。
  3. 在不同的包中不可能有两个具有相同名称的模型类。
  4. 不支持嵌套事务,如果检测到异常,则抛出异常。
  5. Strings和字节数组(byte[])不能大于16 MB。
  6. 领域模型不支持finalvolatile字段。这主要是为了避免对象在Realm或非托管方面的行为方式之间存在差异。
  7. 如果提供了自定义构造函数,则还必须存在公共无参数构造函数。
  8. Realm模型类不允许扩展任何其他类RealmObject

对字符串进行排序和查询

只有Latin Basic,Latin Supplement,Latin Extended A和Latin Extended B(UTF-8范围0-591)中的字符集才支持查询中的排序和不区分大小写的字符串匹配。此外,使用时设置区分大小写标志查询equalTonotEqualTocontainsendsWithbeginsWith,或like只从英语语言环境的字符工作。

Realm对大写和小写字母使用非标准排序,将它们排序在一起而不是先排序大写。这意味着它'- !"#0&()*,./:;?_+<=>123aAbBcC...xXyYzZ是Realm中的实际排序顺序。了解更多关于这些限制在这里

主题

尽管Realm文件可以由多个线程同时访问,但您无法在线程之间移交Realms,Realm对象,查询和结果。线程示例演示如何在多线程环境中使用Realm。阅读有关Realm线程的更多信息。

尽管Realm文件可以由多个线程同时访问,但它们一次只能由一个进程访问。不同的进程应该复制Realm文件或创建自己的文件。

RealmObject的hashCode

RealmObject是活动对象,可能会通过其他线程的更改进行更新。虽然回国2个境界对象trueRealmObject.equals必须有相同的值RealmObject.hashCode,这个值是不是稳定,既不应该被用作一个键HashMap,也没有保存在HashSet

多进程

  • 不支持同时访问不同进程的加密域。有一个Realm Core问题(#1845)跟踪此问题。
  • 不支持从不同APK中的不同进程访问相同的域。这样做是安全的,但通知等内容无法按预期工作。
  • 不支持从不同进程访问同步的域。

增量构建

领域字节码转换器支持增量构建,但在少数情况下需要完整构建。变压器本身无法检测到这些情况。

  • @Ignore在模型类的字段中添加或删除注释。
  • static在模型类的字段中添加或删除关键字。
  • transient在模型类的字段中添加或删除关键字。

在这些情况下未能执行完整构建将导致应用程序崩溃或导致字段返回数据类型的默认值(例如0或null),而不是返回存储在Realm中的真值。

最佳做法

开箱即用,Realm可以与Android无缝协作。你必须记住的主要事情是RealmObjects是线程限制的当您想要在活动,后台服务,广播接收器等之间开始传递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,这也是安全的:即使从未调用onDestroyclose方法,数据库也将保持一致状态

显然,如果与活动相关联的大多数片段需要访问同一数据集,那么控制实例生命周期的活动(而不是单个片段)是有意义的。

// 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进行更改时,所有RealmObjects和都会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位支持:

如何备份和恢复领域?

领域存储在文件系统上的文件中。通过调用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加密密钥的最安全方式。以下是推荐使用它的方法。

  1. 使用Android的KeyStore,生成一个非对称RSA密钥,由Android安全存储/检索。在版本> = M系统需要用户PIN(或指纹)来解锁KeyStore,因此即使在有根设备上,您也有额外的安全层。
  2. 生成对称密钥(AES),用于加密Realm。
  3. 使用私有RSA密钥加密对称AES密钥。
  4. 现在将加密的AES密钥存储在文件系统上安全的SharedPreferences例如)。
  5. 当您需要使用加密的Realm时,检索加密的AES密钥,使用公共RSA密钥对其进行解密,然后在其中使用它RealmConfiguration来打开加密的Realm。

有关端到端的示例,请查看我们的演示存储库:

如何在自定义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/policy2)将SELinux错误复制到名为的文本文件中input.txt3)运行audit2allow工具:audit2allow -p policy -i input.txt4)该工具应输出您可以添加到现有策略中的规则,以允许您使用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-serverrealm-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端口。

posted @ 2019-07-10 19:43  为敢技术  阅读(1214)  评论(0编辑  收藏  举报