Я использую 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()
ed, задание все еще продолжается и пытается завершиться. Я считаю, что это то, что можно рассматривать как утечку памяти, поскольку процесс все еще продолжается, даже если он уже должен быть остановлен / уничтожен.
Хуже всего то, что когда я пытаюсь получить другие данные (используя другую задачу в другом действии, отличном от другого узла в Firebase), мы заметили, что она ждет, пока предыдущая задача завершится первой, прежде чем приступить к этой новой. .
Чтобы получить больше контекста, мы разрабатываем приложение для чата, похожее на Telegram, в котором пользователи могут иметь несколько комнат, и поведение, которое мы видели, происходит, когда пользователь входит в комнату. Это поток:
- Пользователь входит в комнату, я запрашиваю данные для деталей комнаты.
- Получив информацию о комнате, я показываю ее, затем запрашиваю сообщения. Я получаю только самые последние 10. В течение этого времени я просто показываю индикатор выполнения операции.
Чтобы детали сообщения были полными, я получаю данные из разных узлов Firebase, именно здесь я в основном использую Задачи.
- Получив сообщения, я передаю их в View для отображения сообщений, затем прикрепляю прослушиватель новых сообщений. Все работает как положено.
Поведение, которое я упоминал в начале, заметно, когда пользователь делает что-то вроде этого:
- Пользователь входит в комнату с сообщениями, информация о комнате извлекается мгновенно, сообщения все еще загружаются.
- Пользователь покидает комнату (нажимает кнопку «Назад»), он возвращает пользователя в список комнат и вводит другой.
На этом этапе поиск информации о комнате занимает очень много времени - что, по нашему мнению, было странно, поскольку данные на самом деле не так уж и велики.
После еще нескольких испытаний мы пришли к выводу, что длительное время поиска было вызвано тем, что текущая задача (получить информацию о комнате) все еще ожидает, пока предыдущая задача (получить сообщения) была запущена в другом действии, чтобы сначала завершить ее перед запуском.
Я попытался реализовать свой ответ здесь , пытаясь использовать 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());
}
});
}
К сожалению, это, похоже, не работает, хотя выход из действия все еще ожидает завершения задач, прежде чем начнется новая задача, инициированная в другом действии.