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

时间:2018-04-28 03:50:59

标签: android firebase firebase-realtime-database google-tasks-api

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

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()返回一个Task对象,我在我的交互器类中使用它(我使用我的应用程序的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);

    }

}

这可以按预期工作。但是,我们注意到一种行为,即在检索大量数据时,即使启动任务的活动已经finish(),该任务仍会继续并尝试完成。我相信,这可以被视为内存泄漏,因为即使它已经被停止/销毁,过程仍在继续。

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

为了提供更多背景信息,我们正在开发类似于Telegram的聊天应用程序,用户可以拥有多个房间,并且当用户进入房间时我们看到的行为正在发生。这是流程:

  1. 用户进入房间,我要求提供房间详细信息的数据。
  2. 获取房间详细信息后,我显示它,然后请求消息。我只检索最近的10.在此期间,我只在活动上显示一个进度条。
  3. 为了完成消息详细信息,我从Firebase上的不同节点获取数据,这是我主要使用Tasks的地方。

    1. 收到消息后,我将其传递给View,显示消息,然后我为新消息附加一个监听器。一切都按预期工作。
    2. 当用户执行以下操作时,我在开头提到的行为很明显:

      1. 用户进入有消息的房间,即时检索房间详细信息,消息仍在加载。
      2. 用户离开房间(按下后退按钮),这会让用户返回房间列表,然后输入另一个房间列表。
      3. 此时,房间细节的检索需要很长时间 - 我们认为这很奇怪,因为数据开始时并不是那么大。

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

        我尝试实现我的回答here,尝试使用CancellableTask,但我对如何将其用于我当前的实现感到茫然,我使用了TaskCompletionSource ,您只能设置结果或例外。

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

        所以我想为什么不尝试Doug's answer,使用活动范围的侦听器。所以我测试了它如下。

        在我的活动中,我添加了一个getActivity()方法,可以在演示者中调用:

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

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

2 个答案:

答案 0 :(得分:2)

如果您启动对实时数据库的查询,它将始终运行完成,无论是否有任何侦听器附加到返回的任务。无法通过手动删除最后一个侦听器,也无法使用自动删除的活动范围侦听器来取消该工作。运动中的查询保持运动。 此外,进出RTDB的所有流量都是通过单个套接字进行流水线操作的,这意味着后续查询的结果不完整后,必须等待队列中的所有内容先完成后才能完成。这可能是您观察的根本原因 - 您有一个不完整的查询,其他查询正在等待,无论您使用任务API。

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

如果您需要确保在破坏活动的配置更改中保留第一个查询的结果,那么您应该使用Android架构组件中的LiveData之类的东西来管理它,以便您可以在配置更改后选择停止的查询。如果这样做,请不要使用活动范围的侦听器。

我写了一篇关于using architecture components with Firebase的三篇博客文章,这也可能是有趣的。

答案 1 :(得分:0)

嘿,您可以使用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) {

        }
    });