Форум программистов, компьютерный форум CyberForum.ru

Программирование Android

Войти
Регистрация
Восстановить пароль
 
CoolMind
418 / 401 / 65
Регистрация: 06.10.2012
Сообщений: 1,723
#1

Статический listener - Программирование Android

11.08.2016, 18:51. Просмотров 368. Ответов 11
Метки нет (Все метки)

Всем привет!
Немного затупил. Есть некий ListView, в котором хранятся изображения. Изображения гружу при помощи Glide, в котором есть обработчик (listener). После загрузки я могу делать какие-то операции с изображением, допустим, уменьшать или делать кружочек, неважно. Т.е. по окончании загрузки каждого изображения должен выполниться listener:
Java
1
2
3
4
5
6
7
8
9
10
11
12
.listener(new RequestListener<String, GlideDrawable>() {
    @Override
    public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
        return false;
    }
 
    @Override
    public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
        // Какие-то действия.
        return false;
    }
})
Поскольку это список, то крутить его можно долго и упорно, при этом объём памяти всё время увеличивается. Я офигел, когда увидел 60 Мб. Пришлось ограничить кэш. Но он всё равно растёт, я пока не врубился почему.
В последнее время из-за такого поведения памяти в Java я стал сторонником Singleton'ов в противовес созданию и удалению объектов.
Вопрос такой. Можно ли один раз создать слушатель, который будет применяться ко всем изображениям? Правда, он зависит от переменных (как минимум, текущего изображения).
Просто удивляюсь, что везде пишут, например:
Java
1
button.setOnClickListener(new OnClickListener...).
Это же какое нерациональное использование памяти! Объекты постоянно появляются и исчезают, проще было бы создать один на время работы активности.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
11.08.2016, 18:51     Статический listener
Посмотрите здесь:

Listener SQLite - Программирование Android
Если какой нибудь стандпртный слушатель изменений в SQLite android. Ну например добавили или удалили что нибудь из базы данных и он в этот...

Listener в OnPostExecute ? - Программирование Android
Дратуте! Подскажите, пожалуйста, по такой ситуации: AsyncTask парсит данные и возвращает их в onPostExecute(). В ActionBar есть кнопка. Как...

Listener и RSS - Программирование Android
Здравствуйте! Начинаю изучать программирование на java под android. В качестве практики мой выбор пал на написание RSS ридера, который бы...

Взаимодействие ExecutorService и Listener - Программирование Android
Всем привет. Хотел спросить, кто как решает такой вопрос. Допустим есть регистрация, которая запускается в ExecutorService. Внутри ES...

Listener перемещения объекта - Программирование Android
Сколько дней рою интернет, никак не могу найти. Я при помощи ObjectAnimator перемещаю объект(ImageView) мне нужно отследить каждую...

Установить listener в потоке - Программирование Android
Нужно установить listener, но нет возможности его установить на Activity, можно ли как то обойти это. Есть возможность работы с Thread и...

SearchView ругается на Listener? - Программирование Android
Добрый день! подскажите что ему не нравится: @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate...

После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
YuraAAA
1566 / 1308 / 269
Регистрация: 25.10.2009
Сообщений: 3,424
Записей в блоге: 2
11.08.2016, 18:56     Статический listener #2
CoolMind, Хм, интересно. Но, думаю, дело не в лисенере, а в изображениях. Попробуйте посмотреть что занимает память (https://developer.android.com/studio...am-memory.html). Если дело в них, то не грузить их в память (не знаю как в Glide, в Picasso это
Java
1
2
.memoryPolicy(MemoryPolicy.NO_CACHE)
.networkPolicy(NetworkPolicy.NO_CACHE)
Pablito
2420 / 1865 / 583
Регистрация: 12.05.2014
Сообщений: 6,604
Завершенные тесты: 1
11.08.2016, 19:00     Статический listener #3
Цитата Сообщение от CoolMind Посмотреть сообщение
Есть некий ListView
А почему не RecyclerView ? Его специально и создавали в противовес листвью.
В ресайклере есть пул вьюшек, он все что выходит за пределы экрана - переиспользует, а лист - все держит в памяти.

Цитата Сообщение от CoolMind Посмотреть сообщение
Можно ли один раз создать слушатель, который будет применяться ко всем изображениям?
я бы сказал даже - нужно, а еще проще делать классу, в котором должен жить листенер implements OnClickListener
Цитата Сообщение от CoolMind Посмотреть сообщение
Просто удивляюсь, что везде пишут, например:
если это где-то в onCreate то разницы особой нет, тем более если кнопка одна
CoolMind
418 / 401 / 65
Регистрация: 06.10.2012
Сообщений: 1,723
11.08.2016, 20:17  [ТС]     Статический listener #4
YuraAAA, спасибо! Если честно, я как раз ожидал вашего ответа. Да, наблюдал графики в Monitors, нажимал на грузовичок "Initiate GC", зачастую объём сразу уменьшался почти до значений при запуске ListView. Но всё равно, по чуть-чуть этот минимум тоже растёт после прокруток списка. Я помню, что в Glide прописывал:
Java
1
2
3
Glide.with(this.appContext)
                .load(url)
                .skipMemoryCache(true)
И после этого каждый запуск GC сильно помогал. Т.е. видимо, изображения в кэше памяти достаточно сильно растут. Я в конфигурации прописал ограничение в 4 МБ, но, похоже, Glide всё равно как-то этот порог обходит.
Кстати, начинал я в этом проекте с Picasso, уж очень она хороша. Но потом решил пооптимизировать, перешёл на Glide и начал ловить грабли.
Ладно, если что, там ещё есть на SO и в GitHub у них отличная техподдержка. Если что буду ещё оптимизировать, отпишусь. Ну а так грабли с Glide тянут на отдельную статью.
Можно попробовать ещё через LeakCanary или HPROF, но последний - это мучение.

Добавлено через 4 минуты
Цитата Сообщение от Паблито Посмотреть сообщение
А почему не RecyclerView ? Его специально и создавали в противовес листвью.
Спасибо! Я просто мало работал с RecyclerView, но всё планировал именно на этом экране его использовать, т.к. там ещё и анимацию потом потребуется сделать, а она как раз с RV хорошо дружит.
Цитата Сообщение от Паблито Посмотреть сообщение
В ресайклере есть пул вьюшек, он все что выходит за пределы экрана - переиспользует, а лист - все держит в памяти.
Вот тоже всё время про это думаю, а как доказать - не знаю (из анекдота про ноль пять + ноль пять).
К тому же, на эмуляторе легко добиться того, чтобы выскочил OOM при прокрутке буквально десяти-двадцати изображений.
Цитата Сообщение от Паблито Посмотреть сообщение
я бы сказал даже - нужно, а еще проще делать классу, в котором должен жить листенер implements OnClickListener
Вот, я как раз подзанялся, но что-то сходу не получилось. Могу, конечно, написать singleton, но там ещё параметры есть (поскольку использую ViewHolder и текущий getItem(), то приходится вызывать слушатель с параметрами, а я пока что-то затупил).

Добавлено через 1 час 3 минуты
В теме http://stackoverflow.com/a/14238689/2914140 приведён пример некого очистителя неиспользуемых View в списке:
Java
1
2
3
4
5
6
7
8
mGridView.setRecyclerListener(new RecyclerListener() {
        @Override
        public void onMovedToScrapHeap(View view) {
            // Release strong reference when a view is recycled
            final ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
            imageView.setImageBitmap(null);
        }
    });
Забавно, но у меня он не только ничего не делает, но ещё и притормаживает работу. Думаю, правда, пора переходить на RecyclerView.
YuraAAA
1566 / 1308 / 269
Регистрация: 25.10.2009
Сообщений: 3,424
Записей в блоге: 2
11.08.2016, 21:13     Статический listener #5
CoolMind, переходите на RecyclerView и сделайте ещё кое что.
Когда view отцепляется от листа, лучше очистить память. Вот как:
Java
1
2
3
4
@Override
public void onViewDetachedFromWindow(YourViewHolder holder) {
    Glide.clear(holder.imageView);
}
Где YourViewHolder будет Ваш холдер, imageView, соответственно, вьюшка
CoolMind
418 / 401 / 65
Регистрация: 06.10.2012
Сообщений: 1,723
15.08.2016, 10:05  [ТС]     Статический listener #6
YuraAAA, впечатления.
1. Расход памяти на RecyclerView - обалденный. Если немного покрутить, то на ListView я получал 40-60 Мб, а на RecyclerView - 50-80 (можно и больше, и приложение не показывало OOM). Визуальной разницы я не заметил, т.е. применение RecyclerView только лишь как замена ListView ухудшает характеристики (но имеет ряд преимуществ, которые могут пригодиться в дальнейшем).
2. Применение onViewDetachedFromWindow с Glide.clear пока что дало такой результат. Действительно, запуск мусоровоза GC даёт сброс обратно в среднем к 45 Мб примерно (число всё время разное). Но есть побочный эффект. В некоторых местах появились белые прямоугольники вместо фоток. Решается долгой прокруткой вверх-вниз. Если не использовать Glide.clear, то раньше такое случалось очень редко (на сотню фоток один раз).
Pablito
2420 / 1865 / 583
Регистрация: 12.05.2014
Сообщений: 6,604
Завершенные тесты: 1
15.08.2016, 10:44     Статический listener #7
код ресайклера с холдером в студию!
CoolMind
418 / 401 / 65
Регистрация: 06.10.2012
Сообщений: 1,723
15.08.2016, 11:24  [ТС]     Статический listener #8
Забыл добавить, в статье https://habrahabr.ru/post/258195/ рекомендуют использовать WeakReference, правда, там приложение к своему случаю + использование стороннего кода. И ListView, и RecyclerView, как следует из многочисленных статей, не хранят ссылки на элементы, ушедшие с экрана. Т.е. проблема, вероятно, в Glide. Код могу привести, только вряд ли это поможет.

Добавлено через 16 минут
Удалил лишнее (проверки, TextView), вот что осталось. Часть методов также опустил, они не должны влиять на увеличение памяти.
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        final ViewHolder viewHolder = (ViewHolder) holder;
        final Item item = this.items.get(position);
 
            // Убираем скачки при прокрутке, для этого устанавливаем высоту ImageView.
            if (item.photoHeight > 0) {
                viewHolder.photo.getLayoutParams().height = item.photoHeight;
                viewHolder.photo.requestLayout();
            }
 
            if (viewHolder.photoUrl == null
                    || !viewHolder.photoUrl.equals(item.photoUrl) && !viewHolder.photoUrl.equals(item.thumbUrl)
                    || viewHolder.photo.getDrawable() == null) {
 
                downloadPhoto(item, viewHolder);
            }
 
            viewHolder.number.setText(item.number);
    }
 
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(resource, parent, false);
        return new ViewHolder(view);
    }
 
//    @Override
//    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
//        super.onViewDetachedFromWindow(holder);
//        ViewHolder viewHolder = (ViewHolder) holder;
//        Glide.clear(viewHolder.photo);
//    }
 
    /**
     * Загрузить фотографию.
     */
    private Target<GlideDrawable> downloadPhoto(final Item item, final ViewHolder viewHolder) {
        return Glide.with(context)
                .load(item.photoUrl)
                .crossFade(0)
                .thumbnail(getThumbnailRequest(item, viewHolder))
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
//                        .skipMemoryCache(true)
                .placeholder(R.drawable.placeholder)
                .fallback(R.drawable.placeholder)
                .error(R.drawable.placeholder)
                .listener(new RequestListener<String, GlideDrawable>() {
                    @Override
                    public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
                        return false;
                    }
 
                    @Override
                    public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                        viewHolder.photoUrl = item.photoUrl;
                        item.photoHeight = getImageViewHeight(resource, viewHolder.photo);
                        return false;
                    }
                })
                .into(viewHolder.photo);
    }
Разметку не даю, думаю, пока нет необходимости. Там FrameLayout, в котором есть ProgressBar, LinearLayout с фотографией, текстом.
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    public static class Item {
        public String number;
        public String photoUrl; // Путь к фотографии.
        public String thumbUrl; // Путь к уменьшенному изображению.
        public int photoHeight;
 
        public Item() {
        }
 
        public Item(String number,String photoUrl, String thumbUrl) {
            this.number = number;
            this.photoUrl = photoUrl;
            this.thumbUrl = thumbUrl;
            this.photoHeight = 0;
        }
    }
 
    private static class ViewHolder extends RecyclerView.ViewHolder {
        protected final ImageView photo;
        protected final TextView number;
        protected String photoUrl;
 
        public ViewHolder(View view) {
            super(view);
            this.photo = (ImageView) view.findViewById(R.id.photo);
            this.number = (TextView) view.findViewById(R.id.number);
        }
    }
Добавлено через 4 минуты
Конструктор адаптера тривиален, но приведу, на всякий случай.
Java
1
2
3
4
5
6
7
8
9
10
11
12
    private final Context context;
    private final int resource;
    private final List<Item> items;
    private final LayoutInflater inflater;
 
    public SomeAdapter(Context context, int resource, List<Item> items) {
        super();
        this.context = context;
        this.resource = resource;
        this.items = items;
        this.inflater = LayoutInflater.from(context);
    }
Pablito
2420 / 1865 / 583
Регистрация: 12.05.2014
Сообщений: 6,604
Завершенные тесты: 1
15.08.2016, 13:02     Статический listener #9
бегло посмотрел, кажись все в порядке
смущает только этот кусок
Java
1
2
3
4
5
6
 if (viewHolder.photoUrl == null
                    || !viewHolder.photoUrl.equals(item.photoUrl) && !viewHolder.photoUrl.equals(item.thumbUrl)
                    || viewHolder.photo.getDrawable() == null) {
 
                downloadPhoto(item, viewHolder);
            }
в onBindViewHolder может запросто прилететь вьюшка, которая переиспользуется и там уже может быть картинка, я бы вообще убрал эти проверки
CoolMind
418 / 401 / 65
Регистрация: 06.10.2012
Сообщений: 1,723
15.08.2016, 13:17  [ТС]     Статический listener #10
Паблито, спасибо.
Да, я тоже общался с техподдержкой Glide по этому поводу, он тоже малость удивился, зачем этот кусок, сказал, что не надо. Я тогда ещё только начинал работать с Glide и вставил, потому что в разметке фрагмента над RecyclerView есть шапка. Программно она не привязана к RecyclerView. Эта шапка анимируется, т.е. например, если крутить список вверх-вниз, то шапка может уменьшаться или увеличиваться. Поскольку она может становиться больше-меньше, то и сам RecyclerView может то уменьшаться, то увеличиваться. В результате получается, что при прокрутке элементы начнут чуть сдвигаться, это вызовет "дрожание", а фотки начнут постоянно перерисовываться. Поэтому я убрал перерисовку фотографий, если элемент существует, но меняет положение.
Цитата Сообщение от Паблито Посмотреть сообщение
в onBindViewHolder может запросто прилететь вьюшка, которая переиспользуется и там уже может быть картинка
Хороший совет, там действительно всё не так просто, как в ListView, надо бы поизучать.
androbro
323 / 283 / 59
Регистрация: 17.10.2014
Сообщений: 836
15.08.2016, 13:50     Статический listener #11
Цитата Сообщение от CoolMind Посмотреть сообщение
там действительно всё не так просто, как в ListView
а в ListView по вашему не может прилететь вьюшка которая переиспользуется?
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
20.08.2016, 00:03     Статический listener
Еще ссылки по теме:

EditText.Listener внутри ListView - Программирование Android
Добрый день. Помогите пожалуйста с задачкой. У меня есть ListView, который пополняется записями типа(TextView/EditText), количество...

RecyclerView создание Item Click Listener - Программирование Android
Здравствуйте. Недавно начал изучать Android. Дошел до изучения RecyclerView. Научился создавать RecyclerView и отображать там данные из БД....

Работают ли Listener в абстрактной родительской Activity? - Программирование Android
Ситуация следующая. Я захотел сделать NavigationView с DrawerLayout вшитыми в Activity, которое я сделал abstract, оно реализует интерфейс...

Как отменить Checked \ Selected CheckBox из самого события на Selected в Listener - Программирование Android
В самом событии - Листенере надо отменить установку галки. Чтобы не зациклилось надо установить без вызова опять этого листенера. Как? ...

Статический импорт в Android - Программирование Android
Когда я пишу, например, import static java.lang.Math.abs - все работает и все хорошо. А если я хочу импортнуть все статические методы, я...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
CoolMind
418 / 401 / 65
Регистрация: 06.10.2012
Сообщений: 1,723
20.08.2016, 00:03  [ТС]     Статический listener #12
Цитата Сообщение от Паблито Посмотреть сообщение
в onBindViewHolder может запросто прилететь вьюшка, которая переиспользуется и там уже может быть картинка, я бы вообще убрал эти проверки
Докладываю. Решил убрать этот кусок кода, и надо же, практически никаких визуальных отличий. Больше не дёргается ничего. Не знаю, решилось ли это из-за RecyclerView или я поборол Glide. Распределение памяти выглядит чуть получше. По крайней мере, график более пологий, рост до 100 Мб достигается не за несколько десятков секунд.
Цитата Сообщение от androbro Посмотреть сообщение
а в ListView по вашему не может прилететь вьюшка которая переиспользуется?
Может быть)

Glide меня немного достала. Давно с библиотеками не ловил столько глюкобагов. То placeholder начинает масштабировать изображения, то далеко не с первой попытки напишешь скругление углов.
Хотел, опять же, с утечкой памяти поразбираться, нашёл тему: https://github.com/bumptech/glide/wiki/Custom-targets
Пишу:
Java
1
2
3
4
5
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
    super.onViewRecycled(holder);
    Glide.clear(((ViewHolder) holder).photo);
}
Ага, распределение памяти не поменялось, зато подмигивания изображений появились. В общем, минное поле.
Если кому надо, как-нибудь опишу эту библиотеку в трёх словах (матерных).
Yandex
Объявления
20.08.2016, 00:03     Статический listener
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru