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

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

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

Статический listener - Android

11.08.2016, 18:51. Просмотров 317. Ответов 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
Посмотрите здесь:

C++ Нужно создать статический массив строк как статический массив указателей на строки. Условние ниже -->
Как отменить Checked \ Selected CheckBox из самого события на Selected в Listener Android
Oracle Status : Failure -Test failed: Listener refused the connection with the following error: ORA-12505, TNS:listener does not currently know of SID given
Android Listener перемещения объекта
Android Listener и RSS
Android Как объявить статический класс?
RecyclerView создание Item Click Listener Android
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
YuraAAA
1564 / 1306 / 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)
Паблито
2012 / 1754 / 545
Регистрация: 12.05.2014
Сообщений: 6,208
Завершенные тесты: 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
1564 / 1306 / 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, то раньше такое случалось очень редко (на сотню фоток один раз).
Паблито
2012 / 1754 / 545
Регистрация: 12.05.2014
Сообщений: 6,208
Завершенные тесты: 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);
    }
Паблито
2012 / 1754 / 545
Регистрация: 12.05.2014
Сообщений: 6,208
Завершенные тесты: 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
319 / 279 / 55
Регистрация: 17.10.2014
Сообщений: 826
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
Android Статический импорт в Android
Android Listener в OnPostExecute ?
Android Работают ли Listener в абстрактной родительской Activity?
Listener SQLite Android

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

Или воспользуйтесь поиском по форуму:
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
Ответ Создать тему
Опции темы

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