Что такое трассировка opengl. Аппаратное ускорение(Hardware Acceleration). Анализ памяти(Memory Profiling)

Интересны Предупреждения(Alerts) и Кадры(Frames), показывающие подсказки сгенерированные из собранных данных. Давайте взглянем на мою трассировку и выберем одно из предупреждений сверху:

Предупреждение говорит, что был долгий вызов View#draw(). Нам предоставляют описание, ссылки на документацию и даже видео ссылки по соответствующим вопросам.

Смотря на колонку Кадров(Frames) мы видим ниже обозначение каждого отрисованного кадра, которое окрашено зеленым, желтым и красным для обозначения проблем производительности вовремя отрисовки данного кадра. Давайте выберем один из красных кадров.

Внизу мы видим все предупреждения для этого кадра. Их было 3 и одно из них мы видели ранее. Давайте увеличим этот кадр и раскроем предупреждение внизу: Наполнение вовремя рециркуляции ListView (“Inflation during ListView recycling”).

Мы видим, что эта часть составила 32 миллисекунды, а это выходит за границу 16 миллисекунд- требование для достижения 60 кадров в секунду. Там есть больше временной информации для каждого элемента в ListView в этом кадре- около 6 миллисекунд потрачено на каждый из 5 элементов. Описание помогает нам понять проблему и даже предоставляет решение. На графике вверху мы видим всю визуализацию и даже можем увеличить кусочек наполнения макета для просмотра вьюх, которые заняли много времени при наполнении макета.

Другой пример медленно отрисовывающегося кадра:

После выбора кадра мы можем нажать клавишу m чтобы его подсветить и посмотреть сколько времени заняла эта часть. Посмотрев наверх, мы видим, что отрисовка этого кадра заняла 19 миллисекунд. Развернув предупреждение, увидим, что была задержка расписания (“Scheduling delay”).

Задержка расписания обозначает то, что поток, обрабатывающий эту часть, не становился в очередь процессора длительное время. Следовательно, для завершения работы этого потока понадобилось больше времени. Выбор более длинного отрезка в этом кадре показывает более конкретную информацию:

Настенное время(Wall duration) это время, прошедшее с начала и до конца данной части кадра. Оно так называется потому, что это как смотреть на настенные часы сначала старта потока.

Время процессора это время потраченное процессором на обработку данной части.

Заметна большая разница между настенным временем и временем процессора. Первое заняло 18 миллисекунд, а второе 4 миллисекунды. Что немного странно, так что сейчас подходящее время посмотреть, чем занимался процессор все это время:

Все четыре ядра были достаточно заняты. Выбор одного из потоков показывает, что причина приложение com.udinic.keepbusyapp. В этом случае другое приложение нагружало процессор, отказывая нашему приложению в выделении времени. Этот сценарий обычно временный, так как другие приложения в фоне не часто забирают себе работу процессора, этими потоками может стать другой процесс в вашем приложении или даже ваш главный поток. Так как Системная трассировка(Systrace) инструмент обзора есть предел того, как далеко мы можем зайти. Чтобы найти причину того, что нагружает процессор в нашем приложении мы будем использовать другой инструмент Просмотр Трассировки (Traceview).

Просмотр Трассировки (Traceview)

Просмотр Трассировки инструмент анализа, показывающий время работы каждого метода. Посмотрим, как выглядит файл трассировки:

Посмотрим на различные столбцы:

Имя(Name) - имя и цвет метода на графике.
Включительное время процессора(Inclusive CPU Time) - время обработки процессором данного метода и его детей(всех методов которые он вызывает)
Исключительное время процессора(Exclusive CPU Time) - время обработки процессором данного метода исключая методы вызываемые внутри него.
Включительное/исключительное реальное время(Inclusive / Exclusive Real Time) - время с начала и до конца работы метода. Тоже что и настенное время в Системной трассировке(Systrace).
Вызовы + Рекурсия(Calls+Recursion) - сколько раз был вызван метод и также количество рекурсивных вызовов.
Процессорное/Реальное время на вызов(CPU/Real time per Call) - среднее Процессорное/Реальное время, ушедшее на вызов метода. Другие поля со временем показывают суммарное время всех вызовов метода.
Я открыл приложение, в котором прокрутка работала не плавно и долго. Я запустил трассировку, прокрутил немного и остановил трассировку. Я нашел метод getView() и раскрыл его, вот что я увидел:

Этот метод был вызван 12 раз, процессор потратил около 3мс на каждый вызов, но реальное время для завершения каждого вызова составило 162мс! Конечно это проблема…

Смотря на детей этого метода, мы видим распределение времени между различными методами. Thread.join() занял 98% включительного времени. Мы используем этот метод когда нужно дождаться завершения другого потока. Один из других детей Thread.start(), что наталкивает на мысль что метод getView() запускает поток и ждет его окончания.

Но где этот поток?
Мы не можем увидеть что делал этот поток как дочерний от getView(), так как getView() не делает эту работу напрямую. Чтоб найти это, я искал метод Thread.run(), который вызывается при старте нового потока. Я последовал дальше пока не нашел виновника:

Я выяснил, что время вызова метода BgService.doWork() заняло ~14мс, а у нас их 40! Есть шанс, что каждый getView() вызывает BgService.doWork() больше чем 1 раз и объясняет, почему каждый вызов getView() занимает много времени. Этот метод загружает процессор длительное время. Посмотрев на Исключительное время процессора, мы видим, что он использовал 80% всего времени в трассировке. Сортировка по исключительному времени процессора также хороший путь для поиска загруженных методов в трассировке. Возможно, они причастны к вашей проблеме производительности.

Следующие критические методы, такие как getView(), View#onDraw() и другие, помогают нам найти причины почему наше приложение медленно работает. Но иногда, кое-что другое загружает процессор, забирая ценные циклы процессора, которые могли бы быть потрачены на более плавную отрисовку нашего интерфейса. Сборщик мусора запускается произвольно, очищая неиспользуемые объекты и обычно не влияет на приложение, работающее на переднем плане. Если сборщик мусора запускается слишком часто, это может вызвать тормоза в приложении и возможно в этом наша вина.

Анализ памяти(Memory Profiling)

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

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

В левой части графика доступно два инструмента Дамп Кучи(Heap dump) и Трекер выделения памяти(Allocation Tracker).

Дамп кучи(Heap dump)

Для того чтобы рассмотреть что сейчас находиться в куче, мы можем использовать кнопку дамп кучи. В результате сделается снимок того что сейчас находиться в куче, и покажет это в специальном экране отчета в Андроид Студии:

Слева мы видим гистограмму экземпляров в куче, сгруппированных по имени класса. Для каждого экземпляра есть количество объектов в памяти, размер этих экземпляров (Малый размер(Shallow size)) и размер этих объектов хранящихся в памяти. Последнее говорит нам, сколько памяти может освободиться, если эти экземпляры будут освобождены. Это представление показывает объем памяти нашего приложения, помогая нам определить большие структуры данных и связи объектов. Эта информация может помочь нам создавать более эффективные структуры данных, освобождая связи объектов для уменьшения выделенной памяти и в конце концов - уменьшая объем памяти насколько возможно.

Смотря на гистограмму мы видим, что MemoryActivity имеет 39 экземпляров, что странно для активности. Выбрав один из экземпляров справа, увидим все ссылки на этот экземпляр в Дереве ссылок внизу.

Одна из них часть массива объекта ListenersManager. Смотря на другие экземпляры активности, видим, что все они удерживаются этим объектом. Это объясняет почему единственный объект этого класса занимает так много памяти.

Такие ситуации общепринято называются Утечка Памяти, так как активности были чисто уничтожены и эта неиспользуемая память не может быть убрана сборщиком мусора из за этой ссылки. Мы можем избегать подобных ситуаций, удостоверяясь что на наши объекты не ссылаются другие объекты, которые существуют больше времени. В данной ситуации ListenersManager не должен хранить эту ссылку после того как активность была уничтожена. Решение будет удалить эту ссылку, перед тем как активность будет уничтожена, в методе обратного вызова onDestory().

Утечки памяти и другие большие объекты занимают много места в куче, уменьшая доступную память, в результате сборщик мусора запустит много событий для попытки освобождения памяти. Эти события сборщика мусора занимают процессор, что приводит к ухудшению производительности вашего приложения. Если количество доступной памяти не достаточно для приложения и куча не может больше вырасти, будет более драматический результат - OutOfMemoryException, приводящий падению вашего приложения.

Более продвинутый инструмент Анализатор Памяти Eclipse(Eclipse Memory Analyzer Tool - Eclipse MAT):

Этот инструмент может делать тоже, что и Андроид Студия, а также определять потенциальные утечки памяти и предоставлять улучшенный поиск экземпляров, как например поиск всех Растровых изображений объёмом более 2Мб, или всех пустых объектов Rect .

Другой отличный инструмент библиотека LeakCanary , которая следит, чтобы у ваших объектов не было утечек. Вы получите напоминание о том, что произошло и где.

Трекер выделения памяти (Allocation Tracker)

Трекер выделения памяти запускается/останавливается с помощью других кнопок слева от графика памяти. Он сгенерирует отчет всех экземпляров расположенных в памяти в этот промежуток времени, сгруппированных по классу.

Или по методу:

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

Общие подсказки по памяти

Вот несколько быстрых подсказок/руководств, которые я использую, когда пишу код:

Перечисления(Enums) уже горячая тема для обсуждения производительности. Есть Видео об этом, показывающее размер занимаемый перечислениями, и обсуждение этого видео и некоторая информация в нем, вводящая в заблуждение. Занимают ли перечисления больше памяти чем обычные константы? Конечно. Это плохо? Не обязательно. Если вы пишите библиотеку и необходима строгая безопасность типов, это может оправдать использование перечислений вместо других решений, таких как @IntDef . Если у вас просто куча констант, которые могут быть сгруппированы - то может быть не разумно использовать для этого перечисления. Как всегда есть компромисс, который нужно принимать во внимание, когда вы принимаете решение.

Автоупаковка(Auto-boxing) - автоупаковка это автоматическое преобразование из примитивных типов в их объектное представление (например, int -> Integer). Каждый раз, когда примитивный тип «упаковывается» в объектное представление, создается новый объект(шок, я знаю). Если у нас их много - сборщик мусора запускается чаще. Количество происходящих автоупаковок легко пропустить, потому, что для нас это происходит автоматически при присваивании объекту примитивного типа. Решение- старайтесь быть последовательными с этими типами. Если вы повсеместно используете примитивные типы в вашем приложении, постарайтесь избегать их беспричинной автоупаковки. Вы можете использовать инструменты анализа памяти, чтоб найти много объектов представляющих примитивные типы. Также вы можете использовать Просмотр Трассировки и искать Integer.valueOf(), Long.valueOf() и т.д.

HashMap против ArrayMap / Sparse*Array - также как и проблема автоупаковки, использование HashMap требует от нас использовать объекты в качестве ключей. Если мы используем примитивный тип “int” в приложении и он автоупаковыветься в Integer при взаимодействии с HashMap, возможно мы можем просто использовать SparseIntArray. В случае, если нам все же нужны ключи в виде объектов, мы можем использовать класс ArrayMap. Он похож на HashMap, но внутри работает по-другому , более эффективно используя память, но уменьшая скорость работы. Эти два варианта имеют меньший объем памяти, чем HashMap, но для получения элементов или выделения памяти нужно больше времени, чем HashMap. Если у вас меньше 1000 элементов, то при выполнении программы разница не заметна, что делает их не плохим вариантом для ваших нужд использования пар ключ/значение.
Осознание Контекста - как показано ранее, легко создать утечку памяти в активностях. Возможно, вас не удивит что активности наиболее общий случай утечек в Андроид (!). Также эти утечки очень дороги, так как они содержат всю иерархию пользовательского интерфейса, что само по себе может занимать много места. Множество операций на платформе требуют объект Context, и вы обычно посылаете Активность. Убедитесь, что вы понимаете, что происходит с этой Активностью. Если ссылка на нее кэшируется, и этот объект живет дольше самой Активности, без удаления этой ссылки, то у вас появляется утечка памяти.

Избегайте не статических внутренних классов , их инициализация создает неявную ссылку не внешний класс. Если экземпляр объекта внутреннего класса необходим больше времени, чем внешний, то внешний класс останется в памяти, даже если он больше не нужен. Например, создавая не статический класс в классе Активности, наследуемый от AsyncTask, затем запустить AsyncTask, и во время его работы убить активность. Этот AsyncTask будет держать эту активность, пока не закончит свою работу. Решение- не делайте так, объявляйте статический внутренний класс если это необходимо.

Анализ графического процессора(GPU Profiling)

Новое дополнение к Андроид Студии 1.4, инструмент для анализа отрисовки графического процессора.

В окне Андроид, перейдите к вкладе GPU, и вы увидите график показывающий время отрисовки каждого кадра на вашем экране:

Каждая полоска на графике представляет один отрисованный кадр, и цвета представляют различные фазы в процессе отрисовки:

Рисование(синий) - представляет метод View#onDraw(). Эта часть создает/обновляет объекты DisplayList (В Википедии display list (или display file) это последовательность графических команд, которые определяют выходное изображение, преобразовываемые позже в команды OpenGL, которые понимает графический процессор. Большие значения могут быть из-за сложных вьюх, которые требуют больше времени для создания их display lists, или если много вьюх аннулируются в небольшой промежуток времени.

Prepare (purple) - In Lollipop, another thread was added to help the UI Thread render the UI faster. Подготовка (фиолетовый) - в Lollipop добавлен еще один поток, чтоб помочь потоку интерфейса отрисовать интерфейс быстрее. Этот поток называется RenderThread. Он отвечает за преобразование display lists в команды OpenGL и отсылку их в графический процессор. В это время поток UI может продолжить обрабатывать следующий кадр. На этом шаге показывается время за котjрое поток UI передает все ресурсы в поток RenderThread. Если у нас много ресурсов для передачи, например много display lists или они большие, то этот шаг может занять больше времени.

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

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

В Marshmallow, было добавлено больше цветов для обозначения новых шагов, таких как Замер/Макет, обработка ввода и другие:

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

Это позволит инструменту использовать команды ADB для получения необходимой информации, таким образом, мы используем:

Adb shell dumpsys gfxinfo Мы можем получить все эти данными и сами сделать график. Команда показывает другую полезную информацию, например количество вьюх в иерархии, размеры всех display lists, и другое. В Marshmallow, мы можем увидеть еще больше статистики.

Если у нас есть автоматическое тестирование интерфейса, мы можем сделать так, чтоб наш билд сервер запускал эту команду после определенных взаимодействий (прокрутка списка, тяжелые анимации и т.д.) и увидеть есть ли изменения в значениях с течением времени, например избыточные кадры. Это может помочь определить проблемы производительности после пуша некоторых коммитов, давая нам время определить проблему перед тем как приложение уйдет в производство. Мы даже можем получить более точную информацию отрисовки, используя ключевое слово “framestats”, как объяснено.

Но это не единственный способ увидеть этот график!

Как вы видели в опциях разработчика в разделе “Profile GPU Rendering”, есть вариант просмотра графика как «Полоски на экране». Его включение, покажет график для каждого окна на нашем экране, вместе с зеленой полосой для определения порога 16 мс.

На примере справа мы видим некоторые кадры пересекли зеленую линию, это означает, что для их отрисовки потребовалось более 16мс. Так на этих полосках преобладает синий цвет, мы понимаем, что для отрисовки либо было много вьюх либо они были сложные, либо и то и другое. В этом сценарии, я прокрутил список новостной ленты, который поддерживает различные типы вьюх. Некоторые вьюхи были аннулированы, а некоторые сложнее отрисовывать чем другие. Возможная причина того, что некоторые кадры пересекают этот порог то, что есть сложная вьюха для отрисовки в данный момент.

Обозреватель иерархии(Hierarchy Viewer)

Я люблю этот инструмент, и меня печалит, то что многие его вообще не используют!

Используя Обозреватель иерархии, мы можем получить статистику производительности, видеть на экране полную иерархию вьюх и иметь доступ ко всем свойствам вьюх. Вы также можете получить дамп данных темы, увидеть все значения, использованные для каждого атрибута стиля, но это доступно только при запуске Обозревателя иерархии как отдельного приложения, не из Андроид Монитора. Я использую этот инструмент при дизайне моих макетов и когда я хочу их оптимизировать.

В центре мы видим дерево представляющие иерархию вьюх. Иерархия вьюх может быть широкой, но если она слишком глубока(около 10 уровней), ценой могут быть дорогостоящие фазы макета/замера. Каждый раз, когда вьюха замеряется, в View#onMeasure() или когда она располагает все дочерние вьюхи, в View#onLayout(), эти команды распространяются на дочерние вьюхи, которые повторяют те же действия. Некоторые макеты повторяют каждый шаг дважды, например RelativeLayout и некоторые конфигурации LinearLayout, и если они вложенные - количество проходов возрастает по экспоненте.

Внизу справа мы видим «чертеж» нашего макета, отмечающий расположение каждой вьюхи. Мы можем выделить вьюху здесь или в дереве и увидим все её свойства слева.

Когда я создаю макет, иногда я не уверен, почему определенная вьюха закончилась там где она сейчас. Используя этот инструмент, я могу отследить её в дереве, выбрать ее и увидеть где она в окне предосмотра.. Смотря на конечные замеры вьюх на экране, я могу создавать интересные анимации, и использовать эту информацию чтобы аккуратно передвигать вещи. Я могу найти потерянные вьюхи, на которые нечаянно наложились другие вьюхи, и многое другое.

Для каждой вьюхи у нас есть время ее измерения/макета/отрисовки и всех ее дочерних вьюх. Цвета указывают, как представлена данная вьюха в сравнении с другими вьюхами в дереве, это хороший способ найти слабейшую ссылку. Так как мы также видим предосмотр этой вьюхи мы можем обойти дерево по шагам ее создания, находя избыточные шаги, которые мы можем убрать. Одна из таких вещей, которые имеют большое влияние на производительность, называется Наложение(Overdraw).

Наложение(Overdraw)

Как вы видели в разделе Анализ графического процессора(GPU Profiling) - фаза Исполнение, представленная желтым цветом на графике, для завершения может занять больше времени если графическому процессору нужно рисовать много объектов на экране, увеличивая время отрисовки каждого кадра. Наложение происходит, когда мы рисуем одно поверх другого, например желтую кнопку на красном фоне. Графический процессор сначала должен нарисовать красный фон и затем желтую кнопку сверху, делая наложение неизбежным. Если у нас много слоев наложения, это приводит к тому что графический процессор работает больше и будет дальше от цели 16 мс.

Используя пункт «Отладка Наложения GPU» в опциях Разработчика, все наложения будут окрашены для обозначения сложности наложения в этой области. Наложение 1x/2x это нормально, даже некоторые светло красные области это неплохо, но если мы видим слишком много красного на экране, это может быть проблемой. Давайте рассмотрим несколько примеров:

На примере слева список нарисован зеленым, что обычно хорошо, но сверху есть наложение, которое делает его красным и это уже проблема. На примере справа весь список в красном цвете. В обоих случаях есть непрозрачный список с наложением 2x/3x. Эти наложения могут быть в случае, если есть полноэкранный цвет фона в окне содержащем вашу Активность/Фрагмент, список и и каждый элемент списка. Такую проблему можно решить установкой цвета фона только для одного из них.

Примечание: тема по умолчанию объявляет полно экранный фоновый цвет для вашего окна. Если у вашей активности непрозрачный макет, который занимает весь экран, вы можете убрать фон окна, чтобы убрать один слой наложения. Это можно сделать в теме или в коде, вызвав getWindow().setBackgroundDrawable(null) в onCreate().

Используя Обозреватель иерархии, вы может сделать экспорт всех слоев иерархии в PSD файл и открыть его в Photoshop. Рассмотрение различных слоев в Photoshop, раскроет все наложения в макете. Используйте эту информацию для удаления избыточных наложений, и не довольствуйтесь зеленым, добивайтесь синего!

Альфа(Alpha)

Использование прозрачности может повлиять на производительность, чтоб понять почему- посмотрим что происходит при установке значения альфа вьюхе. Рассмотрим следующий макет:

Мы видим макет, содержащий 3 ImageViews, наложенных друг на друга. В прямой/простой реализации, установка альфа, используя setAlpha() команда передается всем дочерним вьюхам, ImageViews в данном случае. Затем, эти ImageViews будут нарисованы с этим значением альфа в буфере кадра. Результат:

Мы не это хотели видеть.

Та как ImageView отрисован с значением альфа, все наложенные изображения смешались. К счастью у операционной системы есть решение этой проблемы. Макет будет скопирован в внеэкранный буфер, альфа будет применена ко всему этому буферу и результат будет скопирован в буфер кадра. Результат:

Но мы заплатили за это цену.

Рисование вьюхи в внеэкранном буфере, перед рисованием ее в буфере кадра, это виртуальное добавление еще одного неопределенного слоя наложения. Операционная система не знает точно когда применять этот подход или прямой подход показанный ранее, таким образом, по умолчанию делает более сложный. Но есть еще способы установить альфа и избежать сложность, добавляемую вне экранным буфером:

  • TextViews - Используйте setTextColor() вместо setAlpha(). Использование альфа канала для цвета текста, приводит к прямому рисованию текста с использованием этого канала.
  • ImageView - Используйте setImageAlpha() вместо setAlpha(). Та же причина, что для TextView.
  • Настраиваемые вьюхи - если настраиваемая вьюха не поддерживает наложенные вюхи, то это сложное поведение нам не подходит. Нет возможности для наших дочерних вьюх смешаться, как показано в примере выше. Переопределяя метод hasOverlappingRendering() вернуть false, мы даем сигнал операционной системе выбрать прямой/простой путь с нашей вьюхой. Таже есть возможность вручную обрабатывать происходящее при установке альфа, переопределяя метод onSetAlpha() возращать true.

Аппаратное ускорение(Hardware Acceleration)

Когда Аппаратное ускорение было представлено в Honeycomb, у нас появилась новая модель рисования для отрисовки нашего приложения на экране. Оно представило структуры DisplayList, которые записывают команды рисования вьюхи для быстрой отрисовки. Но есть еще одна супер особенность, которую разработчики иногда упускают или не используют правильно - слои Вьюхи.

Используя слой вьюхи, мы можем нарисовать вьюхи в внеэкранном буфере(как вы видели ранее, применяя Альфа канал) и обработать как нам необходимо. Эта особенность, в основном, хороша для анимаций, так как мы можем анимировать сложные Вьюхи быстрее. Без слоев анимации вьюхи аннулирует её после изменения анимированного свойства(например, х координаты, масштаб, значение альфа и др.). Для сложных вьюх это аннулирование передается на все дочерние вьюхи, и они затем перерисуют себя, сделав дорогостоящую операцию. Использую слой вьюхи, обеспечиваемый Аппаратными средствами, текстура для нашей вьюхи создается в графическом процессоре. Есть несколько операций, которые мы можем применить к этой текстуре без ее аннулирования, такие как позиция x/y, вращение, альфа и другие. Все это означает, что мы можем анимировать сложную вьюху вообще без ее аннулирования во время анимации! Это делает анимацию более сглаженной. Вот пример кода, как это сделать:
// Используя Object animator view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); objectAnimator.start(); // Используя аниматор Свойства(Property animator) view.animate().translationX(20f).withLayer().start();
Правда, просто?

Да, но нужно помнить несколько вещей при использовании аппаратных слоев:

  • Подчищайте за своей вьюхой - аппаратные слои потребляют место на вашем графическом процессоре, компоненте с ограниченной памятью. Пробуйте и используйте их только в то время, когда они необходимы, например в анимации, и потом очищайте их. В примере с ObjectAnimator выше я применил слушатель для удаления слоя после окончания анимации. В примере аниматора Свойства, я использовал метод withLayers(), который автоматически создает слой вначале и удаляет его в конце анимации.
  • Если вы измените свою вьюху после применения аппаратного слоя, это аннулирует аппаратный слой и отрисует всю вьюху заново в вне экранном буфере. Это произойдет после изменения свойства которое не оптимизировано для аппаратных слоев(сейчас оптимизированы следующие: вращение, масштабирование, x/y, перемещение, точка вращения и альфа. Например, если вы анимируете вьюху с поддержкой аппартного слоя, изменяя цвет фона во время её движения по экрану, приведет к постоянным обновлениям аппаратного слоя. Обновление аппаратного слоя имеет накладные расходы, из за которых возможно его не стоит использовать.

Для второй проблемы, есть способ отобразить эти обновления аппаратного слоя. Используя опции Разработчика, мы можем включить «Показывать обновления аппаратного слоя».

Когда это включено, Вьюха светится зеленым цветом во время обновления аппаратного слоя. Один раз я это использовал, когда мой ViewPager не прокручивался так плавно, как я ожидал. После включения этой опции разработчика, я пошел вперед и прокрутил ViewPager, и вот что я увидел:

Во время всей прокрутки обе страницы были зелеными!

Это значит, что для них был создан аппаратный слой, и страницы были аннулированы во время прокрутки ViewPager. Я обновил прокрутку страниц, используя параллакс эффект на фоне и постепенно анимировал объекты на странице. Однако я не сделал создание аппаратного слоя для страниц ViewPager. После прочтения исходного кода ViewPager, я обнаружил, что после того как пользователь начал прокрутку, аппаратный слой создается для обеих страниц и удаляется после остановки прокрутки.

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

Интересны Предупреждения(Alerts) и Кадры(Frames), показывающие подсказки сгенерированные из собранных данных. Давайте взглянем на мою трассировку и выберем одно из предупреждений сверху:

Предупреждение говорит, что был долгий вызов View#draw(). Нам предоставляют описание, ссылки на документацию и даже видео ссылки по соответствующим вопросам.

Смотря на колонку Кадров(Frames) мы видим ниже обозначение каждого отрисованного кадра, которое окрашено зеленым, желтым и красным для обозначения проблем производительности вовремя отрисовки данного кадра. Давайте выберем один из красных кадров.

Внизу мы видим все предупреждения для этого кадра. Их было 3 и одно из них мы видели ранее. Давайте увеличим этот кадр и раскроем предупреждение внизу: Наполнение вовремя рециркуляции ListView (“Inflation during ListView recycling”).

Мы видим, что эта часть составила 32 миллисекунды, а это выходит за границу 16 миллисекунд- требование для достижения 60 кадров в секунду. Там есть больше временной информации для каждого элемента в ListView в этом кадре- около 6 миллисекунд потрачено на каждый из 5 элементов. Описание помогает нам понять проблему и даже предоставляет решение. На графике вверху мы видим всю визуализацию и даже можем увеличить кусочек наполнения макета для просмотра вьюх, которые заняли много времени при наполнении макета.

Другой пример медленно отрисовывающегося кадра:

После выбора кадра мы можем нажать клавишу m чтобы его подсветить и посмотреть сколько времени заняла эта часть. Посмотрев наверх, мы видим, что отрисовка этого кадра заняла 19 миллисекунд. Развернув предупреждение, увидим, что была задержка расписания (“Scheduling delay”).

Задержка расписания обозначает то, что поток, обрабатывающий эту часть, не становился в очередь процессора длительное время. Следовательно, для завершения работы этого потока понадобилось больше времени. Выбор более длинного отрезка в этом кадре показывает более конкретную информацию:

Настенное время(Wall duration) это время, прошедшее с начала и до конца данной части кадра. Оно так называется потому, что это как смотреть на настенные часы сначала старта потока.

Время процессора это время потраченное процессором на обработку данной части.

Заметна большая разница между настенным временем и временем процессора. Первое заняло 18 миллисекунд, а второе 4 миллисекунды. Что немного странно, так что сейчас подходящее время посмотреть, чем занимался процессор все это время:

Все четыре ядра были достаточно заняты. Выбор одного из потоков показывает, что причина приложение com.udinic.keepbusyapp. В этом случае другое приложение нагружало процессор, отказывая нашему приложению в выделении времени. Этот сценарий обычно временный, так как другие приложения в фоне не часто забирают себе работу процессора, этими потоками может стать другой процесс в вашем приложении или даже ваш главный поток. Так как Системная трассировка(Systrace) инструмент обзора есть предел того, как далеко мы можем зайти. Чтобы найти причину того, что нагружает процессор в нашем приложении мы будем использовать другой инструмент Просмотр Трассировки (Traceview).

Просмотр Трассировки (Traceview)

Просмотр Трассировки инструмент анализа, показывающий время работы каждого метода. Посмотрим, как выглядит файл трассировки:

Посмотрим на различные столбцы:

Имя(Name) - имя и цвет метода на графике.
Включительное время процессора(Inclusive CPU Time) - время обработки процессором данного метода и его детей(всех методов которые он вызывает)
Исключительное время процессора(Exclusive CPU Time) - время обработки процессором данного метода исключая методы вызываемые внутри него.
Включительное/исключительное реальное время(Inclusive / Exclusive Real Time) - время с начала и до конца работы метода. Тоже что и настенное время в Системной трассировке(Systrace).
Вызовы + Рекурсия(Calls+Recursion) - сколько раз был вызван метод и также количество рекурсивных вызовов.
Процессорное/Реальное время на вызов(CPU/Real time per Call) - среднее Процессорное/Реальное время, ушедшее на вызов метода. Другие поля со временем показывают суммарное время всех вызовов метода.
Я открыл приложение, в котором прокрутка работала не плавно и долго. Я запустил трассировку, прокрутил немного и остановил трассировку. Я нашел метод getView() и раскрыл его, вот что я увидел:

Этот метод был вызван 12 раз, процессор потратил около 3мс на каждый вызов, но реальное время для завершения каждого вызова составило 162мс! Конечно это проблема…

Смотря на детей этого метода, мы видим распределение времени между различными методами. Thread.join() занял 98% включительного времени. Мы используем этот метод когда нужно дождаться завершения другого потока. Один из других детей Thread.start(), что наталкивает на мысль что метод getView() запускает поток и ждет его окончания.

Но где этот поток?
Мы не можем увидеть что делал этот поток как дочерний от getView(), так как getView() не делает эту работу напрямую. Чтоб найти это, я искал метод Thread.run(), который вызывается при старте нового потока. Я последовал дальше пока не нашел виновника:

Я выяснил, что время вызова метода BgService.doWork() заняло ~14мс, а у нас их 40! Есть шанс, что каждый getView() вызывает BgService.doWork() больше чем 1 раз и объясняет, почему каждый вызов getView() занимает много времени. Этот метод загружает процессор длительное время. Посмотрев на Исключительное время процессора, мы видим, что он использовал 80% всего времени в трассировке. Сортировка по исключительному времени процессора также хороший путь для поиска загруженных методов в трассировке. Возможно, они причастны к вашей проблеме производительности.

Следующие критические методы, такие как getView(), View#onDraw() и другие, помогают нам найти причины почему наше приложение медленно работает. Но иногда, кое-что другое загружает процессор, забирая ценные циклы процессора, которые могли бы быть потрачены на более плавную отрисовку нашего интерфейса. Сборщик мусора запускается произвольно, очищая неиспользуемые объекты и обычно не влияет на приложение, работающее на переднем плане. Если сборщик мусора запускается слишком часто, это может вызвать тормоза в приложении и возможно в этом наша вина.

Анализ памяти(Memory Profiling)

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

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

В левой части графика доступно два инструмента Дамп Кучи(Heap dump) и Трекер выделения памяти(Allocation Tracker).

Дамп кучи(Heap dump)

Для того чтобы рассмотреть что сейчас находиться в куче, мы можем использовать кнопку дамп кучи. В результате сделается снимок того что сейчас находиться в куче, и покажет это в специальном экране отчета в Андроид Студии:

Слева мы видим гистограмму экземпляров в куче, сгруппированных по имени класса. Для каждого экземпляра есть количество объектов в памяти, размер этих экземпляров (Малый размер(Shallow size)) и размер этих объектов хранящихся в памяти. Последнее говорит нам, сколько памяти может освободиться, если эти экземпляры будут освобождены. Это представление показывает объем памяти нашего приложения, помогая нам определить большие структуры данных и связи объектов. Эта информация может помочь нам создавать более эффективные структуры данных, освобождая связи объектов для уменьшения выделенной памяти и в конце концов - уменьшая объем памяти насколько возможно.

Смотря на гистограмму мы видим, что MemoryActivity имеет 39 экземпляров, что странно для активности. Выбрав один из экземпляров справа, увидим все ссылки на этот экземпляр в Дереве ссылок внизу.

Одна из них часть массива объекта ListenersManager. Смотря на другие экземпляры активности, видим, что все они удерживаются этим объектом. Это объясняет почему единственный объект этого класса занимает так много памяти.

Такие ситуации общепринято называются Утечка Памяти, так как активности были чисто уничтожены и эта неиспользуемая память не может быть убрана сборщиком мусора из за этой ссылки. Мы можем избегать подобных ситуаций, удостоверяясь что на наши объекты не ссылаются другие объекты, которые существуют больше времени. В данной ситуации ListenersManager не должен хранить эту ссылку после того как активность была уничтожена. Решение будет удалить эту ссылку, перед тем как активность будет уничтожена, в методе обратного вызова onDestory().

Утечки памяти и другие большие объекты занимают много места в куче, уменьшая доступную память, в результате сборщик мусора запустит много событий для попытки освобождения памяти. Эти события сборщика мусора занимают процессор, что приводит к ухудшению производительности вашего приложения. Если количество доступной памяти не достаточно для приложения и куча не может больше вырасти, будет более драматический результат - OutOfMemoryException, приводящий падению вашего приложения.

Более продвинутый инструмент Анализатор Памяти Eclipse(Eclipse Memory Analyzer Tool - Eclipse MAT):

Этот инструмент может делать тоже, что и Андроид Студия, а также определять потенциальные утечки памяти и предоставлять улучшенный поиск экземпляров, как например поиск всех Растровых изображений объёмом более 2Мб, или всех пустых объектов Rect .

Другой отличный инструмент библиотека LeakCanary , которая следит, чтобы у ваших объектов не было утечек. Вы получите напоминание о том, что произошло и где.

Трекер выделения памяти (Allocation Tracker)

Трекер выделения памяти запускается/останавливается с помощью других кнопок слева от графика памяти. Он сгенерирует отчет всех экземпляров расположенных в памяти в этот промежуток времени, сгруппированных по классу.

Или по методу:

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

Общие подсказки по памяти

Вот несколько быстрых подсказок/руководств, которые я использую, когда пишу код:

Перечисления(Enums) уже горячая тема для обсуждения производительности. Есть Видео об этом, показывающее размер занимаемый перечислениями, и обсуждение этого видео и некоторая информация в нем, вводящая в заблуждение. Занимают ли перечисления больше памяти чем обычные константы? Конечно. Это плохо? Не обязательно. Если вы пишите библиотеку и необходима строгая безопасность типов, это может оправдать использование перечислений вместо других решений, таких как @IntDef . Если у вас просто куча констант, которые могут быть сгруппированы - то может быть не разумно использовать для этого перечисления. Как всегда есть компромисс, который нужно принимать во внимание, когда вы принимаете решение.

Автоупаковка(Auto-boxing) - автоупаковка это автоматическое преобразование из примитивных типов в их объектное представление (например, int -> Integer). Каждый раз, когда примитивный тип «упаковывается» в объектное представление, создается новый объект(шок, я знаю). Если у нас их много - сборщик мусора запускается чаще. Количество происходящих автоупаковок легко пропустить, потому, что для нас это происходит автоматически при присваивании объекту примитивного типа. Решение- старайтесь быть последовательными с этими типами. Если вы повсеместно используете примитивные типы в вашем приложении, постарайтесь избегать их беспричинной автоупаковки. Вы можете использовать инструменты анализа памяти, чтоб найти много объектов представляющих примитивные типы. Также вы можете использовать Просмотр Трассировки и искать Integer.valueOf(), Long.valueOf() и т.д.

HashMap против ArrayMap / Sparse*Array - также как и проблема автоупаковки, использование HashMap требует от нас использовать объекты в качестве ключей. Если мы используем примитивный тип “int” в приложении и он автоупаковыветься в Integer при взаимодействии с HashMap, возможно мы можем просто использовать SparseIntArray. В случае, если нам все же нужны ключи в виде объектов, мы можем использовать класс ArrayMap. Он похож на HashMap, но внутри работает по-другому , более эффективно используя память, но уменьшая скорость работы. Эти два варианта имеют меньший объем памяти, чем HashMap, но для получения элементов или выделения памяти нужно больше времени, чем HashMap. Если у вас меньше 1000 элементов, то при выполнении программы разница не заметна, что делает их не плохим вариантом для ваших нужд использования пар ключ/значение.
Осознание Контекста - как показано ранее, легко создать утечку памяти в активностях. Возможно, вас не удивит что активности наиболее общий случай утечек в Андроид (!). Также эти утечки очень дороги, так как они содержат всю иерархию пользовательского интерфейса, что само по себе может занимать много места. Множество операций на платформе требуют объект Context, и вы обычно посылаете Активность. Убедитесь, что вы понимаете, что происходит с этой Активностью. Если ссылка на нее кэшируется, и этот объект живет дольше самой Активности, без удаления этой ссылки, то у вас появляется утечка памяти.

Избегайте не статических внутренних классов , их инициализация создает неявную ссылку не внешний класс. Если экземпляр объекта внутреннего класса необходим больше времени, чем внешний, то внешний класс останется в памяти, даже если он больше не нужен. Например, создавая не статический класс в классе Активности, наследуемый от AsyncTask, затем запустить AsyncTask, и во время его работы убить активность. Этот AsyncTask будет держать эту активность, пока не закончит свою работу. Решение- не делайте так, объявляйте статический внутренний класс если это необходимо.

Анализ графического процессора(GPU Profiling)

Новое дополнение к Андроид Студии 1.4, инструмент для анализа отрисовки графического процессора.

В окне Андроид, перейдите к вкладе GPU, и вы увидите график показывающий время отрисовки каждого кадра на вашем экране:

Каждая полоска на графике представляет один отрисованный кадр, и цвета представляют различные фазы в процессе отрисовки:

Рисование(синий) - представляет метод View#onDraw(). Эта часть создает/обновляет объекты DisplayList (В Википедии display list (или display file) это последовательность графических команд, которые определяют выходное изображение, преобразовываемые позже в команды OpenGL, которые понимает графический процессор. Большие значения могут быть из-за сложных вьюх, которые требуют больше времени для создания их display lists, или если много вьюх аннулируются в небольшой промежуток времени.

Prepare (purple) - In Lollipop, another thread was added to help the UI Thread render the UI faster. Подготовка (фиолетовый) - в Lollipop добавлен еще один поток, чтоб помочь потоку интерфейса отрисовать интерфейс быстрее. Этот поток называется RenderThread. Он отвечает за преобразование display lists в команды OpenGL и отсылку их в графический процессор. В это время поток UI может продолжить обрабатывать следующий кадр. На этом шаге показывается время за котjрое поток UI передает все ресурсы в поток RenderThread. Если у нас много ресурсов для передачи, например много display lists или они большие, то этот шаг может занять больше времени.

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

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

В Marshmallow, было добавлено больше цветов для обозначения новых шагов, таких как Замер/Макет, обработка ввода и другие:

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

Это позволит инструменту использовать команды ADB для получения необходимой информации, таким образом, мы используем:

Adb shell dumpsys gfxinfo Мы можем получить все эти данными и сами сделать график. Команда показывает другую полезную информацию, например количество вьюх в иерархии, размеры всех display lists, и другое. В Marshmallow, мы можем увидеть еще больше статистики.

Если у нас есть автоматическое тестирование интерфейса, мы можем сделать так, чтоб наш билд сервер запускал эту команду после определенных взаимодействий (прокрутка списка, тяжелые анимации и т.д.) и увидеть есть ли изменения в значениях с течением времени, например избыточные кадры. Это может помочь определить проблемы производительности после пуша некоторых коммитов, давая нам время определить проблему перед тем как приложение уйдет в производство. Мы даже можем получить более точную информацию отрисовки, используя ключевое слово “framestats”, как объяснено.

Но это не единственный способ увидеть этот график!

Как вы видели в опциях разработчика в разделе “Profile GPU Rendering”, есть вариант просмотра графика как «Полоски на экране». Его включение, покажет график для каждого окна на нашем экране, вместе с зеленой полосой для определения порога 16 мс.

На примере справа мы видим некоторые кадры пересекли зеленую линию, это означает, что для их отрисовки потребовалось более 16мс. Так на этих полосках преобладает синий цвет, мы понимаем, что для отрисовки либо было много вьюх либо они были сложные, либо и то и другое. В этом сценарии, я прокрутил список новостной ленты, который поддерживает различные типы вьюх. Некоторые вьюхи были аннулированы, а некоторые сложнее отрисовывать чем другие. Возможная причина того, что некоторые кадры пересекают этот порог то, что есть сложная вьюха для отрисовки в данный момент.

Обозреватель иерархии(Hierarchy Viewer)

Я люблю этот инструмент, и меня печалит, то что многие его вообще не используют!

Используя Обозреватель иерархии, мы можем получить статистику производительности, видеть на экране полную иерархию вьюх и иметь доступ ко всем свойствам вьюх. Вы также можете получить дамп данных темы, увидеть все значения, использованные для каждого атрибута стиля, но это доступно только при запуске Обозревателя иерархии как отдельного приложения, не из Андроид Монитора. Я использую этот инструмент при дизайне моих макетов и когда я хочу их оптимизировать.

В центре мы видим дерево представляющие иерархию вьюх. Иерархия вьюх может быть широкой, но если она слишком глубока(около 10 уровней), ценой могут быть дорогостоящие фазы макета/замера. Каждый раз, когда вьюха замеряется, в View#onMeasure() или когда она располагает все дочерние вьюхи, в View#onLayout(), эти команды распространяются на дочерние вьюхи, которые повторяют те же действия. Некоторые макеты повторяют каждый шаг дважды, например RelativeLayout и некоторые конфигурации LinearLayout, и если они вложенные - количество проходов возрастает по экспоненте.

Внизу справа мы видим «чертеж» нашего макета, отмечающий расположение каждой вьюхи. Мы можем выделить вьюху здесь или в дереве и увидим все её свойства слева.

Когда я создаю макет, иногда я не уверен, почему определенная вьюха закончилась там где она сейчас. Используя этот инструмент, я могу отследить её в дереве, выбрать ее и увидеть где она в окне предосмотра.. Смотря на конечные замеры вьюх на экране, я могу создавать интересные анимации, и использовать эту информацию чтобы аккуратно передвигать вещи. Я могу найти потерянные вьюхи, на которые нечаянно наложились другие вьюхи, и многое другое.

Для каждой вьюхи у нас есть время ее измерения/макета/отрисовки и всех ее дочерних вьюх. Цвета указывают, как представлена данная вьюха в сравнении с другими вьюхами в дереве, это хороший способ найти слабейшую ссылку. Так как мы также видим предосмотр этой вьюхи мы можем обойти дерево по шагам ее создания, находя избыточные шаги, которые мы можем убрать. Одна из таких вещей, которые имеют большое влияние на производительность, называется Наложение(Overdraw).

Наложение(Overdraw)

Как вы видели в разделе Анализ графического процессора(GPU Profiling) - фаза Исполнение, представленная желтым цветом на графике, для завершения может занять больше времени если графическому процессору нужно рисовать много объектов на экране, увеличивая время отрисовки каждого кадра. Наложение происходит, когда мы рисуем одно поверх другого, например желтую кнопку на красном фоне. Графический процессор сначала должен нарисовать красный фон и затем желтую кнопку сверху, делая наложение неизбежным. Если у нас много слоев наложения, это приводит к тому что графический процессор работает больше и будет дальше от цели 16 мс.

Используя пункт «Отладка Наложения GPU» в опциях Разработчика, все наложения будут окрашены для обозначения сложности наложения в этой области. Наложение 1x/2x это нормально, даже некоторые светло красные области это неплохо, но если мы видим слишком много красного на экране, это может быть проблемой. Давайте рассмотрим несколько примеров:

На примере слева список нарисован зеленым, что обычно хорошо, но сверху есть наложение, которое делает его красным и это уже проблема. На примере справа весь список в красном цвете. В обоих случаях есть непрозрачный список с наложением 2x/3x. Эти наложения могут быть в случае, если есть полноэкранный цвет фона в окне содержащем вашу Активность/Фрагмент, список и и каждый элемент списка. Такую проблему можно решить установкой цвета фона только для одного из них.

Примечание: тема по умолчанию объявляет полно экранный фоновый цвет для вашего окна. Если у вашей активности непрозрачный макет, который занимает весь экран, вы можете убрать фон окна, чтобы убрать один слой наложения. Это можно сделать в теме или в коде, вызвав getWindow().setBackgroundDrawable(null) в onCreate().

Используя Обозреватель иерархии, вы может сделать экспорт всех слоев иерархии в PSD файл и открыть его в Photoshop. Рассмотрение различных слоев в Photoshop, раскроет все наложения в макете. Используйте эту информацию для удаления избыточных наложений, и не довольствуйтесь зеленым, добивайтесь синего!

Альфа(Alpha)

Использование прозрачности может повлиять на производительность, чтоб понять почему- посмотрим что происходит при установке значения альфа вьюхе. Рассмотрим следующий макет:

Мы видим макет, содержащий 3 ImageViews, наложенных друг на друга. В прямой/простой реализации, установка альфа, используя setAlpha() команда передается всем дочерним вьюхам, ImageViews в данном случае. Затем, эти ImageViews будут нарисованы с этим значением альфа в буфере кадра. Результат:

Мы не это хотели видеть.

Та как ImageView отрисован с значением альфа, все наложенные изображения смешались. К счастью у операционной системы есть решение этой проблемы. Макет будет скопирован в внеэкранный буфер, альфа будет применена ко всему этому буферу и результат будет скопирован в буфер кадра. Результат:

Но мы заплатили за это цену.

Рисование вьюхи в внеэкранном буфере, перед рисованием ее в буфере кадра, это виртуальное добавление еще одного неопределенного слоя наложения. Операционная система не знает точно когда применять этот подход или прямой подход показанный ранее, таким образом, по умолчанию делает более сложный. Но есть еще способы установить альфа и избежать сложность, добавляемую вне экранным буфером:

  • TextViews - Используйте setTextColor() вместо setAlpha(). Использование альфа канала для цвета текста, приводит к прямому рисованию текста с использованием этого канала.
  • ImageView - Используйте setImageAlpha() вместо setAlpha(). Та же причина, что для TextView.
  • Настраиваемые вьюхи - если настраиваемая вьюха не поддерживает наложенные вюхи, то это сложное поведение нам не подходит. Нет возможности для наших дочерних вьюх смешаться, как показано в примере выше. Переопределяя метод hasOverlappingRendering() вернуть false, мы даем сигнал операционной системе выбрать прямой/простой путь с нашей вьюхой. Таже есть возможность вручную обрабатывать происходящее при установке альфа, переопределяя метод onSetAlpha() возращать true.

Аппаратное ускорение(Hardware Acceleration)

Когда Аппаратное ускорение было представлено в Honeycomb, у нас появилась новая модель рисования для отрисовки нашего приложения на экране. Оно представило структуры DisplayList, которые записывают команды рисования вьюхи для быстрой отрисовки. Но есть еще одна супер особенность, которую разработчики иногда упускают или не используют правильно - слои Вьюхи.

Используя слой вьюхи, мы можем нарисовать вьюхи в внеэкранном буфере(как вы видели ранее, применяя Альфа канал) и обработать как нам необходимо. Эта особенность, в основном, хороша для анимаций, так как мы можем анимировать сложные Вьюхи быстрее. Без слоев анимации вьюхи аннулирует её после изменения анимированного свойства(например, х координаты, масштаб, значение альфа и др.). Для сложных вьюх это аннулирование передается на все дочерние вьюхи, и они затем перерисуют себя, сделав дорогостоящую операцию. Использую слой вьюхи, обеспечиваемый Аппаратными средствами, текстура для нашей вьюхи создается в графическом процессоре. Есть несколько операций, которые мы можем применить к этой текстуре без ее аннулирования, такие как позиция x/y, вращение, альфа и другие. Все это означает, что мы можем анимировать сложную вьюху вообще без ее аннулирования во время анимации! Это делает анимацию более сглаженной. Вот пример кода, как это сделать:
// Используя Object animator view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); objectAnimator.start(); // Используя аниматор Свойства(Property animator) view.animate().translationX(20f).withLayer().start();
Правда, просто?

Да, но нужно помнить несколько вещей при использовании аппаратных слоев:

  • Подчищайте за своей вьюхой - аппаратные слои потребляют место на вашем графическом процессоре, компоненте с ограниченной памятью. Пробуйте и используйте их только в то время, когда они необходимы, например в анимации, и потом очищайте их. В примере с ObjectAnimator выше я применил слушатель для удаления слоя после окончания анимации. В примере аниматора Свойства, я использовал метод withLayers(), который автоматически создает слой вначале и удаляет его в конце анимации.
  • Если вы измените свою вьюху после применения аппаратного слоя, это аннулирует аппаратный слой и отрисует всю вьюху заново в вне экранном буфере. Это произойдет после изменения свойства которое не оптимизировано для аппаратных слоев(сейчас оптимизированы следующие: вращение, масштабирование, x/y, перемещение, точка вращения и альфа. Например, если вы анимируете вьюху с поддержкой аппартного слоя, изменяя цвет фона во время её движения по экрану, приведет к постоянным обновлениям аппаратного слоя. Обновление аппаратного слоя имеет накладные расходы, из за которых возможно его не стоит использовать.

Для второй проблемы, есть способ отобразить эти обновления аппаратного слоя. Используя опции Разработчика, мы можем включить «Показывать обновления аппаратного слоя».

Когда это включено, Вьюха светится зеленым цветом во время обновления аппаратного слоя. Один раз я это использовал, когда мой ViewPager не прокручивался так плавно, как я ожидал. После включения этой опции разработчика, я пошел вперед и прокрутил ViewPager, и вот что я увидел:

Во время всей прокрутки обе страницы были зелеными!

Это значит, что для них был создан аппаратный слой, и страницы были аннулированы во время прокрутки ViewPager. Я обновил прокрутку страниц, используя параллакс эффект на фоне и постепенно анимировал объекты на странице. Однако я не сделал создание аппаратного слоя для страниц ViewPager. После прочтения исходного кода ViewPager, я обнаружил, что после того как пользователь начал прокрутку, аппаратный слой создается для обеих страниц и удаляется после остановки прокрутки.

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

Сегодня попробуем разобраться с одним интересным пунктом меню, в разделе Настройки - Для разработчиков . В этом меню размещены 37 подпунктов для гибкой настройки и глубокого управления своим устройством на Android. Далее предлагаем ознакомиться с каждым из них и рассмотреть скриншоты самого меню для разработчиков.

Все действия проводятся на смартфоне Nexus 4, который работает под управлением ОС Android 4.4. Комментирование каждого пункта меню основано на его же описании. Если читатель знает более подробную информацию о какой-то команде - делитесь опытом. Не забывайте, все изменения в этом режиме вы вносите на свой страх и риск!

Для того чтобы включить отоброжение пункта "Для разработчиков" в списке настроек, нужно провести следующие действия:

  1. Заходим в меню настроек
  2. Переходим в пункт О телефоне
  3. Находим строку Номер сборки
  4. Нажимаем по нему семь раз.

Если появилось вот такое уведомление, то у нас все получилось!

Меню "Для разработчиков" теперь будет находится в списке в настройках гаджета. На Android 4.4 пункт размещен между "Печать" и "О телефоне"!

Вот список и описание всех функций, которое дает нам скрытое меню:

  1. Создание отчета об ошибке - кнопка неактивна при нормальной работе устройства.
  2. Пароль для резервного копирования - возможность защитить полные резервные копии данных смартфона или планшета. Пароль вводится вручную и создается самым пользователем.
  3. Не выключать экран - активация этого действия приведет к постоянной подсветки экрана в процессе зарядки устройства.
  4. Выберите среду - доступны для выбора две рабочие среды: Dalvik и ART. Последняя более новая и призвана повысить уровень производительности приложений и устройства в целом. Включить журнал трансляции - команда служит для сохранения всех пакетов HCI Bluetooth в файле.
  5. Статистика процессов - выполнение этой операции позволит вести статистику запущенных процессов.
  6. Отладка по USB - включает режим отладки при подключении к компьютеру при помощи USB-кабеля.
  7. Запретить доступ для отладки с помощью USB на всех компьютерах, на которых вы входили в аккаунт .
  8. Отчеты об ошибках - кнопка неактивна в штатном режиме. При активации, устройство начнет отображать в меню опцию отправки отчета об ошибке.
  9. Фиктивное местоположение - удивительная функция, которая умеет маскировать текущее местоположение владельца с гаджетом и выдавать ложные координаты.
  10. Выберите приложение для отладки - можно задать определенные приложения для возможности их отладки.
  11. Подождите пока подключится - приложение ожидает подключения отладчика в штатном режиме. Кнопка неактивна.
  12. Установленные через USB - функция позволяет проверять на безопасность приложения, устанавливаемые через ADB/ADT. Кнопка неактивна.
  13. Сертификация беспроводных … - отображает параметры сертификации беспроводных мониторов.
  14. Показывать нажатия - при активации кнопки, реализуется отображения точки в местах нажатия на экран.
  15. Отображать касания - проводится визуализация на экране нажатия и жестов.
  16. Показ. обнов. поверхности - осуществляется подсвечивание окон полностью при их обновлении.
  17. Показывать границы элементов - обеспечивает отображение границ клипа, поля и т.д.
  18. Написание справа налево - отображает написание текстов справа налево для всех языков.
  19. Окно: масштаб - установка масштаба анимации от 0,5х до 10х. Также можно отключить анимацию вообще.
  20. Переход: масштаб - установка масштаба перехода от 0,5х до 10х. Отключение анимации также возможна.
  21. Скорость анимации - можно задать скорость анимации гаджета в интервале от 0,5х до 10х. Анимацию можно отключить вообще.
  22. Эмуляция дополнительных экранов - интересная опция, которая может провести эмуляцию дополнительных экранов на основном физическом экране устройства. Включив эту опцию, пользователь увидит еще один экран, который продублирует основной. Размеры дополнительного экрана настраиваются самостоятельно.
  23. GPU-ускорение - можно поставить галочку и, тем самым, всегда использовать GPU для двухмерного рисования.
  24. Показывать обновление экрана - поставленная галочка обеспечит подсветку области экрана при отрисовке GPU.
  25. Показывать аппаратные обновления - задействование этой функции выделит аппаратные уровни зеленым при обновлении.
  26. Отладка наложения - этот пункт содержит три возможных варианта для выбора: 1) выкл.; 2) показывать области наложения; 3) выделять области определенного цвета; 4) показывать число объектов.
  27. Отладить операции непрямоугольного усечения - на этом этапе можно провести три действия: 1) отключить опцию; 2) непрямоугольное сечение; 3) тестирование команды рисование зеленым.
  28. Включить 4х MSAA - поставив галочку, пользователь (или уже разработчик) включает 4х MSAA в приложениях OpenGL ES 2.0.
  29. Отключить аппаратные наложения - активация пункта повлечет за собой постоянное использование GPU при компоновке экрана.
  30. Включен строгий режим - данный режим будет подсвечивать экран Android-устройства во время длительных операций.
  31. Показывать загрузку ЦП - при активации этого действия, смартфон отобразит на своем экране дополнительное окошко с графическим отображением текущей загрузки ЦП. Запись времени работы GPU - название данного пункта говорит само за себя.
  32. Включить трассировку OpenGL - имеет четыре возможным варианта развития событий: 1) нет данных; 2) Logcat; 3) Systrace; 4) Список вызовов в glGetError/
  33. Не сохранять действий - данный пункт меню относится к приложениям, активация его приведет к удалению сводки действий после их завершения.
  34. Лимит фоновых процессов - полезная функция. Она способна ограничить количество работающих приложений в фоновом режиме.
  35. Все ANR - птичка, поставленная напротив данного пункта обеспечит уведомления пользователя о том, что приложение лагает (авт.) или не отвечает.

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

Но с чего начать?

Предположим, что я никогда не делал освещения в старом OpenGL, поэтому я бы пошел прямо на не устаревшие методы.

В настоящее время приложение правильно настроило объекты буфера вершин, вершинный, нормальный и цветной ввод и правильно рисует и преобразует модели в пространстве в плоском цвете.

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

2 ответов

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

Чтобы проиллюстрировать проблему немного дальше: Для трассировки лучей вам нужно проследить вторичные лучи и проверить пересечение с геометрией. Поэтому вам необходим доступ к геометрии каким-то умным способом внутри шейдера, однако внутри шейдера фрагмента вы не можете получить доступ к геометрии, если вы не храните его "закодировано" в некоторую текстуру. Вершинный шейдер также не предоставляет вам эту информацию геометрии изначально, а геометрические шейдеры знают только соседей, поэтому здесь проблема уже началась. Затем вам нужны структуры данных ускорения, чтобы получить разумные частоты кадров. Однако перемещение, например, Kd-Tree внутри шейдера довольно сложно, и, если я правильно помню, есть несколько статей только по этой проблеме. Если вы действительно хотите пройти этот маршрут, тем не менее, есть много статей по этой теме, их не должно быть слишком сложно найти.

Трассировщик лучей требует очень хорошо разработанных шаблонов доступа и кеширования для достижения хорошей производительности. Однако у вас есть только небольшой контроль над ними внутри GLSL, и оптимизация производительности может стать очень сложной.

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

Возможно, вы что-то путаете.

OpenGL - это растеризатор. Заставлять его делать трассировку лучей возможно, но сложно. Вот почему raytracing не "идеально подходит для графики в реальном времени через несколько лет". Через несколько лет только гибридные системы будут жизнеспособными.

Итак, у вас есть три возможности.

  • Чистая трассировка. Выполните рендеринг только полноэкранного квадроцикла и в своем шейдере фрагмента прочитайте описание сцены, упакованное в буфер (например, текстуру), перейдите по иерархии и вычислите пересечения лучей треугольников.
  • Гибридная трассировка. Растрируйте свою сцену обычным способом и используйте raytracing в вашем шейдере на некоторых участках сцены, которые действительно требуют этого (преломление,... но это может быть одновременно в растеризации).
  • Чистая растеризация. Фрагментный шейдер выполняет свою нормальную работу.

Что именно вы хотите достичь? Я могу улучшить ответ в зависимости от ваших потребностей.

В Android, как и в других популярных операционных системах, есть свои секреты. Некоторые из них полезны, но используются редко. Мы расскажем о малоизвестных и интересных секретах Андроида.

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

Инженерные коды

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

*#*#4636#*#* - информация и настройка;

*#*#8351#*#* - включить запись телефонных разговоров;

*#*#4636#*#* - предоставит полезные данные об устройстве:

  • о телефоне;
  • о батарее;
  • статистика и использование телефона и батареи.

*#*#7780#*#* - отформатирует смартфон или планшет, но оставит все приложения, будь то системные или загруженные. Также останутся все файлы на внешней SD-карте.

*2767*3855# - полностью отформатирует девайс.

*#*#34971539#*#* - позволяет управлять прошивкой камеры, а также получить о ней информацию. После введения кода вы можете выбрать:

  • обновление прошивки камеры в образ (ни в коем случае не делать!);
  • обновление прошивки камеры;
  • данные о прошивке камеры;
  • количество выполняемых ранее прошивок камеры.

*#*#7594#*#* - позволит изменить функцию при длительном зажатии кнопки питания. Другими словами, вы можете назначить для нее выключение или перезагрузку гаджета, включение/выключение мобильных данных и так далее;

*#*#273283*255*663 282*#*#* - позволяет сделать резервное копирование любых файлов на устройстве;

*#*#197328640#*#* - открывает меню обслуживания. Вы можете протестировать ваш гаджет, а также сменить настройки WLAN, Bluetooth и GPS;

*#*#232339#*#* или *#*#526#*#* или *#*#528#*#* - настройки WLAN;

*#*#232338#*#* - поможет узнать МАС-адрес Wi-FI;

*#*#1472365#*#* - тест GPS системы;

*#*#1575#*#* - GPS;

*#*#232331#*#* - Bluetooth;

*#*#232337#*# - поможет узнать адрес Bluetooth.

Коды для тестирования

Они запускают различные тесты устройства.

*#*#0283#*#* - тестирование передающей инфраструктуры;

*#*#0*#*#* - экрана (LCD);

*#*#0673#*#* или *#*#0289#*#* - звука;

*#*#0842#*#* - девайса (подсветки и вибрации);

*#*#2663#*#* - сенсора;

*#*#2664#*#* - еще один тест сенсора;

*#*#0588#*#* - датчика движения;

*#*#3264#*#* - RAM.

Режим разработчика

Теперь поговорим о «Режиме разработчика». Вы уже наверняка видели такой пункт в настройках, но теперь пора рассмотреть его подробнее. Данный режим активирует дополнительные функции, которые вряд ли понадобятся в повседневной жизни. В основном, они используются разработчиками и программистами.

Для начала необходимо включить режим разработчика. Заходим в настройки и листаем в самый низ. Находим пункт «Об устройстве», и нажимаем на него несколько раз подряд. Гаджет потребует подтверждения разблокировки режима разработчика – нажмите ОК.


Теперь нам доступен ряд возможностей, пользоваться которыми нужно аккуратно! Они могут привести к системным ошибкам, о чем предупредит сам смартфон. Ниже представлены и описаны все функции данного режима.

  • Пароль резервного копирования. Если не хотите, чтобы чужие руки делали резервное копирование всех файлов вашего телефона (например, после этого загрузив все данные на свое устройство), поставьте пароль на использование.
  • Активный режим. Если ваш смартфон заряжается, то он не будет гаснуть (конечно, если вы сами его не выключите).
  • Защитить карту памяти SD. Все программы будут спрашивать разрешение на использование данных с карты памяти. Так, например, вы можете предотвратить работу вируса.
  • Отладка USB нужна для выполнения взаимодействия гаджета с ПК.
  • Эмуляция расположения эмулирует расположение.
  • Выберите отлаживаемое приложение.
  • Подождать отладчик. Когда отладчик подключится, откроется заданное выше приложение.
  • Показывать прикосновения. Довольно интересная функция, которая показывает, в каком месте вы прикоснулись к экрану. Очень полезная штука, ведь с помощью нее можно обозначать касания на экран и делать скриншоты к инструкциям, как мы сделали в статье про .
  • Показывать место указателя. Выводит подробную информацию о прикосновении и слайде по экрану (Местоположение по Х и Y и др).


  • Показывать ограничения макета. Интересная функция, с помощью которой можно увидеть размер каждого элемента на экране.

  • Показывать обновления представлений GPU. Окна, представленные посредством GPU, будут мигать.
  • Показывать обновления экрана. Обновляемая область экрана будет мерцать ярко-желтым цветом.
  • Настройка анимации. Включает масштаб анимации окна, масштаб анимации перехода и шкалу длительности аппарата. Их отключение очень помогает .
  • Отключить аппаратное наложение – постоянное использование GPU для композиции экрана.
  • Принудительная обработка GPU. Использовать аппаратное ускорение 2D в приложениях.
  • Строгий режим. Если процесс будет выполнять длительные операции в главном потоке, то экран будет мигать.
  • Выводить использование ЦП – информация об использовании центрального процессора в правом верхнем углу.

  • Профиль обработки GPU – измерение времени обработки в ASDG.
  • Включить трассировку. Включает различные трассировки, например, graphics, Input, View и другие.
  • Не сохранять операции. Удалять операции после их завершения пользователем.
  • Фоновые процессы. Позволяет ограничить количество фоновых процессов от одного до четырех.
  • Показать все ANR. Выводить окно «Приложение не отвечает» для фоновых процессов.


Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: