5

我使用 Realm 3.0.0 作为我的 Android 应用程序的数据库。这就像一个问卷调查应用程序,用户在应用程序内部进行了很多导航。当我连续使用该应用程序(来回)时,出现以下错误:

Fatal Exception: io.realm.exceptions.RealmError: Unrecoverable error. mmap() failed: Out of memory size: 1073741824 offset: 0 in /Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 109
       at io.realm.internal.SharedRealm.nativeGetSharedRealm(SharedRealm.java)
       at io.realm.internal.SharedRealm.(SharedRealm.java:187)
       at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:229)
       at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:204)
       at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:124)
       at io.realm.Realm.getDefaultInstance(Realm.java:210)

现在我知道造成这种情况的主要原因是没有关闭 Realm 实例。但我已经检查过很多次了。我很肯定我会关闭我打开的每个实例。

该应用程序有许多 Activity 和 Fragment,它们都在其上获取 Realm 实例onCreate并在其onDestroy. 还有其他后台网络作业运行以上传获取 Realm 实例的数据。这些作业在完成运行或取消时关闭其 Realm 实例。

以上所有内容都是通过 Dagger 2 通过注入获得他们的 Realm 实例:

  @Provides
  @Nullable
  static Realm realm(@Nullable RealmConfiguration configuration) {
    if (configuration != null) {
      Realm.setDefaultConfiguration(configuration);
      return Realm.getDefaultInstance();
    }
    return null;
  }

配置也在同一个 Dagger 模块中提供。

更具体地说,问卷由 ViewPager 中显示的许多问题片段组成。每个片段都被注入一个领域。给定问题片段中的许多交互将数据写入数据库(一些异步,一些阻塞)。这些片段还查询数据库onResume以获取其更新的数据。其中一些数据也通过realm.copyFromRealm(). 现在,在这些发生的任何给定时间,上传作业很可能正在运行并从数据库读取数据并将其上传到服务器。上传作业完成后,它会写入数据库。

我认为在给定时刻,我最多可以有 7-12 个片段/活动在 UI 线程上持有领域引用。以及 0-3 个其他线程(后台作业)上的 0-6 个其他引用。

此外,我在每次应用程序启动时压缩我的领域数据库Realm.compactRealm(realmConfiguration)(也许作为一个单独的问题,这似乎并没有始终如一地完成它的工作)。

上面我试图描述性地描述我的 Realm 用法,而没有详细说明。现在我的问题是,当用户过度使用应用程序时(在活动/片段之间来回切换(领域注入 + DB 读取查询),上传数据(领域注入 + DB 读写查询)),我得到上面发布的内存不足错误。

我也在使用 Leak Canary,它没有检测到任何泄漏。(不确定是否可以)

我是否以不应该使用的方式使用 Realm?我应该关闭 Realm 实例onPause而不是onDestroy?我是否应该在一个活动中只有一个领域实例,并让它的所有片段(在我的情况下最多 5 个)都使用这个实例?我可以在我的应用程序中进行哪些更改,也许我的应用程序架构可以解决这个问题?

感谢您尝试解决此问题的任何帮助。

编辑:我在后台线程中共享领域打开关闭逻辑。

我所有的工作共享相同的领域使用情况,如下所示:领域是通过以下方式惰性注入的:

@Inject protected transient Lazy<Realm> lazyRealm;

领域对象引用保存在private transient Realm realm;字段中。我正在使用Android Priority Job Queue。添加作业时:

@Override
public void onAdded() {
    realm = lazyRealm.get();
    realm.executeTransaction(realm1 -> {
        //write some stuff into realm
    });
    realm.close();
}

并且当作业运行时,领域会被检索一次,并且此方法的每个可能的结束都调用realm.close()

@Override public void onRun() throws Throwable {
    synchronized (syncAdapterLock) {
      realm = lazyRealm.get();
      Answer answer = realm.where(Answer.class).equalTo(AnswerQuery.ID, answerId).findFirst();
      if (answer == null) {
        realm.close();
        throw new RealmException("File not found");
      }
        final File photoFile = new File(answer.getFilePath());
        final Response response = answerService.uploadPhotoAnswer(answerId, RequestBody.create(MediaType.parse("multipart/form-data"), photoFile)).execute();
        if (!response.isSuccessful()) {
          realm.close();
          throw new HttpError(statusCode);
        }
        realm.executeTransaction(realm1 -> {
          answer.setSyncStatus(SyncStatus.SYNCED.getCode());
        });
      }
      realm.close();
    }
  }

如您所见,就我而言,这些后台线程确实正确关闭了它们的领域实例。

4

1 回答 1

8

虽然我所有的后台任务确实调用realm.close()了,但其中一个调用它在它的生命周期中为时已晚。那是我的 GPSService,它是一个后台服务。问题是 GPS 服务在应用程序启动时被初始化为 Android 服务,很少被破坏。我正在注入一个领域实例onCreate并关闭它onDestroy。在@EpicPandaForce 的评论和阅读他关于正确使用领域的文章之后。我意识到这是泄漏的原因。非循环线程保持开放领域引用的时间非常长,因此,mmap每次写入事务发生时都会膨胀。现在,每次服务运行时我都会移动领域获取/关闭,我的问题就解决了。

我的收获是需要非常小心地对待后台线程领域访问。感谢您的快速回复和帮助!

于 2017-04-10T13:26:15.503 回答