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

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

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

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

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

  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 ответ

Решение

Если вы запускаете запрос к базе данных реального времени, он всегда будет выполняться до конца, независимо от того, есть ли какие-либо прослушиватели, связанные с возвращенной задачей. Невозможно отменить эту работу ни путем удаления последнего прослушивателя вручную, ни с помощью прослушивателей с заданной областью действия, которые удаляются автоматически. Запросы в движении остаются в движении. Кроме того, весь трафик в 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) {

        }
    });
Другие вопросы по тегам