Предыдущие задачи, ожидающие в базе данных Firebase Realtime, должны быть выполнены в первую очередь перед запуском новой - PullRequest
0 голосов
/ 28 апреля 2018

Я использую 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, в котором пользователи могут иметь несколько комнат, и поведение, которое мы видели, происходит, когда пользователь входит в комнату. Это поток:

  1. Пользователь входит в комнату, я запрашиваю данные для деталей комнаты.
  2. Получив информацию о комнате, я показываю ее, затем запрашиваю сообщения. Я получаю только самые последние 10. В течение этого времени я просто показываю индикатор выполнения операции.

Чтобы детали сообщения были полными, я получаю данные из разных узлов Firebase, именно здесь я в основном использую Задачи.

  1. Получив сообщения, я передаю их в View для отображения сообщений, затем прикрепляю прослушиватель новых сообщений. Все работает как положено.

Поведение, которое я упоминал в начале, заметно, когда пользователь делает что-то вроде этого:

  1. Пользователь входит в комнату с сообщениями, информация о комнате извлекается мгновенно, сообщения все еще загружаются.
  2. Пользователь покидает комнату (нажимает кнопку «Назад»), он возвращает пользователя в список комнат и вводит другой.

На этом этапе поиск информации о комнате занимает очень много времени - что, по нашему мнению, было странно, поскольку данные на самом деле не так уж и велики.

После еще нескольких испытаний мы пришли к выводу, что длительное время поиска было вызвано тем, что текущая задача (получить информацию о комнате) все еще ожидает, пока предыдущая задача (получить сообщения) была запущена в другом действии, чтобы сначала завершить ее перед запуском.

Я попытался реализовать свой ответ здесь , пытаясь использовать 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 Ответ

0 голосов
/ 28 апреля 2018

Если вы запускаете запрос к базе данных реального времени, он всегда будет выполняться до конца, независимо от того, есть ли какие-либо прослушиватели, связанные с возвращенной задачей. Невозможно отменить эту работу ни путем удаления последнего прослушивателя вручную, ни с помощью прослушивателей с заданной активностью, которые удаляются автоматически. Запросы в движении остаются в движении. Кроме того, весь трафик в RTDB и из него передается по конвейеру через один сокет, что означает, что результаты последующих запросов после одного неполного будут ждать, пока все в очереди в очереди завершится первым. Вероятно, это является основной причиной вашего наблюдения - у вас есть неполный запрос, который ожидают другие запросы, независимо от того, используете ли вы Task API.

К счастью, если у вас включена постоянство, второй запрос должен обслуживаться кешем первого запроса и не требовать повторной передачи на сервер.

Если вам нужно убедиться, что вы сохраняете результаты первого запроса при изменениях конфигурации, которые разрушают активность, вам следует использовать что-то вроде LiveData из компонентов архитектуры Android, чтобы управлять этим, чтобы Вы можете выбрать запрос, который был остановлен после изменения конфигурации. Если вы сделаете это, не используйте слушателей с заданием активности.

Я написал пост из трех частей о , использующем компоненты архитектуры с Firebase , который также может представлять интерес.

...