Рендеринг пользовательского интерфейса — это создание кадра из вашего приложения и его отображение на экране. Чтобы обеспечить плавное взаимодействие пользователя с вашим приложением, ваше приложение должно отображать кадры менее чем за 16 мс, чтобы достичь частоты 60 кадров в секунду (fps). Чтобы понять, почему предпочтительнее 60 кадров в секунду, ознакомьтесь со статьей «Модели производительности Android: почему 60 кадров в секунду?» . Если вы пытаетесь добиться 90 фпс, то это окно падает до 11мс, а для 120 фпс — 8мс.
Если вы превысите это окно на 1 мс, это не означает, что кадр отображается с опозданием на 1 мс, но Choreographer
полностью удаляет кадр. Если ваше приложение страдает от медленного рендеринга пользовательского интерфейса, система вынуждена пропускать кадры, и пользователь замечает заикание в вашем приложении. Это называется джанк . На этой странице показано, как диагностировать и устранить зависания.
Если вы разрабатываете игры, не использующие систему View
, то вы обходите Choreographer
. В этом случае библиотека кадровой синхронизации помогает играм OpenGL и Vulkan добиться плавного рендеринга и правильной синхронизации кадров на Android.
Чтобы улучшить качество приложения, Android автоматически отслеживает ваше приложение на наличие нежелательных файлов и отображает информацию на панели мониторинга Android Vitals. Информацию о том, как собираются данные, см. в статье Мониторинг технического качества вашего приложения с помощью Android Vitals .
Определить джанк
Найти в вашем приложении код, вызывающий зависание, может быть сложно. В этом разделе описываются три метода выявления мусора:
Визуальная проверка позволяет вам просмотреть все варианты использования вашего приложения за несколько минут, но она не предоставляет столько деталей, как Systrace. Systrace предоставляет более подробную информацию, но если вы запустите Systrace для всех вариантов использования вашего приложения, вы можете быть завалены таким большим количеством данных, что их будет сложно проанализировать. И визуальный осмотр, и Systrace обнаруживают сбои на вашем локальном устройстве. Если вы не можете воспроизвести зависание на локальных устройствах, вы можете создать собственный мониторинг производительности для измерения определенных частей вашего приложения на устройствах, работающих в полевых условиях.
Визуальный осмотр
Визуальная проверка помогает определить варианты использования, вызывающие зависание. Чтобы выполнить визуальную проверку, откройте приложение и вручную просмотрите различные его части и найдите недостатки в пользовательском интерфейсе.
Вот несколько советов по проведению визуального осмотра:
- Запустите релизную или, по крайней мере, неотлаживаемую версию вашего приложения. Среда выполнения ART отключает несколько важных оптимизаций для поддержки функций отладки, поэтому убедитесь, что вы смотрите на что-то похожее на то, что видит пользователь.
- Включить рендеринг профиля на графическом процессоре . Профиль рендеринга графического процессора отображает на экране полосы, которые дают вам визуальное представление о том, сколько времени требуется для рендеринга кадров окна пользовательского интерфейса относительно контрольного показателя 16 мс на кадр. Каждая полоса имеет цветные компоненты, которые соответствуют этапу конвейера рендеринга, поэтому вы можете видеть, какая часть занимает больше всего времени. Например, если фрейм тратит много времени на обработку ввода, посмотрите на код вашего приложения, который обрабатывает ввод пользователя.
- Запустите компоненты, которые являются распространенными источниками мусора , например
RecyclerView
. - Запустите приложение с холодного старта .
- Запустите приложение на более медленном устройстве, чтобы усугубить проблему.
Когда вы обнаружите варианты использования, вызывающие зависания, у вас может возникнуть хорошее представление о том, что вызывает зависания в вашем приложении. Если вам нужна дополнительная информация, вы можете использовать Systrace для более глубокого изучения причины.
Систраце
Хотя Systrace — это инструмент, который показывает, что делает все устройство, он может быть полезен для выявления ошибок в вашем приложении. Systrace требует минимальных системных затрат, поэтому во время инструментирования вы можете столкнуться с реалистичными зависаниями.
Запишите трассировку с помощью Systrace во время выполнения нестандартного сценария использования на вашем устройстве. Инструкции по использованию Systrace см. в разделе «Захват трассировки системы в командной строке» . Systrace разделена по процессам и потокам. Найдите процесс вашего приложения в Systrace, который выглядит примерно так, как показано на рисунке 1.
Пример Systrace на рисунке 1 содержит следующую информацию для идентификации мусора:
- Systrace показывает, когда рисуется каждый кадр, и кодирует каждый кадр цветом, чтобы подчеркнуть медленное время рендеринга. Это поможет вам более точно обнаружить отдельные неровные кадры, чем визуальный осмотр. Дополнительные сведения см. в разделе Проверка фреймов и оповещений пользовательского интерфейса .
- Systrace обнаруживает проблемы в вашем приложении и отображает оповещения как в отдельных кадрах, так и на панели оповещений . Лучше всего следовать указаниям в предупреждении.
- Части платформы и библиотек Android, такие как
RecyclerView
, содержат маркеры трассировки. Таким образом, временная шкала systrace показывает, когда эти методы выполняются в потоке пользовательского интерфейса и сколько времени они занимают.
После того, как вы посмотрите на выходные данные Systrace, в вашем приложении могут быть методы, которые, как вы подозреваете, вызывают зависания. Например, если временная шкала показывает, что медленный кадр вызван тем, что RecyclerView
занимает много времени, вы можете добавить пользовательские события трассировки в соответствующий код и повторно запустить Systrace для получения дополнительной информации. В новой Systrace временная шкала показывает, когда вызываются методы вашего приложения и сколько времени требуется для их выполнения.
Если Systrace не показывает подробную информацию о том, почему работа потока пользовательского интерфейса занимает много времени, используйте Android CPU Profiler для записи выборочной или инструментированной трассировки метода. Как правило, трассировки методов не подходят для выявления зависаний, поскольку они создают ложноположительные зависания из-за больших накладных расходов и не позволяют определить, когда потоки выполняются, а когда блокируются. Но трассировка методов может помочь вам определить методы в вашем приложении, которые занимают больше всего времени. После определения этих методов добавьте маркеры трассировки и повторно запустите Systrace, чтобы проверить, не вызывают ли эти методы зависания.
Дополнительные сведения см. в разделе Общие сведения о Systrace .
Пользовательский мониторинг производительности
Если вы не можете воспроизвести помехи на локальном устройстве, вы можете встроить в свое приложение специальный мониторинг производительности, чтобы помочь определить источник помех на устройствах в полевых условиях.
Для этого собирайте данные о времени рендеринга кадров из определенных частей вашего приложения с помощью FrameMetricsAggregator
, а затем записывайте и анализируйте данные с помощью Firebase Performance Monitoring .
Дополнительные сведения см. в разделе Начало работы с мониторингом производительности для Android .
Замороженные кадры
Замороженные кадры — это кадры пользовательского интерфейса, рендеринг которых занимает более 700 мс. Это проблема, поскольку ваше приложение зависает и не отвечает на ввод пользователя почти целую секунду во время рендеринга кадра. Мы рекомендуем оптимизировать приложения для рендеринга кадра в течение 16 мс, чтобы обеспечить плавный пользовательский интерфейс. Однако во время запуска приложения или при переходе на другой экран прорисовка начального кадра обычно занимает больше 16 мс, поскольку вашему приложению приходится расширять представления, компоновать экран и выполнять начальную отрисовку с нуля. Вот почему Android отслеживает зависшие кадры отдельно от медленного рендеринга. Ни один кадр в вашем приложении не должен занимать больше 700 мс для рендеринга.
Чтобы помочь вам улучшить качество приложения, Android автоматически проверяет ваше приложение на наличие зависших кадров и отображает информацию на панели управления Android Vitals. Информацию о том, как собираются данные, см. в статье Мониторинг технического качества вашего приложения с помощью Android Vitals .
Замороженные кадры — это крайняя форма медленного рендеринга, поэтому процедура диагностики и устранения проблемы одинакова.
Отслеживание мусора
FrameTimeline в Perfetto может помочь отслеживать медленные или зависшие кадры.
Связь между медленными кадрами, замороженными кадрами и ошибками ANR
Медленные кадры, зависшие кадры и ошибки ANR — это различные формы помех, с которыми может столкнуться ваше приложение. Посмотрите таблицу ниже, чтобы понять разницу.
Медленные кадры | Замороженные кадры | ANR | |
---|---|---|---|
Время рендеринга | Между 16 мс и 700 мс | Между 700 мс и 5 с | Больше 5 секунд |
Видимая зона воздействия пользователя |
|
|
|
Отслеживайте медленные и зависшие кадры отдельно
Во время запуска приложения или при переходе на другой экран прорисовка начального кадра обычно занимает больше 16 мс, поскольку приложение должно расширять представления, компоновать экран и выполнять начальную отрисовку с нуля.
Рекомендации по определению приоритетов и устранению ошибок
При устранении ошибок в приложении помните о следующих рекомендациях:
- Выявите и устраните наиболее легко воспроизводимые случаи зависаний.
- Расставьте приоритеты ANR. Хотя медленные или зависшие кадры могут привести к замедлению работы приложения, ошибки ANR приводят к тому, что приложение перестает отвечать на запросы.
- Медленный рендеринг трудно воспроизвести, но вы можете начать с уничтожения замороженных кадров длительностью 700 мс. Чаще всего это происходит при запуске приложения или смене экранов.
Исправление рывков
Чтобы исправить зависание, проверьте, какие кадры не завершаются за 16 мс, и найдите причину. Проверьте, не занимает ли Record View#draw
или Layout
аномально много времени в некоторых кадрах. См. раздел «Общие источники ошибок», чтобы узнать об этих и других проблемах.
Чтобы избежать зависаний, выполняйте длительные задачи асинхронно вне потока пользовательского интерфейса. Всегда помните, в каком потоке выполняется ваш код, и будьте осторожны при отправке нетривиальных задач в основной поток.
Если у вас сложный и важный основной пользовательский интерфейс вашего приложения, например центральный список прокрутки, рассмотрите возможность написания инструментальных тестов , которые могут автоматически определять медленное время рендеринга и часто запускать тесты, чтобы предотвратить регрессии.
Распространенные источники мусора
В следующих разделах описаны распространенные источники ошибок в приложениях, использующих систему View
, и рекомендации по их устранению. Информацию об устранении проблем с производительностью с помощью Jetpack Compose см. в разделе Производительность Jetpack Compose .
Прокручиваемые списки
ListView
— и особенно RecyclerView
— обычно используются для сложных списков прокрутки, которые наиболее подвержены сбоям. Оба они содержат маркеры Systrace, поэтому вы можете использовать Systrace, чтобы узнать, способствуют ли они сбою в вашем приложении. Передайте аргумент командной строки -a <your-package-name>
чтобы отобразить разделы трассировки в RecyclerView
, а также любые добавленные вами маркеры трассировки. Если возможно, следуйте указаниям предупреждений, созданных в выходных данных Systrace. Внутри Systrace вы можете щелкнуть разделы, отслеживаемые RecyclerView
, чтобы увидеть объяснение работы, которую выполняет RecyclerView
.
RecyclerView: notifyDataSetChanged()
Если вы видите, что каждый элемент в вашем RecyclerView
перенастраивается — и, таким образом, перерисовывается и перерисовывается в одном кадре — убедитесь, что вы не вызываете notifyDataSetChanged()
, setAdapter(Adapter)
или swapAdapter(Adapter, boolean)
для небольших обновления. Эти методы сигнализируют об изменениях всего содержимого списка и отображаются в Systrace как RV FullInvalidate . Вместо этого используйте SortedList
или DiffUtil
для создания минимальных обновлений при изменении или добавлении содержимого.
Например, рассмотрим приложение, которое получает новую версию списка новостного контента с сервера. Когда вы отправляете эту информацию в адаптер, можно вызвать notifyDataSetChanged()
, как показано в следующем примере:
Котлин
fun onNewDataArrived(news: List<News>) { myAdapter.news = news myAdapter.notifyDataSetChanged() }
Ява
void onNewDataArrived(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
Обратной стороной этого является то, что если есть тривиальное изменение, например, один элемент, добавленный вверху, RecyclerView
не узнает. Поэтому ему приказывают удалить все состояние кэшированного элемента и, следовательно, необходимо все пересвязать.
Мы рекомендуем вам использовать DiffUtil
, который вычисляет и отправляет за вас минимальные обновления:
Котлин
fun onNewDataArrived(news: List<News>) { val oldNews = myAdapter.items val result = DiffUtil.calculateDiff(MyCallback(oldNews, news)) myAdapter.news = news result.dispatchUpdatesTo(myAdapter) }
Ява
void onNewDataArrived(List<News> news) { List<News> oldNews = myAdapter.getItems(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); myAdapter.setNews(news); result.dispatchUpdatesTo(myAdapter); }
Чтобы сообщить DiffUtil
как проверять ваши списки, определите MyCallback
как реализацию Callback
.
RecyclerView: вложенные RecyclerViews
Обычно несколько экземпляров RecyclerView
вкладываются друг в друга, особенно с вертикальным списком списков с горизонтальной прокруткой. Примером этого являются сетки приложений на главной странице Play Store. Это может отлично сработать, но при этом вокруг будет перемещаться много просмотров.
Если вы видите, что при первой прокрутке страницы вниз увеличивается множество внутренних элементов, возможно, вам стоит убедиться, что вы используете RecyclerView.RecycledViewPool
совместно с внутренними (горизонтальными) экземплярами RecyclerView
. По умолчанию каждый RecyclerView
имеет собственный пул элементов. Однако в случае с десятком itemViews
на экране одновременно возникает проблема, когда itemViews
не могут совместно использоваться различными горизонтальными списками, если все строки отображают одинаковые типы представлений.
Котлин
class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() { ... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Inflate inner item, find innerRecyclerView by ID. val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false) innerRv.apply { layoutManager = innerLLM recycledViewPool = sharedPool } return OuterAdapter.ViewHolder(innerRv) } ...
Ява
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool(); ... @Override public void onCreateViewHolder(ViewGroup parent, int viewType) { // Inflate inner item, find innerRecyclerView by ID. LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(sharedPool); return new OuterAdapter.ViewHolder(innerRv); } ...
Если вы хотите оптимизировать дальше, вы также можете вызвать setInitialPrefetchItemCount(int)
в LinearLayoutManager
внутреннего RecyclerView
. Если, например, у вас всегда отображается 3,5 элемента подряд, вызовите innerLLM.setInitialItemPrefetchCount(4)
. Это сигнализирует RecyclerView
о том, что когда на экране вот-вот появится горизонтальная строка, он должен попытаться предварительно загрузить элементы внутри, если в потоке пользовательского интерфейса есть свободное время.
RecyclerView: слишком большая инфляция или создание занимает слишком много времени
В большинстве случаев функция предварительной выборки в RecyclerView
может помочь обойти издержки инфляции, выполняя работу заранее, пока поток пользовательского интерфейса простаивает. Если вы видите расширение во время кадра, а не в разделе с надписью RV Prefetch , убедитесь, что вы тестируете на поддерживаемом устройстве и используете последнюю версию библиотеки поддержки . Предварительная выборка поддерживается только в Android 5.0 API уровня 21 и более поздних версиях.
Если вы часто видите, что увеличение количества просмотров приводит к зависаниям при появлении новых элементов на экране, убедитесь, что у вас не больше типов представлений, чем вам нужно. Чем меньше типов представлений в содержимом RecyclerView
, тем меньше требуется раздувания, когда на экране появляются новые типы элементов. Если возможно, объедините типы представлений там, где это целесообразно. Если между типами меняется только значок, цвет или фрагмент текста, вы можете внести это изменение во время привязки и избежать инфляции, что в то же время уменьшает объем памяти вашего приложения.
Если ваши типы представлений выглядят хорошо, подумайте о снижении стоимости инфляции. Может помочь сокращение ненужных контейнерных и структурных представлений. Рассмотрите возможность создания itemViews
с помощью ConstraintLayout
, что может помочь уменьшить структурные представления.
Если вы хотите дополнительно оптимизировать производительность, а иерархия ваших элементов проста и вам не нужны сложные функции тем и стилей, рассмотрите возможность самостоятельного вызова конструкторов. Однако часто не стоит жертвовать потерей простоты и возможностей XML.
RecyclerView: привязка занимает слишком много времени
Привязка — то есть onBindViewHolder(VH, int)
— должна быть простой и занимать гораздо меньше одной миллисекунды для всего, кроме самых сложных элементов. Он должен брать элементы обычного старого объекта Java (POJO) из внутренних данных элементов вашего адаптера и вызывать установщики для представлений в ViewHolder
. Если RV OnBindView занимает много времени, убедитесь, что вы выполняете минимальную работу в коде привязки.
Если вы используете базовые объекты POJO для хранения данных в адаптере, вы можете полностью избежать написания кода привязки в onBindViewHolder
используя библиотеку привязки данных .
RecyclerView или ListView: макет или рисование занимают слишком много времени
О проблемах с отрисовкой и макетом см. разделы «Производительность макета» и «Производительность отрисовки» .
ListView: Инфляция
Вы можете случайно отключить переработку в ListView
если не будете осторожны. Если вы видите инфляцию каждый раз, когда элемент появляется на экране, убедитесь, что ваша реализация Adapter.getView()
размышляет, выполняет повторную привязку и возвращает параметр convertView
. Если ваша реализация getView()
всегда раздувается, ваше приложение не получит преимуществ от переработки в ListView
. Структура вашего getView()
почти всегда должна быть похожа на следующую реализацию:
Котлин
fun getView(position: Int, convertView: View?, parent: ViewGroup): View { return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply { // Bind content from position to convertView. } }
Ява
View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { // Only inflate if no convertView passed. convertView = layoutInflater.inflate(R.layout.my_layout, parent, false) } // Bind content from position to convertView. return convertView; }
Производительность макета
Если Systrace показывает, что сегмент макета Choreographer#doFrame
работает слишком много или работает слишком часто, это означает, что вы столкнулись с проблемами производительности макета. Производительность макета вашего приложения зависит от того, в какой части иерархии представлений меняются параметры или входные данные макета.
Производительность макета: Стоимость
Если сегменты длиннее нескольких миллисекунд, возможно, вы столкнулись с наихудшей производительностью вложения для RelativeLayouts
или weighted-LinearLayouts
. Каждый из этих макетов может запускать несколько проходов измерений и макетов своих дочерних элементов, поэтому их вложение может привести к поведению O(n^2)
на глубине вложенности.
Старайтесь избегать RelativeLayout
или функции веса LinearLayout
во всех узлах иерархии, кроме самых нижних. Это можно сделать следующими способами:
- Реорганизуйте свои структурные представления.
- Определите логику пользовательского макета. Конкретный пример см. в разделе Оптимизация иерархий макетов . Вы можете попробовать перейти на
ConstraintLayout
, который предоставляет аналогичные функции, но без проблем с производительностью.
Производительность макета: Частота
Ожидается, что макет произойдет, когда на экране появится новый контент, например, когда новый элемент прокручивается в поле зрения в RecyclerView
. Если в каждом кадре происходит значительная компоновка, возможно, вы анимируете компоновку, что может привести к потере кадров.
Как правило, анимация должна запускаться на основе свойств рисования View
, таких как следующие:
Вы можете изменить все это гораздо дешевле, чем свойства макета, такие как отступы или поля. Как правило, гораздо дешевле изменить свойства рисования представления, вызвав установщик, который вызывает invalidate()
, за которой следует draw(Canvas)
в следующем кадре. При этом операции рисования перезаписываются для представления, которое становится недействительным, а также, как правило, намного дешевле, чем макет.
Производительность рендеринга
Пользовательский интерфейс Android работает в два этапа:
- Запишите View#draw в потоке пользовательского интерфейса, который запускает
draw(Canvas)
для каждого недействительного представления и может вызывать вызовы в пользовательские представления или в ваш код. - DrawFrame в
RenderThread
, который работает на собственномRenderThread
, но работает на основе работы, созданной на этапе рисования Record View# .
Производительность рендеринга: поток пользовательского интерфейса
Если Record View#draw занимает много времени, растровое изображение обычно рисуется в потоке пользовательского интерфейса. При рисовании в растровое изображение используется рендеринг ЦП, поэтому по возможности избегайте этого. Вы можете использовать трассировку методов с помощью профилировщика ЦП Android, чтобы выяснить, не в этом ли проблема.
Рисование растрового изображения часто выполняется, когда приложение хочет украсить растровое изображение перед его отображением — иногда такое украшение, как добавление закругленных углов:
Котлин
val paint = Paint().apply { isAntiAlias = true } Canvas(roundedOutputBitmap).apply { // Draw a round rect to define the shape: drawRoundRect( 0f, 0f, roundedOutputBitmap.width.toFloat(), roundedOutputBitmap.height.toFloat(), 20f, 20f, paint ) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) // Multiply content on top to make it rounded. drawBitmap(sourceBitmap, 0f, 0f, paint) setBitmap(null) // Now roundedOutputBitmap has sourceBitmap inside, but as a circle. }
Ява
Canvas bitmapCanvas = new Canvas(roundedOutputBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); // Draw a round rect to define the shape: bitmapCanvas.drawRoundRect(0, 0, roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); // Multiply content on top to make it rounded. bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint); bitmapCanvas.setBitmap(null); // Now roundedOutputBitmap has sourceBitmap inside, but as a circle.
Если вы выполняете такую работу в потоке пользовательского интерфейса, вместо этого вы можете выполнить ее в потоке декодирования в фоновом режиме. В некоторых случаях, как в предыдущем примере, вы можете выполнять работу даже во время отрисовки. Итак, если ваш код Drawable
или View
выглядит примерно так:
Котлин
fun setBitmap(bitmap: Bitmap) { mBitmap = bitmap invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawBitmap(mBitmap, null, paint) }
Ява
void setBitmap(Bitmap bitmap) { mBitmap = bitmap; invalidate(); } void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, null, paint); }
Вы можете заменить его на это:
Котлин
fun setBitmap(bitmap: Bitmap) { shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint) }
Ява
void setBitmap(Bitmap bitmap) { shaderPaint.setShader( new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); invalidate(); } void onDraw(Canvas canvas) { canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint); }
Вы также можете сделать это для защиты фона, например, при рисовании градиента поверх растрового изображения и фильтрации изображений с помощью ColorMatrixColorFilter
— двух других распространенных операций, выполняющих изменение растровых изображений.
Если вы рисуете в растровом изображении по другой причине — возможно, используете его в качестве кеша — попробуйте рисовать в Canvas
с аппаратным ускорением, переданном непосредственно в ваш View
или Drawable
. При необходимости также рассмотрите возможность вызова setLayerType()
с LAYER_TYPE_HARDWARE
для кэширования сложных результатов рендеринга и по-прежнему использовать преимущества рендеринга с помощью графического процессора.
Производительность рендеринга: RenderThread
Некоторые операции Canvas
записываются дешево, но вызывают дорогостоящие вычисления в RenderThread
. Systrace обычно вызывает такие оповещения.
Анимация больших путей
Когда Canvas.drawPath()
вызывается на Canvas
с аппаратным ускорением, переданном в View
, Android сначала рисует эти пути на процессоре и загружает их в графический процессор. Если у вас большие пути, избегайте их редактирования от кадра к кадру, чтобы их можно было кэшировать и эффективно отрисовывать. drawPoints()
, drawLines()
и drawRect/Circle/Oval/RoundRect()
более эффективны и их лучше использовать, даже если вы используете больше вызовов отрисовки.
Canvas.clipPath
clipPath(Path)
вызывает дорогостоящее отсечение, и его обычно следует избегать. По возможности выбирайте рисование фигур вместо обрезки непрямоугольников. Он работает лучше и поддерживает сглаживание. Например, следующий вызов clipPath
можно выразить по-другому:
Котлин
canvas.apply { save() clipPath(circlePath) drawBitmap(bitmap, 0f, 0f, paint) restore() }
Ява
canvas.save(); canvas.clipPath(circlePath); canvas.drawBitmap(bitmap, 0f, 0f, paint); canvas.restore();
Вместо этого сформулируйте предыдущий пример следующим образом:
Котлин
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) // At draw time: canvas.drawPath(circlePath, mPaint)
Ява
// One time init: paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); // At draw time: canvas.drawPath(circlePath, mPaint);
Загрузка растровых изображений
Android отображает растровые изображения как текстуры OpenGL, и при первом отображении растрового изображения в кадре оно загружается в графический процессор. Вы можете увидеть это в Systrace как загрузку текстуры (id) ширина x высота . Это может занять несколько миллисекунд, как показано на рисунке 2, но изображение необходимо отобразить с помощью графического процессора.
Если это занимает много времени, сначала проверьте значения ширины и высоты трассировки. Убедитесь, что отображаемое растровое изображение не значительно больше области экрана, в которой оно отображается. Если это так, это приведет к пустой трате времени и памяти на загрузку. Как правило, библиотеки загрузки растровых изображений предоставляют средства запроса растрового изображения соответствующего размера.
В Android 7.0 код загрузки растрового изображения, обычно выполняемый библиотеками, может вызывать prepareToDraw()
, чтобы инициировать раннюю загрузку до того, как она понадобится. Таким образом, загрузка происходит раньше, пока RenderThread
простаивает. Вы можете сделать это после декодирования или при привязке растрового изображения к представлению, если вы знаете растровое изображение. В идеале ваша библиотека загрузки растровых изображений делает это за вас, но если вы управляете своей собственной или хотите убедиться, что вы не выполняете загрузку на более новых устройствах, вы можете вызвать prepareToDraw()
в своем собственном коде.
Задержки планирования потоков
Планировщик потоков — это часть операционной системы Android, отвечающая за принятие решения о том, какие потоки в системе должны выполняться, когда и как долго.
Иногда зависание происходит из-за того, что поток пользовательского интерфейса вашего приложения заблокирован или не работает. Systrace использует разные цвета, как показано на рисунке 3, чтобы указать, когда поток находится в спящем режиме (серый), работоспособен (синий: он может выполняться, но еще не выбран планировщиком для запуска), активно работает (зеленый) или в непрерывном сне (красный или оранжевый). Это чрезвычайно полезно для отладки проблем с задержкой, вызванных задержками планирования потоков.
Часто вызовы связывателей — механизма межпроцессного взаимодействия (IPC) в Android — вызывают длительные паузы в выполнении вашего приложения. В более поздних версиях Android это одна из наиболее распространенных причин прекращения работы потока пользовательского интерфейса. Как правило, исправление заключается в том, чтобы избегать вызова функций, которые выполняют вызовы связующих. Если это неизбежно, кэшируйте значение или переместите работу в фоновые потоки. По мере того, как базы кода становятся больше, вы можете случайно добавить вызов связующего, вызвав какой-либо низкоуровневый метод, если не будете осторожны. Однако их можно найти и исправить с помощью трассировки.
Если у вас есть транзакции связывания, вы можете захватить их стеки вызовов с помощью следующих команд adb
:
$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt
Иногда вызовы, которые кажутся безобидными, например getRefreshRate()
, могут запускать транзакции связывания и вызывать большие проблемы при частом вызове. Периодическое отслеживание может помочь вам обнаружить и устранить эти проблемы по мере их появления.
Если вы не видите активность связывателя, но по-прежнему не видите выполнение потока пользовательского интерфейса, убедитесь, что вы не ожидаете блокировки или другой операции из другого потока. Обычно потоку пользовательского интерфейса не приходится ждать результатов от других потоков. Другие темы должны публиковать в нем информацию.
Распределение объектов и сборка мусора
Распределение объектов и сбор мусора (GC) представляют собой значительно меньшую проблему, поскольку ART был введен в качестве среды выполнения по умолчанию в Android 5.0, но все же можно утяжелить потоки этой дополнительной работой. Выделение ресурсов в ответ на редкое событие, которое происходит не часто в секунду, например, когда пользователь нажимает кнопку, — это нормально, но помните, что за каждое распределение приходится платить. Если это тесный цикл, который часто вызывается, рассмотрите возможность избежать выделения, чтобы снизить нагрузку на сборщик мусора.
Systrace показывает, часто ли выполняется сборщик мусора, а профилировщик памяти Android может показать, откуда поступают выделения. Если вы по возможности избегаете выделения памяти, особенно в тесных циклах, у вас меньше шансов столкнуться с проблемами.
В последних версиях Android сборщик мусора обычно работает в фоновом потоке с именем HeapTaskDaemon . Значительные объемы выделения могут означать, что на сборщик мусора будет потрачено больше ресурсов ЦП, как показано на рисунке 5.
{% дословно %}Рекомендуется для вас
- Примечание. Текст ссылки отображается, когда JavaScript отключен.
- Протестируйте свое приложение
- Обзор измерения производительности приложений
- Рекомендации по оптимизации приложений