Производительность — один из ключевых параметров мобильного приложения. Ваше детище может быть сколь угодно функциональным, красивым и полезным, но если оно тормозит — провал практически гарантирован. К счастью, многих проблем можно избежать, следуя простым правилам и пользуясь подходящими инструментами.
Очевидно, что это огромный объем работы и следует приложить все усилия для того, чтобы выполнить ее как можно быстрее. Два главных инструмента в этом деле:
Обработку сложных и дорогостоящих операций, которые невозможно оптимизировать, а также блокируемых операций типа чтения с диска или получения данных с сервера, следует отправлять в отдельный поток и обновлять интерфейс асинхронно по завершении его работы.
Пример: у вас есть приложение, которое должно показывать на главном экране сводку данных, полученных из интернета. Самый очевидный способ сделать это — получить данные с сервера и после этого отобразить интерфейс. И хотя Android по умолчанию не позволяет выполнять сетевые запросы в основном потоке приложения, вынуждая создавать отдельный поток для получения данных с сервера, большинство разработчиков все равно постараются сделать код последовательным.
Проблема такого подхода в том, что он вносит задержки, которые просто не нужны. Большую часть времени сетевой поток будет простаивать в ожидании данных, и это время лучше использовать для отображения интерфейса. Другими словами: сразу после запуска приложения следует создать поток, который будет получать данные с сервера, но не ожидать получения этих данных, а создавать интерфейс, используя временные заглушки вместо пока еще не принятых данных.
В качестве заглушек можно использовать пустые картинки, пустые строки, пустые списки (например, RecyclerView можно инициализировать сразу, а при получении данных просто вызвать notifyDataSetChanged()). После получения данных с сервера их следует кешировать. При следующем запуске их можно будет использовать вместо заглушек.
Такой подход эффективно работает в отношении не только сетевых коммуникаций, но и любых задач, требующих долгих вычислений и/или ожидания данных. Например, лаунчеру необходимо немало времени, чтобы запросить у системы список установленных приложений, отсортировать его, загрузить в память иконки и другие данные. Поэтому современные лаунчеры делают это асинхронно: отображают интерфейс и с помощью фонового потока заполняют его иконками приложений.
Еще одно узкое место: формирование интерфейса из описания лайотов в XML-файле. Когда вы вызываете метод setContentView() или inflate() объекта LayoutInflater (в коде фрагмента), Android находит нужный лайот в бинарном XML-файле (для эффективности Android Studio упаковывает XML в бинарный формат), читает его, проводит парсинг и на основе полученных данных формирует интерфейс, измеряя и подгоняя элементы интерфейса друг к другу.
Это действительно сложная и дорогая операция. Поэтому необходимо уделить особое внимание оптимизации лайотов: избегать излишней вложенности лайотов друг в друга (например, использовать RelativeLayout вместо вложенных друг в друга LinearLayout), а также разбить сложные описания интерфейса на множество более мелких и загружать их только тогда, когда в этом возникнет необходимость.
Другой вариант — перейти на язык Kotlin и использовать библиотеку Anko. Она позволяет описывать интерфейс прямо в коде, благодаря чему скорость отображения интерфейса возрастает в четыре раза, а вы получаете большую гибкость в управлении логикой формирования интерфейса.
Есть, однако, намного более тонкий и неочевидный момент. Android обновляет экран со скоростью 60 FPS. Это значит, что во время показа анимации или промотки списков у него есть всего 16,6 мс на отображение каждого кадра. В большинстве случаев Android справляется с этой работой и не теряет кадров. Но некорректно написанное приложение может затормозить его.
Простой пример: RecyclerView — элемент интерфейса, позволяющий создавать чрезвычайно длинные проматываемые списки, которые занимают одинаковое количество памяти вне зависимости от длины самого списка. Такое возможно благодаря переиспользованию одних и тех же наборов элементов интерфейса (ViewHolder) для отображения разных элементов списка. Когда элемент списка скрывается с экрана, его ViewHolder перемещается в кеш и затем используется для отображения следующих элементов списка.
Когда RecyclerView извлекает ViewHolder из кеша, он запускает метод onBindViewHolder() вашего адаптера, чтобы наполнить его данными конкретного элемента списка. И тут происходит интересное: если метод onBindViewHolder() будет делать слишком много работы, RecyclerView не успеет вовремя сформировать следующий элемент для отображения и список начнет тормозить во время промотки.
Еще один пример. К RecyclerView можно подключить кастомный RecyclerView.OnScrollListener(), метод OnScrolled() которого будет вызван при промотке списка. Обычно его используют для динамического скрытия и показа круглой action-кнопки в углу экрана (FAB — Floating Action Button). Но если реализовать в этом методе более сложный код, список опять же будет притормаживать.
Ну и третий пример. Допустим, интерфейс вашего приложения состоит из множества фрагментов, между которыми можно переключаться с помощью бокового меню (Drawer). Кажется, что самый очевидный вариант решения этой задачи — поместить в обработчик нажатия на пункты меню примерно такой код:
Все логично, вот только если вы запустите приложение и протестируешь его, то увидите, что анимация закрытия меню тормозит. Проблема кроется в том, что метод commit() асинхронный, то есть переключение между фрагментами и закрытие меню будут происходить одновременно и смартфон просто не успеет вовремя обработать все операции обновления экрана.
Чтобы избежать этого, переключать фрагмент нужно уже после того, как анимация закрытия меню закончилась. Сделать это можно, подключив к меню кастомный DrawerListener:
Еще один совсем не очевидный момент. Начиная с Android 3.0 рендеринг интерфейса приложений происходит на графическом процессоре. Это значит, что все битмапы, drawable и ресурсы, указанные в теме приложения, выгружаются в память GPU и поэтому доступ к ним происходит очень быстро.
Любой элемент интерфейса, показанный на экране, преобразуется в набор полигонов и GPU-инструкций и поэтому отображается в случае, например, промотки быстро. Так же быстро будет скрываться и показываться View с помощью изменения атрибута visibility (button.setVisibility(View.GONE) и button.setVisibility(View.VISIBLE)).
А вот при изменении View, даже самом минимальном, системе вновь придется пересоздать View с нуля, загружая в GPU новые полигоны и инструкции. Более того, при изменении TextView эта операция станет еще более дорогой, так как Android придется сначала произвести растеризацию шрифта, то есть превратить текст в набор прямоугольных картинок, затем сделать все замеры и сформировать инструкции для GPU. А еще есть операция пересчета положения текущего элемента и других элементов лайота. Все это необходимо учитывать и изменять View только тогда, когда это действительно нужно.
Overdraw — еще одна серьезная проблема. Как мы говорили выше, разбор сложных лайотов с множеством вложенных элементов будет медленным сам по себе, но также он наверняка принесет с собой проблему частой перерисовки экрана.
Представьте, что у вас есть несколько вложенных друг в друга LinearLayout и некоторые из них к тому же со свойством background, то есть они не только вмещают в себя другие элементы интерфейса, но и имеют фон в виде рисунка или заливки цветом. В результате на этапе рендеринга интерфейса GPU будет делать так: заполнит пикселями нужного цвета область, занимаемую корневым LinearLayout, затем заполнит часть экрана, занимаемую вложенным лайотом, другим цветом (или тем же) и так далее. В результате во время рендеринга одного кадра многие пиксели на экране будут обновлены несколько раз. А это не имеет никакого смысла.
Полностью избежать overdraw невозможно. Например, если необходимо отобразить кнопку на красном фоне, тебе все равно придется сначала залить экран красным, а затем повторно изменить пиксели, отображающие кнопку. К тому же Android умеет оптимизировать рендеринг так, чтобы overdraw не происходил (например, если два элемента одинакового размера находятся друг над другом и второй непрозрачен, первый просто не будет отрисован). Однако многое зависит от программиста, который должен всеми силами стараться минимизировать overdraw.
Поможет в этом инструмент отладки наложений. Он встроен в Android и находится здесь: Settings → Developer Options → Debug GPU overdraw → Show overdraw areas. После его включения экран перекрасится в разные цвета, которые означают следующее:
Overdraw здорового приложения и overdraw курильщика
Ну и несколько советов:
Systrace — один из важнейших инструментов, которые вы должны освоить. Это трассировщик, который позволяет проследить, что происходит на устройстве во время работы приложения. В частности, он наглядно покажет, как происходит отрисовка каждого кадра, какие кадры были отрисованы вовремя, а какие системе пришлось выбросить.
Systrace можно запустить с помощью Android Device Monitor, который, в свою очередь, находится в меню Tools → Android в Android Studio. Открываем Android Device Monitor, дожидаемся, пока он обнаружит смартфон, выбираем приложение и нажимаем кнопку запуска трассировки.
В открывшемся окне запуска оставляем все настройки как есть и нажимаем Ok. Трассировка продолжится пять секунд, после чего будет сформирован HTML-файл с данными (в *nix это файл trace.html в домашнем каталоге). Его следует открыть в браузере.
При первом взгляде отчет Systrace вводит в замешательство — огромное количество непонятно что значащих данных. К счастью, большая часть этих данных вам не понадобится, вас интересуют только строки Frames, UI Thread и RenderThread.
Frames показывает обновления экрана. Каждый кадр — это кружок одного из трех цветов: зеленый, желтый, красный. Зеленый означает, что кадр был отрисован за 16,6 мс, желтый и красный — отрисовка кадра заняла больше 16,6 мс, то есть фреймрейт падает. Сразу под строкой Frames находится строка UI Thread, с помощью которой можно проанализировать, какие шаги выполнила система для отображения фрейма.
Кликнув по кругу, вы получите дополнительную информацию о том, почему система потратила на отрисовку фрейма больше времени, чем положено. Возможные ситуации и методы их решения описаны в документации разработчика. Добавим только, что не стоит обращать внимания на проблему Sheduling delay. Обычно она вызвана вовсе не вашим приложением, а самим Android. Особенно часто она появляется на старых и маломощных смартфонах.
Systrace позволяет оценить, на каком этапе произошла задержка отрисовки. Но он не скажет вам, была ли проблема вызвана вашим кодом, и если да, то где конкретно узкое место. Чтобы найти проблему, вывод Systrace можно детализировать, добавив в код приложения маркеры, которые позволят оценить, сколько времени занимает выполнение вашего собственного кода. Пример трассировки метода onBindViewHolder:
Есть более простой инструмент трассировки, встроенный в Android. Просто включи опцию Developer options → Profile GPU rendering → On screen as bars, и на экране появится график. По оси X — кадры, по оси Y — столбцы, отображающие длительность отрисовки каждого кадра. Если столбец выше зеленой черты — на отрисовку кадра ушло больше 16,6 мс.
Запускаем Android Studio, кликаем на Android Profiler внизу экрана, затем на CPU и нажимаем красную круглую кнопку записи вверху экрана, останавливаем запись, когда нужно. Внизу экрана появится окно с отчетом.
По умолчанию отчет выводится в виде диаграммы, где по оси X отображается время, а по оси Y — вызываемые методы. Оранжевым помечены системные методы (API), зеленым — методы самого приложения, голубым — методы сторонних API, включая Java. На вкладке Flame chart — похожая диаграмма, в которой одинаковые методы объединены. Она удобна тем, что позволяет наглядно оценить, сколько всего времени работал тот или иной метод за весь период трейсинга.
Вкладки Top Down и Bottom Up показывают дерево вызовов методов, включая информацию о затраченном на их выполнение времени:
Как появляются лаги
Перед тем как перейти к обсуждению инструментов и техник повышения производительности, уделим немного времени тому, как вообще появляются лаги и почему приложение может быть медленным. Основные проблемы современных приложений:- слишком долгая загрузка приложения и отдельных экранов;
- фризы, когда приложение просто зависает, а через некоторое время появляется сообщение с предложением его "убить";
- проседания FPS, когда вместо плавной прокрутки и анимации пользователь видит слайд-шоу.
Холодный старт
Запуск приложения состоит из нескольких стадий: это инициализация нового процесса, подготовка окна для вывода интерфейса приложения, показ окна на экране и передача управления коду приложения. Далее приложение должно сформировать интерфейс на основе описания в XML-файле, подгрузить с «диска» или из интернета необходимые для корректного отображения интерфейса элементы (битмапы, данные для списков, графиков и прочее), инициализировать дополнительные элементы интерфейса, такие как выдвижное меню (Drawer), повесить на элементы интерфейса колбэки.Очевидно, что это огромный объем работы и следует приложить все усилия для того, чтобы выполнить ее как можно быстрее. Два главных инструмента в этом деле:
- отложенная инициализация;
- параллельный запуск задач.
Обработку сложных и дорогостоящих операций, которые невозможно оптимизировать, а также блокируемых операций типа чтения с диска или получения данных с сервера, следует отправлять в отдельный поток и обновлять интерфейс асинхронно по завершении его работы.
Пример: у вас есть приложение, которое должно показывать на главном экране сводку данных, полученных из интернета. Самый очевидный способ сделать это — получить данные с сервера и после этого отобразить интерфейс. И хотя Android по умолчанию не позволяет выполнять сетевые запросы в основном потоке приложения, вынуждая создавать отдельный поток для получения данных с сервера, большинство разработчиков все равно постараются сделать код последовательным.
Проблема такого подхода в том, что он вносит задержки, которые просто не нужны. Большую часть времени сетевой поток будет простаивать в ожидании данных, и это время лучше использовать для отображения интерфейса. Другими словами: сразу после запуска приложения следует создать поток, который будет получать данные с сервера, но не ожидать получения этих данных, а создавать интерфейс, используя временные заглушки вместо пока еще не принятых данных.
В качестве заглушек можно использовать пустые картинки, пустые строки, пустые списки (например, RecyclerView можно инициализировать сразу, а при получении данных просто вызвать notifyDataSetChanged()). После получения данных с сервера их следует кешировать. При следующем запуске их можно будет использовать вместо заглушек.
Такой подход эффективно работает в отношении не только сетевых коммуникаций, но и любых задач, требующих долгих вычислений и/или ожидания данных. Например, лаунчеру необходимо немало времени, чтобы запросить у системы список установленных приложений, отсортировать его, загрузить в память иконки и другие данные. Поэтому современные лаунчеры делают это асинхронно: отображают интерфейс и с помощью фонового потока заполняют его иконками приложений.
Еще одно узкое место: формирование интерфейса из описания лайотов в XML-файле. Когда вы вызываете метод setContentView() или inflate() объекта LayoutInflater (в коде фрагмента), Android находит нужный лайот в бинарном XML-файле (для эффективности Android Studio упаковывает XML в бинарный формат), читает его, проводит парсинг и на основе полученных данных формирует интерфейс, измеряя и подгоняя элементы интерфейса друг к другу.
Это действительно сложная и дорогая операция. Поэтому необходимо уделить особое внимание оптимизации лайотов: избегать излишней вложенности лайотов друг в друга (например, использовать RelativeLayout вместо вложенных друг в друга LinearLayout), а также разбить сложные описания интерфейса на множество более мелких и загружать их только тогда, когда в этом возникнет необходимость.
Другой вариант — перейти на язык Kotlin и использовать библиотеку Anko. Она позволяет описывать интерфейс прямо в коде, благодаря чему скорость отображения интерфейса возрастает в четыре раза, а вы получаете большую гибкость в управлении логикой формирования интерфейса.
Фризы и проседания FPS
В Android только главный поток приложения имеет право обновлять экран и обрабатывать нажатия на экран. Это значит, что, когда ваше приложение занимается сложной работой в главном потоке, оно не имеет возможности реагировать на нажатия. Для пользователя приложение будет выглядеть зависшим. В данном случае опять же поможет вынос сложных операций в отдельные потоки.Есть, однако, намного более тонкий и неочевидный момент. Android обновляет экран со скоростью 60 FPS. Это значит, что во время показа анимации или промотки списков у него есть всего 16,6 мс на отображение каждого кадра. В большинстве случаев Android справляется с этой работой и не теряет кадров. Но некорректно написанное приложение может затормозить его.
![draw.png](https://xakep.ru/wp-content/uploads/2017/09/138373/draw.png)
Простой пример: RecyclerView — элемент интерфейса, позволяющий создавать чрезвычайно длинные проматываемые списки, которые занимают одинаковое количество памяти вне зависимости от длины самого списка. Такое возможно благодаря переиспользованию одних и тех же наборов элементов интерфейса (ViewHolder) для отображения разных элементов списка. Когда элемент списка скрывается с экрана, его ViewHolder перемещается в кеш и затем используется для отображения следующих элементов списка.
Когда RecyclerView извлекает ViewHolder из кеша, он запускает метод onBindViewHolder() вашего адаптера, чтобы наполнить его данными конкретного элемента списка. И тут происходит интересное: если метод onBindViewHolder() будет делать слишком много работы, RecyclerView не успеет вовремя сформировать следующий элемент для отображения и список начнет тормозить во время промотки.
Еще один пример. К RecyclerView можно подключить кастомный RecyclerView.OnScrollListener(), метод OnScrolled() которого будет вызван при промотке списка. Обычно его используют для динамического скрытия и показа круглой action-кнопки в углу экрана (FAB — Floating Action Button). Но если реализовать в этом методе более сложный код, список опять же будет притормаживать.
Ну и третий пример. Допустим, интерфейс вашего приложения состоит из множества фрагментов, между которыми можно переключаться с помощью бокового меню (Drawer). Кажется, что самый очевидный вариант решения этой задачи — поместить в обработчик нажатия на пункты меню примерно такой код:
Code:
// Переключаем фрагмент
getSupportFragmentManager
.beginTransaction()
.replace(R.id.container, fragment, "fragment")
.commit()
// Закрываем Drawer
drawer.closeDrawer(GravityCompat.START)
Все логично, вот только если вы запустите приложение и протестируешь его, то увидите, что анимация закрытия меню тормозит. Проблема кроется в том, что метод commit() асинхронный, то есть переключение между фрагментами и закрытие меню будут происходить одновременно и смартфон просто не успеет вовремя обработать все операции обновления экрана.
Чтобы избежать этого, переключать фрагмент нужно уже после того, как анимация закрытия меню закончилась. Сделать это можно, подключив к меню кастомный DrawerListener:
Code:
mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override public void onDrawerSlide(View drawerView, float slideOffset) {}
@Override public void onDrawerOpened(View drawerView) {}
@Override public void onDrawerStateChanged(int newState) {}
@Override
public void onDrawerClosed(View drawerView) {
if (mFragmentToSet != null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, mFragmentToSet)
.commit();
mFragmentToSet = null;
}
}
});
Еще один совсем не очевидный момент. Начиная с Android 3.0 рендеринг интерфейса приложений происходит на графическом процессоре. Это значит, что все битмапы, drawable и ресурсы, указанные в теме приложения, выгружаются в память GPU и поэтому доступ к ним происходит очень быстро.
Любой элемент интерфейса, показанный на экране, преобразуется в набор полигонов и GPU-инструкций и поэтому отображается в случае, например, промотки быстро. Так же быстро будет скрываться и показываться View с помощью изменения атрибута visibility (button.setVisibility(View.GONE) и button.setVisibility(View.VISIBLE)).
А вот при изменении View, даже самом минимальном, системе вновь придется пересоздать View с нуля, загружая в GPU новые полигоны и инструкции. Более того, при изменении TextView эта операция станет еще более дорогой, так как Android придется сначала произвести растеризацию шрифта, то есть превратить текст в набор прямоугольных картинок, затем сделать все замеры и сформировать инструкции для GPU. А еще есть операция пересчета положения текущего элемента и других элементов лайота. Все это необходимо учитывать и изменять View только тогда, когда это действительно нужно.
Overdraw — еще одна серьезная проблема. Как мы говорили выше, разбор сложных лайотов с множеством вложенных элементов будет медленным сам по себе, но также он наверняка принесет с собой проблему частой перерисовки экрана.
Представьте, что у вас есть несколько вложенных друг в друга LinearLayout и некоторые из них к тому же со свойством background, то есть они не только вмещают в себя другие элементы интерфейса, но и имеют фон в виде рисунка или заливки цветом. В результате на этапе рендеринга интерфейса GPU будет делать так: заполнит пикселями нужного цвета область, занимаемую корневым LinearLayout, затем заполнит часть экрана, занимаемую вложенным лайотом, другим цветом (или тем же) и так далее. В результате во время рендеринга одного кадра многие пиксели на экране будут обновлены несколько раз. А это не имеет никакого смысла.
Полностью избежать overdraw невозможно. Например, если необходимо отобразить кнопку на красном фоне, тебе все равно придется сначала залить экран красным, а затем повторно изменить пиксели, отображающие кнопку. К тому же Android умеет оптимизировать рендеринг так, чтобы overdraw не происходил (например, если два элемента одинакового размера находятся друг над другом и второй непрозрачен, первый просто не будет отрисован). Однако многое зависит от программиста, который должен всеми силами стараться минимизировать overdraw.
Поможет в этом инструмент отладки наложений. Он встроен в Android и находится здесь: Settings → Developer Options → Debug GPU overdraw → Show overdraw areas. После его включения экран перекрасится в разные цвета, которые означают следующее:
- обычный цвет — одинарное наложение;
- синий — двойное наложение;
- зеленый — тройное;
- красный — четверное и больше.
![overdraw1.png](https://xakep.ru/wp-content/uploads/2017/09/138373/overdraw1.png)
![overdraw2.png](https://xakep.ru/wp-content/uploads/2017/09/138373/overdraw2.png)
Overdraw здорового приложения и overdraw курильщика
Ну и несколько советов:
- Постарайтесь не использовать свойство background в лайотах.
- Сократите количество вложенных лайотов.
- Вставьте в начало кода Activity такую строку: getWindow().setBackgroundDrawable(null);.
- Не используйте прозрачность там, где можно обойтись без нее.
- Используйте инструмент Hierarchy Viewer для анализа иерархии своих лайотов, их отношений друг к другу, оценки скорости рендеринга и расчета размеров.
Systrace
Найти узкие места в относительно простом приложении, которое вы написали за несколько дней, несложно. Достаточно следовать описанным выше правилам. Но когда речь заходит о действительно большом проекте, без специальных инструментов не обойтись.Systrace — один из важнейших инструментов, которые вы должны освоить. Это трассировщик, который позволяет проследить, что происходит на устройстве во время работы приложения. В частности, он наглядно покажет, как происходит отрисовка каждого кадра, какие кадры были отрисованы вовремя, а какие системе пришлось выбросить.
Systrace можно запустить с помощью Android Device Monitor, который, в свою очередь, находится в меню Tools → Android в Android Studio. Открываем Android Device Monitor, дожидаемся, пока он обнаружит смартфон, выбираем приложение и нажимаем кнопку запуска трассировки.
![adm.png](https://xakep.ru/wp-content/uploads/2017/09/138373/adm.png)
В открывшемся окне запуска оставляем все настройки как есть и нажимаем Ok. Трассировка продолжится пять секунд, после чего будет сформирован HTML-файл с данными (в *nix это файл trace.html в домашнем каталоге). Его следует открыть в браузере.
![trace_2.png](https://xakep.ru/wp-content/uploads/2017/09/138373/trace_2.png)
При первом взгляде отчет Systrace вводит в замешательство — огромное количество непонятно что значащих данных. К счастью, большая часть этих данных вам не понадобится, вас интересуют только строки Frames, UI Thread и RenderThread.
Frames показывает обновления экрана. Каждый кадр — это кружок одного из трех цветов: зеленый, желтый, красный. Зеленый означает, что кадр был отрисован за 16,6 мс, желтый и красный — отрисовка кадра заняла больше 16,6 мс, то есть фреймрейт падает. Сразу под строкой Frames находится строка UI Thread, с помощью которой можно проанализировать, какие шаги выполнила система для отображения фрейма.
Кликнув по кругу, вы получите дополнительную информацию о том, почему система потратила на отрисовку фрейма больше времени, чем положено. Возможные ситуации и методы их решения описаны в документации разработчика. Добавим только, что не стоит обращать внимания на проблему Sheduling delay. Обычно она вызвана вовсе не вашим приложением, а самим Android. Особенно часто она появляется на старых и маломощных смартфонах.
Systrace позволяет оценить, на каком этапе произошла задержка отрисовки. Но он не скажет вам, была ли проблема вызвана вашим кодом, и если да, то где конкретно узкое место. Чтобы найти проблему, вывод Systrace можно детализировать, добавив в код приложения маркеры, которые позволят оценить, сколько времени занимает выполнение вашего собственного кода. Пример трассировки метода onBindViewHolder:
Code:
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Trace.beginSection("MyAdapter.onBindViewHolder");
try {
try {
Trace.beginSection("MyAdapter.queryDatabase");
RowItem rowItem = queryDatabase(position);
mDataset.add(rowItem);
} finally {
Trace.endSection();
}
holder.bind(mDataset.get(position));
} finally {
Trace.endSection();
}
}
Есть более простой инструмент трассировки, встроенный в Android. Просто включи опцию Developer options → Profile GPU rendering → On screen as bars, и на экране появится график. По оси X — кадры, по оси Y — столбцы, отображающие длительность отрисовки каждого кадра. Если столбец выше зеленой черты — на отрисовку кадра ушло больше 16,6 мс.
Android Profiler
Это еще один важный инструмент трассировки, позволяющий оценить, сколько времени понадобилось для завершения работы того или иного метода в твоем приложении. Так же как и Systrace, он формирует отчет за определенный промежуток времени, но отчет этот гораздо более низкоуровневый и касается каждого отдельно взятого метода, который был вызван.Запускаем Android Studio, кликаем на Android Profiler внизу экрана, затем на CPU и нажимаем красную круглую кнопку записи вверху экрана, останавливаем запись, когда нужно. Внизу экрана появится окно с отчетом.
![profiler.png](https://xakep.ru/wp-content/uploads/2017/09/138373/profiler.png)
По умолчанию отчет выводится в виде диаграммы, где по оси X отображается время, а по оси Y — вызываемые методы. Оранжевым помечены системные методы (API), зеленым — методы самого приложения, голубым — методы сторонних API, включая Java. На вкладке Flame chart — похожая диаграмма, в которой одинаковые методы объединены. Она удобна тем, что позволяет наглядно оценить, сколько всего времени работал тот или иной метод за весь период трейсинга.
Вкладки Top Down и Bottom Up показывают дерево вызовов методов, включая информацию о затраченном на их выполнение времени:
- Self — время исполнения кода самого метода;
- Children — время исполнения кода всех вызванных им методов;
- Total — сумма Self и Children.