О чем все-таки говорит developer.android.com про RecyclerView

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


    Начнем с самого начала. Полностью согласен, что "между жизненным циклом активности и работой RecyclerView есть нечто общее" – это "нечто" – необходимость понимать, что мы делаем и зачем. И читать документацию. А невыполнение этих двух необходимостей, как и сон разума, рождает монстров. Только вот с тем, как предлагает с этими монстрами бороться предыдущий автор, я категорически не согласен.


    Рассмотрим 2 условия.


    Условие № раз


    Если вы где-то навешиваете listener, то где-то в другом месте вы должны его отсоединять. Делают это обычно в симметричных функциях – присоединяем в onViewAttachedToWindow, убираем в onViewDetachedFromWindow. Присоединяем в onBindViewHolder… Не присоединяем в onBindViewHolder. Этот вызов не симметричный, он может вызываться несколько раз в зависимости от разных условий. Не надо усложнять себе жизнь.


    Если же вам, как и автору исходной статьи приходит в голову мысль: "Но есть случаи, когда… в слушателе необходимо учитывать позицию элемента в списке, доступ к которой есть в методе onBindViewHolder() и отсутствует в onViewAttachedToWindow()", то гоните эту мысль прочь. Так делать нельзя. Даже если очень хочется. Как сказано в документации, этот метод не будет вызван, если изменилась только позиция элемента, но не его содержимое, поэтому мы рискуем получить в нашем слушателе неправильную позицию. Используйте getAdapterPosition.


    Условие № два


    RecyclerView – штука довольная сложная. Не надо усложнять себе жизнь еще больше. Если вы не занимаетесь оптимизацией производительности, вам не должно быть важно, попал этот элемент в общий пул или нет.


    Результат


    При соблюдении этих двух условий вам вряд ли понадобится изобретать велосипед в виде флага типа viewWasRecycled.


    Что происходит?


    Что вообще может происходить с элементом в RecyclerView? Во-первых, надо учитывать, что есть 2 "хранилища" – кэш и пул. В кэш элемент попадает, если он ушел за границы экрана, но в любое время может вернуться снова – при этом даже не произойдет перепривязки этого элемента (т.е. метод onBindViewHolder вызван не будет). Если кэш заполнился, или по какой-то другой причине RecyclerView решил, что этот элемент нам в ближайшее время не понадобится, он отправится в пул (вот здесь и будет вызван onViewRecycled). Восстановленный из пула элемент будет перепривязан (потому что скорее всего его позиция изменилась), и мы получим вызов onBindViewHolder. А вот если элемент ушел и из пула, тогда новый элемент пройдет через весь цикл – onCreateViewHolder, onBindViewHolder, onViewAttachedToWindow.


    Итого, у нас есть 3 варианта развития событий:


    • элемента раньше не было: создаем, привязываем, присоединяем;
    • элемент был в пуле: привязываем, присоединяем;
    • элемент был в кэше: просто присоединяем.

    Где и как?


    Что и на каких этапах лучше делать с элементом?


    • onCreateViewHolder. Создаем, хм, ViewHolder. Не надо здесь привязывать listener, заполнять контентом и т.д. Достаточно просто определить, какой тип нам нужен и создать его.
    • onBindViewHolder. Выполняем фактическую привязку контента – текст, картинки. Параметр position можно использовать только в самом методе – не сохраняем его, не отправляем в замыкания, помним, что этот метод будет вызван еще раз в том случае, если изменились данные (и не будет, если поменялось только положение). Listener я бы тоже не стал здесь навешивать – из соображений симметрии.
    • onViewAttachedToWindow. Элемент будет сейчас виден на экране, пользователь сможет с ним взаимодействовать – хороший момент, чтобы присоединить listener. Помним, что если внутри него нам нужна позиция элемента, то получаем ее через getAdapterPosition.
    • onViewDetachedFromWindow. Элемент не будет отображаться на экране. Пользователь не сможет с ним взаимодействовать. Убираем listener.
    • onViewRecycled. Элемент отправляется в ад пул. Здесь можно освободить тяжелые ресурсы (закэшированные картинки, например). Если элемент и будет переиспользован – то, скорее всего, для другой позиции.

    Вот вроде бы и всё. Надеюсь, ничего не забыл и не перепутал, но если что – тыкайте носом, не стесняйтесь.

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 8

      +1
      спасибо за инфу!
      очепятка в «Где и как?»
      в списке два раза упомянут «onViewAttachedToWindow» вместо видимо «onViewDetachedFromWindow»
        0

        Спасибо, исправил!

        0
        Замечательная статья! Лучше, чем у меня получилось ))) Однако именно о порядке обращения RecyclerView к методам обратного вызова в документации на developer.android.com я ничего не нашёл (хотя не факт, что там этого нет, а если есть, то, пожалуйста, потыкайте, кто знает, носом). И именно это толкнуло на эксперименты с RecyclerView.
        По вопросу размещения слушателей: в качестве довода «соображения симметрии» не кажутся мне убедительными, ведь располагая их «на экваторе» процессов, а именно, в методе onBindViewHolder() с применением флагов и их анализа, мы ничего не теряем, кроме умозрительной симметрии.
          0

          Спасибо!


          Кроме симметрии, есть 3 причины не использовать флаги:


          1. Введение флагов – дополнительный код. Дополнительный код – усложнение решения.
          2. Вы привязываете слушателя, т.е. навешиваете логику взаимодействия с элементом на основании того, что элемент был или не был отправлен в кэш (оптимизация производительности) – это разные абстракции, и их не стоить смешивать.
          3. onRecycledView теоретически может вообще не быть вызван. Сходу мне не удалось воспроизвести такую ситуацию, но в исходом коде есть такая функция:

          void addViewHolderToRecycledViewPool(@NonNull RecyclerView.ViewHolder holder, boolean dispatchRecycled)

          Если ее вызвать с параметром dispatchRecycled = false, то элемент отправится в пул, но уведомления об этом не будет.


          2 и 3 пункты, на мой взгляд, еще и отвечают на вопрос, почему в документации не расписан порядок вызова этих методов – onRecycledView не надо использовать для чего-то, кроме потенциальной очистки своего кэша.

            0
            Кстати! Давно мучает один вопрос: почему мало кто использует RecyclerView.OnItemTouchListener как замену View.OnClickListener? Как по мне, так для простых элементов RecyclerView (три-четыре действия) самое то. Плюс главное преимущество — OnItemTouchListener устанавливается на RecyclerView целиком, а не на каждый его элемент в отдельности (т.е. в onAttachedToRecyclerView/onDetachedFromRecyclerView, а не в onViewAttachedToWindow/onViewDetachedFromWindow).
              0

              С RecyclerView.OnItemTouchListener получается больше возни. Он не дает прямого доступа к самому элементу (или к его позиции), который был нажат, надо подключать GestureDetector. Он все-таки предназначен для обработки более сложных жестов. Для простых случаев быстрее и проще навесить OnClickListener на сам элемент. Плюс, в зависимости от типа элемента, реакция на нажатие может быть разная, и выносить эту логику наверх, дальше от самого элемента, может быть неудобно.

          0
          В чем минус привязывания listener в onCreateViewHolder()? Вызов данного метода происходит ограниченное количество раз, а доступ к позиции там можно получить при помощи getAdapterPosition().
            0

            По большому счету, зависит от того, надо ли вам убирать этот listener.


            Если вы просто устанавливаете OnClickListener, в котором дергаете какой-нибудь метод из активити, то я не вижу ничего плохо в привязывании в onCreateViewHolder.


            Если же вы подписываетесь на какое-то внешнее событие (например, реагируете на какое-то событие от активити), то от него надо будет где-то отписываться. Если подписка была произведена в onCreateViewHolder, то где от нее отписываться?

          Only users with full accounts can post comments. Log in, please.