错误线程访问 Realm

23

我有一个应用程序,其中包含一个名为LoginActivity的活动。当用户成功登录时,我会注册以接收消息。然后LoginActivity跳转到MainActivity。 这些到达的消息应该存储在数据库(Realm)中,以便从主实例中恢复。

但是当消息到达时,它会导致 Realm 崩溃,并出现以下错误:

Exception in packet listener
    java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
    at io.realm.BaseRealm.checkIfValid(BaseRealm.java:383)
    at io.realm.Realm.executeTransactionAsync(Realm.java:1324)
    at io.realm.Realm.executeTransactionAsync(Realm.java:1276)
    at es.in2.in2tant.LoginActivity.newMessageReceived(LoginActivity.java:124)
    at es.in2.in2tant.Connection.Connection$4$1.processMessage(Connection.java:227)
    at org.jivesoftware.smack.chat.Chat.deliver(Chat.java:180)
    at org.jivesoftware.smack.chat.ChatManager.deliverMessage(ChatManager.java:351)
    at org.jivesoftware.smack.chat.ChatManager.access$300(ChatManager.java:53)
    at org.jivesoftware.smack.chat.ChatManager$2.processPacket(ChatManager.java:162)
    at org.jivesoftware.smack.AbstractXMPPConnection$4.run(AbstractXMPPConnection.java:1126)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)

我对Realm的工作原理有些迷失,不知道如何在整个应用程序中使其可访问,而且还要保证接收自LoginActivity的消息不会导致崩溃。请问有什么帮助或方法可以实现这一点吗?

LoginActivity.java:

public class LoginActivity extends AppCompatActivity implements ConnectionConnectResponse {
.....
protected void onCreate(Bundle savedInstanceState) {
//Realm Init config:
        Realm.init(this);
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build();
        Realm.deleteRealm(realmConfiguration); // Clean slate
        Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default


@Override
    public void newMessageReceived(final ChatMessage message) {
        Logger.d("NEWMESSAGERECEIVED :" + message);


        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {

                Message receivedMessage = realm.createObject(Message.class, message.id);
                receivedMessage.setBodyMessage(message.message);
                receivedMessage.setFrom(message.from);
                receivedMessage.setTo(message.to);
                receivedMessage.setDelivered(false);
                receivedMessage.setMine(false);
                receivedMessage.setDate(Calendar.getInstance().getTime());
            }
        });
        //Logger.d("NEWMESSRE: LAST MESSAGE:" + realm.where(Message.class).equalTo("chatID", message.id));
    }

@Override
    protected void onStart() {
        super.onStart();
        realm = Realm.getDefaultInstance();
    }

    @Override
    protected void onStop() {
        super.onStop();
        realm.close();
    }

所需图像:

输入图像描述

4个回答

25

从错误的线程访问 Realm。 Realm对象只能在创建它们的线程上访问

这个错误信息相当简单明了。

我看到您正在 UI 线程上通过调用 Realm.getDefaultInstance() 来初始化 realm

错误来自于 newMessageReceived(),所以我猜测该方法是从后台线程调用的。

请在后台线程获取一个 Realm 实例,而不是使用全局实例:

@Override
public void run () {
    Realm backgroundRealm = Realm.getDefaultInstance();
    backgroundRealm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            Message receivedMessage = realm.createObject(Message.class, message.id);
            receivedMessage.setBodyMessage(message.message);
            receivedMessage.setFrom(message.from);
            receivedMessage.setTo(message.to);
            receivedMessage.setDelivered(false);
            receivedMessage.setMine(false);
            receivedMessage.setDate(Calendar.getInstance().getTime());
        }
    });
}

或者,如果您出于某些原因想要继续使用全局的Realm实例,则请确保通过调用runOnUiThread()(或直接将Runnable发布到主线程的消息队列中通过Handler)来在UI线程上执行代码:

@Override
public void newMessageReceived(final ChatMessage message) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    Message receivedMessage = realm.createObject(Message.class,
                        message.id);
                    receivedMessage.setBodyMessage(message.message);
                    receivedMessage.setFrom(message.from);
                    receivedMessage.setTo(message.to);
                    receivedMessage.setDelivered(false);
                    receivedMessage.setMine(false);
                    receivedMessage.setDate(Calendar.getInstance().getTime());
                }
            });
        }
    });
}

我们应该在这里如何关闭realm实例? - Asdinesh

4

每次访问数据库时,只需创建 Realm backgroundRealm = Realm.getDefaultInstance(),并使用 realm.close() 关闭它即可。请注意,保留html标签。


1

在事务之前分配实例,并在事务完成后立即释放它,这样你就不会有挂起的连接,通过这样做,你可以从线程调度器中节省开销。我使用“数据访问对象”接口来操作数据,该接口是使用Realm实现的。不要使用“transaction async”,而是同步地执行所有调用,但在后台线程上执行“数据访问对象”的调用。对此的好解决方案是rxJava库。只需一直获取和释放Realm实例-库使其廉价。


使用异步事务API没有任何问题。而且那个人几乎不了解Realm,为什么你还要告诉他用Rx进行全面的FRP呢? - EpicPandaForce
想象一下,您必须执行一个事务,完成该事务后,执行写入事务或某些读取事务。只有在第一个操作完成后才能启动第二个操作。如果使用async(),则无法跟踪先前事务的完成情况。或者,如果在一个线程上创建对象并意外地在另一个线程上使用该实例(使用线程池),则会出现错误。另一方面,当您在事务之前分配实例并在事务之后释放它时,就不会出现这种情况。 - Alex Shutov

0

我还没有使用过 Realm。但是从错误信息中我理解到,ConnectionConnectResponse 可能在 Loginactivity 结束时仍然存在。因此,你应该将 Realm 实例作为参数传递给

newMessageReceived(Realm realm, final ChatMessage message)

并将所有的 Realm 初始化代码放在触发此方法的类中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接