我在我的应用中使用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的聊天应用程序,用户可以拥有多个房间,并且当用户进入房间时我们看到的行为正在发生。这是流程:
为了完成消息详细信息,我从Firebase上的不同节点获取数据,这是我主要使用Tasks的地方。
当用户执行以下操作时,我在开头提到的行为很明显:
此时,房间细节的检索需要很长时间 - 我们认为这很奇怪,因为数据开始时并不是那么大。
经过几次测试后,我们得出结论,长检索时间是由当前任务(获取房间详细信息)仍在等待上一个任务(获取消息)在不同活动中启动,在开始之前先完成。
我尝试实现我的回答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());
}
});
}
不幸的是,这似乎并不起作用,退出活动仍然等待任务完成,然后才开始在另一个活动中启动新任务。
答案 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) {
}
});