Отрисовка пользовательского интерфейса – это процесс создания кадра из приложения и его отображения на экране. Чтобы приложение работало плавно, оно должно отрисовывать кадры менее чем за 16 мс. Это позволит достичь частоты 60 кадров/с. Чтобы узнать, почему предпочтительна частота 60 кадров/с, посмотрите видео Android Performance Patterns: Why 60fps? (на английском языке). Если вы хотите достичь 90 кадров/с, то это окно сокращается до 11 мс, а для 120 кадров/с – до 8 мс.
Если вы превысите это окно на 1 мс, это не значит, что кадр будет показан на 1 мс позже. Choreographer полностью пропустит кадр. Если отрисовка интерфейса в приложении происходит медленно, система вынуждена пропускать кадры, и пользователь видит задержки. Это называется помехами. На этой странице показано, как диагностировать и устранять задержки.
Если вы разрабатываете игры, в которых не используется система View, то Choreographer вам не нужен. В этом случае библиотека синхронизации кадров помогает играм на базе OpenGL и Vulkan добиться плавного рендеринга и правильной синхронизации кадров на устройствах Android.
Как выявить задержки
Найти в приложении код, который вызывает задержки, может быть непросто. В этом разделе описываются три метода выявления задержек:
Визуальный осмотр позволяет за несколько минут проверить все сценарии использования приложения, но не дает таких подробных данных, как Systrace. Systrace предоставляет больше информации, но если вы запустите его для всех сценариев использования в приложении, то можете получить слишком много данных, которые будет сложно анализировать. Визуальная проверка и Systrace позволяют обнаружить временные зависания на локальном устройстве. Если вам не удается воспроизвести задержки на локальных устройствах, вы можете создать собственную систему мониторинга производительности, чтобы измерить показатели определенных частей приложения на устройствах, работающих в полевых условиях.
Визуальный осмотр
Визуальный осмотр помогает определить сценарии использования, которые приводят к задержкам. Чтобы выполнить визуальную проверку, откройте приложение и вручную пройдитесь по его различным частям, обращая внимание на задержки в интерфейсе.
Вот несколько советов по проведению визуальных проверок:
- Запустите версию приложения, предназначенную для выпуска, или хотя бы версию, не предназначенную для отладки. Среда выполнения ART отключает несколько важных оптимизаций для поддержки функций отладки, поэтому убедитесь, что вы видите то же, что и пользователь.
- Включите профилирование графического процессора. Профилирование отрисовки графическим процессором позволяет визуализировать время, необходимое для отрисовки кадров окна пользовательского интерфейса, по сравнению с контрольным значением 16 мс на кадр. Каждая полоса состоит из цветных компонентов, соответствующих этапам конвейера рендеринга, поэтому вы можете увидеть, какая часть занимает больше всего времени. Например, если кадр тратит много времени на обработку ввода, посмотрите на код приложения, который обрабатывает пользовательский ввод.
- Проверьте компоненты, которые часто вызывают задержки, например
RecyclerView. - Выполните холодный запуск приложения.
- Запустите приложение на более медленном устройстве, чтобы усугубить проблему.
Когда вы найдете сценарии использования, которые вызывают задержки, вы сможете понять, что именно их вызывает. Если вам нужна дополнительная информация, вы можете использовать Systrace, чтобы подробнее изучить причину.
Systrace
Хотя Systrace показывает, что происходит на всем устройстве, этот инструмент может быть полезен для выявления задержек в приложении. Systrace оказывает минимальную нагрузку на систему, поэтому вы можете увидеть реалистичные задержки во время инструментирования.
Запишите трассировку с помощью Systrace, выполняя на устройстве действия, которые приводят к задержкам. Инструкции по использованию Systrace приведены в статье Как выполнить трассировку системы в командной строке. Systrace разделен по процессам и потокам. Найдите процесс приложения в Systrace. Он выглядит примерно так, как на рисунке 1.
Пример Systrace на рисунке 1 содержит следующую информацию для выявления задержек:
- Systrace показывает, когда отрисовывается каждый кадр, и выделяет цветом кадры с медленной отрисовкой. Это помогает находить отдельные кадры с задержкой точнее, чем при визуальном осмотре. Подробнее о том, как проверять фреймы и оповещения интерфейса…
- Systrace обнаруживает проблемы в приложении и показывает оповещения как в отдельных кадрах, так и на панели оповещений. Рекомендуем следовать инструкциям в оповещении.
- Некоторые части фреймворка и библиотек Android, например
RecyclerView, содержат маркеры трассировки. Таким образом, временная шкала Systrace показывает, когда эти методы выполняются в потоке UI и сколько времени занимает их выполнение.
После просмотра выходных данных Systrace вы можете обнаружить в своем приложении методы, которые, по вашему мнению, вызывают задержки. Например, если на временной шкале видно, что медленный кадр вызван длительным выполнением RecyclerView, вы можете добавить пользовательские события трассировки в соответствующий код и повторно запустить Systrace, чтобы получить дополнительную информацию. На временной шкале в новом Systrace показано, когда вызываются методы приложения и сколько времени занимает их выполнение.
Если Systrace не показывает, почему поток UI работает медленно, используйте Профилировщик ЦП Android, чтобы записать трассировку метода. Как правило, трассировки методов не подходят для выявления задержек, поскольку они создают ложноположительные задержки из-за большой нагрузки и не могут определить, когда потоки выполняются, а когда заблокированы. Однако трассировка методов позволяет определить, какие методы в приложении занимают больше всего времени. После этого добавьте маркеры трассировки и снова запустите Systrace, чтобы проверить, не вызывают ли эти методы задержки.
Подробнее о Systrace…
Отслеживание эффективности
Если вам не удается воспроизвести задержки на локальном устройстве, вы можете встроить в приложение собственный инструмент мониторинга производительности, чтобы выявить источник задержек на устройствах пользователей.
Для этого соберите данные о времени отрисовки кадров в определенных частях приложения с помощью FrameMetricsAggregator, а затем запишите и проанализируйте их в Firebase Performance Monitoring.
Подробнее о том, как начать работу с Performance Monitoring для Android…
Зависающие кадры
Зависающими считаются кадры, на отрисовку которых уходит более 700 мс. Это проблема, поскольку приложение кажется зависшим и не реагирует на действия пользователя в течение почти целой секунды, пока выполняется рендеринг кадра. Чтобы интерфейс работал плавно, рекомендуем оптимизировать приложения так, чтобы кадр отрисовывался в течение 16 мс. Однако при запуске приложения или переходе на другой экран начальный кадр может отрисовываться дольше 16 мс, поскольку приложению необходимо с нуля загрузить представления, разместить элементы на экране и выполнить начальную отрисовку. Поэтому в Android зависшие кадры отслеживаются отдельно от медленной отрисовки. Время отрисовки кадров не должно превышать 700 мс.
Зависание кадров – это крайняя форма медленной отрисовки, поэтому процедура диагностики и устранения проблемы такая же.
Отслеживание задержек
FrameTimeline в Perfetto помогает отслеживать медленные и зависшие кадры.
Связь между медленно формирующимися кадрами, зависанием кадров и ошибками ANR
Медленные и зависшие кадры, а также ошибки ANR – это разные формы задержек, которые могут возникать в приложении. Различия между ними описаны в таблице ниже.
| Медленные кадры | Зависающие кадры | ANR | |
|---|---|---|---|
| Время отрисовки | От 16 до 700 мс | От 700 мс до 5 с | Более 5 секунд |
| Область видимого воздействия на пользователя |
|
|
|
Отслеживайте медленно формирующиеся и зависшие кадры по отдельности
При запуске приложения или переходе на другой экран начальный кадр может отрисовываться дольше 16 мс. Это нормально, поскольку приложению нужно развернуть представления, разместить экран и выполнить начальную отрисовку с нуля.
Рекомендации по приоритизации и устранению задержек
Чтобы устранить задержки в приложении, следуйте приведенным ниже рекомендациям.
- Выявите и устраните наиболее легко воспроизводимые случаи задержек.
- Расставляйте приоритеты. Если медленные или зависшие кадры могут создать впечатление, что приложение работает медленно, то ошибки ANR приводят к тому, что оно перестает отвечать.
- Медленную отрисовку сложно воспроизвести, но вы можете начать с устранения зависших кадров длительностью 700 мс. Чаще всего это происходит при запуске приложения или переходе между экранами.
Устранение задержек
Чтобы устранить задержки, проверьте, какие кадры не завершаются за 16 мс, и найдите причину. Проверьте, не занимает ли Record View#draw или Layout слишком много времени в некоторых кадрах. Подробнее о распространенных причинах задержек…
Чтобы избежать задержек, выполняйте длительные задачи асинхронно вне потока UI. Всегда следите за тем, в каком потоке выполняется ваш код, и будьте осторожны, когда отправляете нетривиальные задачи в основной поток.
Если в вашем приложении есть сложный и важный основной интерфейс, например центральный прокручиваемый список, рекомендуем написать инструментальные тесты, которые могут автоматически обнаруживать медленную отрисовку. Чтобы предотвратить регрессию, запускайте эти тесты регулярно.
Распространенные причины задержек
В следующих разделах описаны распространенные причины задержек в приложениях, использующих систему 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(), как показано в следующем примере:
Kotlin
fun onNewDataArrived(news: List<News>) { myAdapter.news = news myAdapter.notifyDataSetChanged() }
Java
void onNewDataArrived(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
Недостаток такого подхода в том, что если в список будет добавлен всего один элемент, RecyclerView не узнает об этом. Поэтому ему нужно удалить все кешированные состояния элементов и заново привязать все элементы.
Рекомендуем использовать DiffUtil, который рассчитывает и отправляет минимальные обновления:
Kotlin
fun onNewDataArrived(news: List<News>) { val oldNews = myAdapter.items val result = DiffUtil.calculateDiff(MyCallback(oldNews, news)) myAdapter.news = news result.dispatchUpdatesTo(myAdapter) }
Java
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: вложенные RecyclerView
Часто используется несколько вложенных экземпляров RecyclerView, особенно в вертикальном списке горизонтально прокручиваемых списков. Примером этого являются сетки приложений на главной странице Google Play. Это может отлично работать, но это также много перемещающихся представлений.
Если при первой прокрутке страницы вниз вы видите, что многие внутренние элементы увеличиваются, проверьте, используется ли RecyclerView.RecycledViewPool для внутренних (горизонтальных) экземпляров RecyclerView. По умолчанию у каждого RecyclerView свой набор объектов. Однако если на экране одновременно отображается десяток itemViews, то могут возникнуть проблемы, когда itemViews не может использоваться разными горизонтальными списками, если все строки показывают похожие типы представлений.
Kotlin
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) } ...
Java
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, что когда горизонтальная строка должна появиться на экране, он должен попытаться предварительно загрузить элементы внутри, если в потоке UI есть свободное время.
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() почти всегда должна быть похожа на следующую реализацию:
Kotlin
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. } }
Java
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 показывает, что сегмент Layout метода Choreographer#doFrame работает слишком долго или слишком часто, это означает, что у вас возникли проблемы с производительностью макета. Эффективность макета приложения зависит от того, какая часть иерархии View имеет изменяющиеся параметры или входные данные макета.
Эффективность макета: стоимость
Если сегменты длиннее нескольких миллисекунд, возможно, вы столкнулись с наихудшей производительностью вложенности для RelativeLayouts или weighted-LinearLayouts. Каждый из этих макетов может запускать несколько проходов измерения и макета своих дочерних элементов, поэтому их вложение может привести к поведению O(n^2) на глубине вложения.
Старайтесь не использовать RelativeLayout или функцию веса LinearLayout нигде, кроме самых нижних дочерних узлов иерархии. Вот как это можно сделать:
- Реорганизуйте структурные представления.
- Определите логику специального макета. Пример приведен в статье Оптимизация иерархий макетов. Вы можете перейти на
ConstraintLayout, который предоставляет аналогичные функции без снижения производительности.
Эффективность макета: частота
Макет должен меняться, когда на экране появляется новый контент, например когда в поле зрения в RecyclerView попадает новый элемент. Если в каждом кадре происходит значительный макет, возможно, вы анимируете макет, что может привести к потере кадров.
Как правило, анимация должна выполняться для свойств рисования View, таких как:
Изменение этих свойств требует гораздо меньше ресурсов, чем изменение свойств макета, таких как отступы или поля. Как правило, гораздо проще изменить свойства рисования представления, вызвав метод setter, который запускает invalidate(), а затем draw(Canvas) в следующем кадре. При этом заново записываются операции рисования для представления, которое становится недействительным. Обычно это требует гораздо меньше ресурсов, чем макет.
Производительность рендеринга
Интерфейс Android работает в два этапа:
- Запись View#draw в потоке UI, которая запускает
draw(Canvas)для каждого недействительного представления и может вызывать пользовательские представления или ваш код. - DrawFrame в
RenderThread. Выполняется в нативномRenderThread, но работает на основе данных, сгенерированных на этапе Record View#draw.
Производительность отрисовки: поток UI
Если Record View#draw занимает много времени, то, скорее всего, в потоке UI отрисовывается битовая карта. Отрисовка растрового изображения выполняется на ЦП, поэтому по возможности избегайте этого. Чтобы проверить, так ли это, можно использовать трассировку методов с помощью Профилировщика ЦП Android.
Отрисовка на битовой карте часто выполняется, когда приложение хочет украсить битовую карту перед ее отображением, например добавить закругленные углы:
Kotlin
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. }
Java
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.
Если вы выполняете такую работу в потоке UI, вы можете вместо этого выполнять ее в потоке декодирования в фоновом режиме. В некоторых случаях, как в предыдущем примере, вы даже можете выполнить работу во время отрисовки. Если код Drawable или View выглядит примерно так:
Kotlin
fun setBitmap(bitmap: Bitmap) { mBitmap = bitmap invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawBitmap(mBitmap, null, paint) }
Java
void setBitmap(Bitmap bitmap) { mBitmap = bitmap; invalidate(); } void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, null, paint); }
Вы можете заменить его на следующий:
Kotlin
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) }
Java
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 можно записать по-разному:
Kotlin
canvas.apply { save() clipPath(circlePath) drawBitmap(bitmap, 0f, 0f, paint) restore() }
Java
canvas.save(); canvas.clipPath(circlePath); canvas.drawBitmap(bitmap, 0f, 0f, paint); canvas.restore();
Вместо этого используйте следующий вариант:
Kotlin
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) // At draw time: canvas.drawPath(circlePath, mPaint)
Java
// One time init: paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); // At draw time: canvas.drawPath(circlePath, mPaint);
Загрузка растровых изображений
Android отображает растровые изображения как текстуры OpenGL, и когда растровое изображение впервые отображается в кадре, оно загружается в графический процессор. В Systrace это выглядит как Texture upload(id) width x height. Это может занять несколько миллисекунд, как показано на рисунке 2, но это необходимо для отображения изображения с помощью графического процессора.
Если это занимает много времени, сначала проверьте значения ширины и высоты в трассировке. Убедитесь, что размер отображаемого растрового изображения не превышает размер области экрана, в которой оно показывается. Если это так, то вы потратите время и память впустую. Как правило, библиотеки загрузки растровых изображений предоставляют средства для запроса растрового изображения соответствующего размера.
В Android 7.0 код загрузки растровых изображений (обычно выполняется библиотеками) может вызывать prepareToDraw(), чтобы запустить раннюю загрузку до того, как она потребуется. Таким образом, загрузка происходит раньше, пока RenderThread простаивает. Это можно сделать после декодирования или при привязке растрового изображения к представлению, если вы знаете растровое изображение. В идеале это должна делать библиотека загрузки растровых изображений, но если вы управляете собственной библиотекой или хотите избежать загрузок на новых устройствах, вы можете вызвать prepareToDraw() в своем коде.
prepareToDraw().Задержки планирования потоков
Планировщик потоков – это часть операционной системы Android, которая определяет, какие потоки должны выполняться, когда и как долго.
Иногда задержки возникают из-за того, что поток UI приложения заблокирован или не запущен. В Systrace используются разные цвета, как показано на рисунке 3, чтобы указать, когда поток спит (серый), может быть запущен (синий: он может работать, но ещё не выбран планировщиком для запуска), активно работает (зеленый) или находится в непрерываемом спящем режиме (красный или оранжевый). Это очень полезно для отладки проблем с задержками, вызванными задержками планирования потоков.
Часто вызовы связывателя – механизма межпроцессного взаимодействия (IPC) в Android – вызывают длительные паузы в работе приложения. В более поздних версиях Android это одна из самых распространенных причин остановки потока UI. Как правило, чтобы устранить проблему, нужно избегать вызова функций, которые выполняют вызовы связывателя. Если этого не избежать, кешируйте значение или перенесите работу в фоновые потоки. По мере роста базы кода вы можете случайно добавить вызов связывателя, вызвав низкоуровневый метод. Однако вы можете найти и устранить их с помощью трассировки.
Если у вас есть транзакции Binder, вы можете получить их стеки вызовов с помощью следующих команд 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(), могут запускать транзакции Binder и вызывать серьезные проблемы, если они вызываются часто. Периодическое отслеживание поможет вам находить и устранять эти проблемы по мере их появления.
trace-ipc.Если вы не видите активности связывателя, но поток UI по-прежнему не выполняется, убедитесь, что вы не ожидаете блокировки или другой операции из другого потока. Обычно потоку UI не нужно ждать результатов от других потоков. Другие потоки должны публиковать в нем информацию.
Выделение памяти под объекты и сборка мусора
С тех пор как в Android 5.0 в качестве среды выполнения по умолчанию стала использоваться ART, выделение памяти под объекты и сборка мусора стали гораздо менее серьезной проблемой, но они по-прежнему могут перегружать потоки. Выделять память в ответ на редкое событие, которое не происходит много раз в секунду (например, нажатие кнопки пользователем), вполне нормально, но помните, что каждое выделение памяти имеет свою цену. Если это происходит в цикле, который часто вызывается, попробуйте избежать выделения памяти, чтобы снизить нагрузку на сборщик мусора.
Systrace показывает, часто ли запускается сборщик мусора, а Android Memory Profiler – откуда поступают выделения. Если вы будете избегать выделения памяти, когда это возможно, особенно в узких циклах, у вас будет меньше проблем.
В последних версиях Android сборка мусора обычно выполняется в фоновом потоке с именем HeapTaskDaemon. Значительные объемы выделения могут означать, что на сборку мусора тратится больше ресурсов ЦП, как показано на рисунке 5.
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Как сравнивать показатели приложения
- Общие сведения об оценке производительности приложений
- Рекомендации по оптимизации приложений