Предыдущие задачи, ожидающие в базе данных Firebase Realtime, должны быть выполнены в первую очередь перед запуском новой.
Я использую Task API в своем приложении для извлечения данных из базы данных 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, где пользователи могут иметь несколько комнат, и поведение, которое мы видели, происходит, когда пользователь входит в комнату. Это поток:
- Пользователь входит в комнату, я запрашиваю данные для деталей комнаты.
- Получив информацию о комнате, я показываю ее, затем запрашиваю сообщения. Я получаю только самые последние 10. В течение этого времени я просто показываю индикатор выполнения операции.
Для того чтобы детали сообщения были полными, я получаю данные из разных узлов Firebase, именно здесь я в основном использую Задачи.
- Получив сообщения, я передаю их в представление, чтобы отобразить сообщения, а затем прикрепляю прослушиватель новых сообщений. Все работает как положено.
Поведение, которое я упоминал в начале, заметно, когда пользователь делает что-то вроде этого:
- Пользователь входит в комнату с сообщениями, информация о комнате извлекается мгновенно, сообщения все еще загружаются.
- Пользователь покидает комнату (нажимает кнопку "Назад"), он возвращает пользователя в список комнат и вводит другой.
На этом этапе поиск информации о комнате занимает очень много времени - что, по нашему мнению, было странно, поскольку данные на самом деле не так уж велики с самого начала.
После еще нескольких испытаний мы пришли к выводу, что длительное время поиска было вызвано тем, что текущая задача (получить информацию о комнате) все еще ожидает, пока предыдущая задача (получить сообщения) была запущена в другом действии, чтобы сначала завершить ее перед началом.
Я попытался реализовать свой ответ здесь, пытаясь использовать CancellableTask
, но я не знаю, как использовать его с моей текущей реализацией, где я использую TaskCompletionSource
где вы могли только установить результат или исключение.
Я думал, что это может сработать, если я переместу источник завершения задачи на уровень класса интерактора вместо помощника - я еще не пробовал. Я думаю, что это возможно, но потребуется много времени, чтобы реорганизовать классы, которые у меня уже есть.
Так что я понимаю, почему бы не попробовать ответ Дуга, используя слушателей с заданной активностью. Поэтому я проверил это, как показано ниже.
В своей деятельности я добавил 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());
}
});
}
К сожалению, похоже, что это не работает, выход из операции все еще ожидает завершения задач, прежде чем начнется новая задача, инициированная в другой операции.
1 ответ
Если вы запускаете запрос к базе данных реального времени, он всегда будет выполняться до конца, независимо от того, есть ли какие-либо прослушиватели, связанные с возвращенной задачей. Невозможно отменить эту работу ни путем удаления последнего прослушивателя вручную, ни с помощью прослушивателей с заданной областью действия, которые удаляются автоматически. Запросы в движении остаются в движении. Кроме того, весь трафик в RTDB и из него передается по конвейеру через один сокет, что подразумевает, что результаты последующих запросов после одного неполного будут ждать, пока все в очереди в очереди не завершится первым. Вероятно, это является основной причиной вашего наблюдения - у вас есть неполный запрос, который ожидают другие запросы, независимо от того, используете ли вы Task API.
К счастью, если у вас включена постоянство, второй запрос должен обслуживаться кешем первого запроса и не требовать повторной передачи на сервер.
Если вам нужно убедиться, что вы сохраняете результаты первого запроса при изменениях конфигурации, которые разрушают активность, вам следует использовать что-то вроде LiveData из компонентов архитектуры Android, чтобы управлять этим, чтобы вы могли подобрать запрос, который оставил выключен после изменения конфигурации. Если вы сделаете это, не используйте слушателей с заданной активностью.
Я написал пост в блоге из трех частей об использовании компонентов архитектуры с Firebase, который также может представлять интерес.
Привет, ты можешь использовать 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) {
}
});