等待 Firebase 实时数据库的先前任务需要在新任务开始之前先完成

Previous task(s) waiting on Firebase Realtime Database need to complete first before new one starts

我在我的应用程序中使用 Task API 从 Firebase 数据库中检索数据,这些数据通常来自不同的节点。我有一个 Firebase 数据库的助手 class,如下所示:

public class FirebaseDbHelper {

    public Task<DataSnapshot> getData() {
        TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
        DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference(FIRST_NODE).child(SUB_NODE);
        dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                source.setResult(dataSnapshot);
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                source.setException(databaseError.toException());
            }
        });
        return source.getTask();
    }

}

如您所见,getData() returns 一个 Task 对象,我在我的交互器 class 上使用它(我正在为我的应用程序使用 MVP 架构),如下所示:

public class TestDbInteractor {

    private FirebaseDbHelper mDbHelper;
    private Listener mListener;

    public TestDbInteractor(@NonNull Listener listener) {
        mDbHelper = new FirebaseDbHelper();
        mListener = listener;
    }

    void getData() {
        mDbHelper.getData().addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                mListener.onGetDataSuccess(new MyObject(task.getResult()));
            } else {
                mListener.onGetDataFailed(task.getException());
            }
        });
    }

    public interface Listener {

        void onGetDataSuccess(MyObject object);

        void onGetDataFailed(Exception exception);

    }

}

这按预期工作。但是,我们注意到一个行为,当检索大量数据时,即使启动任务的 activity 已经 finish()ed,任务仍然继续并尝试完成。我认为,这可以被视为内存泄漏,因为一个进程仍在运行,即使它应该已经 stopped/destroyed。

更糟糕的是,当我尝试获取不同的数据时(在不同 activity 中使用不同的任务到 Firebase 中的不同节点),我们注意到它首先等待前一个任务完成在继续这个新的之前。

为了提供更多上下文,我们正在开发一个类似于 Telegram 的聊天应用程序,用户可以在其中拥有多个房间,我们看到的行为是在用户进入房间时发生的。这是流程:

  1. 用户进入房间,我请求房间详细信息的数据。
  2. 获取房间详细信息后,我会显示它,然后请求消息。我只检索了最近的10个。在这段时间里,我只是在activity.
  3. 上显示一个进度条

为了消息细节完整,我从Firebase的不同节点获取数据,这也是我主要使用Tasks的地方。

  1. 收到消息后,我将其传递给视图以显示消息,然后为新消息附加一个侦听器。一切都按预期工作。

当用户执行如下操作时,我在开头提到的行为很明显:

  1. 用户进入带有消息的房间,立即检索房间详细信息,消息仍在加载中。
  2. 用户离开房间(按后退按钮),这会使用户返回房间列表,然后进入另一个房间。

此时,房间详细信息的检索花费了如此长的时间 - 我们认为这很奇怪,因为数据并没有那么大。

经过多次测试,我们得出结论,检索时间过长是由于当前任务(获取房间详细信息)仍在等待上一个任务(获取消息)在不同的activity开始,先完成再开始。

我试图实现我的答案 ,尝试使用 CancellableTask,但我不知道如何在我当前的实现中使用它,我使用 [=17] =], 只能设置结果或异常。

我在想,如果我将任务完成源移动到交互器 class 级别而不是助手级别,这可能会起作用——我还没有尝试过。我认为这是可能的,但需要花费大量时间来重构我已经拥有的 classes。

所以我想为什么不尝试 ,使用 activity 范围的侦听器。所以我像下面这样测试它。

在我的activity中添加了一个getActivity()方法,可以在presenter中调用:

public class TestPresenter
        implements TestDbInteractor.Listener {

    private View mView;

    private TestDbInteractor mDbInteractor;

    @Override
    void bindView(View view) {
        mView = view;

        mDbInteractor = new TestDbInteractor(this);
    }

    @Override
    void requestMessages() {
        mDbInteractor.getData(mView.getActivity());
    }

    // Listener stuff below

}

并像这样更新了我的 getData()

void getData(@NonNull Activity activity) {
    mDbHelper.getData().addOnCompleteListener(activity, task -> {
        if (task.isSuccessful()) {
            mListener.onGetDataSuccess(new MyObject(task.getResult()));
        } else {
            mListener.onGetDataFailed(task.getException());
        }
    });
}

不幸的是,这似乎不起作用,退出 activity 仍然等待任务完成,然后在另一个 activity 中启动的新任务开始。

如果您启动对实时数据库的查询,它将始终 运行 完成,无论是否有任何侦听器附加到返回的任务。没有办法取消这项工作,既不能通过手动删除最后一个侦听器,也不能通过使用自动删除的 activity-scoped 侦听器。运动中的查询保持运动。 此外,进出 RTDB 的所有流量都通过单个套接字进行管道传输,这意味着在一个不完整的查询之后的后续查询结果将不得不等待队列中它前面的所有内容先完成。 这可能是您观察的根本原因 - 您有一个不完整的查询,其他查询正在等待,无论您使用任务 API.

幸运的是,如果您启用了持久化,则第二个查询应该由第一个查询的缓存提供服务,而不需要再次往返服务器。

如果您需要确保在破坏 activity 的配置更改中保留第一个查询的结果,那么您应该使用 Android 中的 LiveData体系结构组件来管理它,以便您可以在配置更改后从中断的地方继续查询。如果这样做,请不要使用 activity 范围的侦听器。

我写了一篇关于 using architecture components with Firebase 的三部分博客 post,您可能也会感兴趣。

嘿,您可以使用 childEventListener。使用 dataSnapshot.getChildrenCount().

    dbFriend=FirebaseDatabase.getInstance().getReference("Friend");
    dbFriend=dbFriend.child(mPreferences.getString("username","")).child("already");

    dbFriend.addChildEventListener(new ChildEventListener() {
        int already=0;
        @Override
        public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            Username u=dataSnapshot.getValue(Username.class);


            already=alread+1;
            if(already >= dataSnapshot.getChildrenCount()){

                //get to know when data fetching got completed

            }

        }

        @Override
        public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

        }

        @Override
        public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

        }

        @Override
        public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });