Android(Kotlin)中Realm结合Promise使用:返回对象列表无效
嘿,我完全懂你碰到的这个坑——Realm查询结果失效几乎都是因为它的活对象(Live Object)特性和Realm实例的生命周期没管理好,结合异步Promise操作的时候特别容易踩这个雷。
问题根源
Realm的List<Top...>是动态绑定到创建它的Realm实例的“活列表”,它并不会直接存储数据,而是实时从Realm数据库中读取。如果你的findCountries方法里,Realm实例在Promise调用resolve之前就被关闭了(比如用了use自动关闭,或者手动调用了close()),那resolve出去的列表就成了“无主”的对象,访问时要么抛出异常,要么返回空数据。
两种可行的解决方案
方案1:将Realm对象转换为非托管(Detached)对象
这是最稳妥的方式,把Realm的活对象复制成普通的Kotlin POJO,脱离对Realm实例的依赖。Realm提供了copyFromRealm方法来实现这一点:
fun findCountries(query: String? = null): Promise<List<TopCountry>, Exception> { val deferred = deferred<List<TopCountry>, Exception>() // 切换到IO线程执行Realm操作,避免阻塞UI CoroutineScope(Dispatchers.IO).launch { // 用use自动管理Realm实例的生命周期 Realm.getDefaultInstance().use { realm -> val realmResults = if (query.isNullOrBlank()) { realm.where(TopCountry::class.java).findAll() } else { realm.where(TopCountry::class.java) .contains("name", query, Case.INSENSITIVE) .findAll() } // 复制为非托管对象列表,脱离Realm实例 val detachedCountries = realm.copyFromRealm(realmResults) // 用复制后的列表resolve Promise deferred.resolve(detachedCountries) } } return deferred.promise }
这样处理后,resolve出去的detachedCountries就是普通的Kotlin对象集合,不再依赖Realm实例,即使Realm被use自动关闭,也能正常访问数据。如果TopCountry有嵌套的Realm对象,copyFromRealm默认会深度复制所有嵌套对象,确保整个数据结构都脱离Realm。
方案2:延迟关闭Realm实例(谨慎使用)
如果你不想复制对象,可以确保Realm实例在Promise的回调全部执行完成后再关闭,但这种方式要特别注意内存泄漏,尤其是在Android环境中:
fun findCountries(query: String? = null): Promise<List<TopCountry>, Exception> { val deferred = deferred<List<TopCountry>, Exception>() val realm = Realm.getDefaultInstance() CoroutineScope(Dispatchers.IO).launch { val realmResults = if (query.isNullOrBlank()) { realm.where(TopCountry::class.java).findAll() } else { realm.where(TopCountry::class.java) .contains("name", query, Case.INSENSITIVE) .findAll() } deferred.resolve(realmResults) // 监听Promise的完成事件,回调结束后关闭Realm deferred.promise.then { realm.close() }.fail { realm.close() } } return deferred.promise }
⚠️ 注意:这种方式如果Promise的回调长期未执行,会导致Realm实例一直持有,可能引发内存泄漏,所以更推荐方案1。
额外提醒
- Android中Realm实例是线程绑定的,不要在不同线程之间传递Realm实例或它的活对象。
- 如果使用第三方Promise库(比如Kovenant),记得在需要更新UI的回调中切换到Main线程。
内容的提问来源于stack exchange,提问作者Fernando Perez

