Введение
Первое, что я хочу сказать: статья не претендует на сильно глубокий уровень, скорее я хочу рассказать о том, что производительность это не только «быстрее с NDK на С++» и «экономьте память, а то сборка мусора будет часто запускаться», а это целый комплекс мер, потому что проблемы с производительностью возникают не когда одна функция медленно работает, а в комплексе.
Не было ли у вас ощущения, что приложение тормозит, а вы уже не знаете что делать — и память вроде не жрет, и профайлером уже посмотрели, а решения все нет. Если да, то эти заметки для вас.
Понятия и термины я переводить не буду, так как я думаю что почти все разработчики их не переводят.
Ссылки (да, ссылки вначале)
Все что я описываю здесь я почерпнул из:
1. Курс по производительность от Google (рекомендую);
2. developer.android.com (если вы сюда не заглядывали, то у меня для вас плохие новости).
Overdraw
Как выглядит процесс превращения вашей xml-разметки в то, что вы видите на экране? Это довольно сложный процесс, но сейчас нас интересует одна деталь. На одном из этапов происходит растроризация (rasterization). Это тот самый процесс, когда высокоуровневые объекты вроде шрифтов и кружков делятся на пиксели.
Представьте себе простой пример:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
....
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000000">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
android:text="Hello, habrahabr!" />
</LinearLayout>
</LinearLayout>
У нас есть TextView, которая лежит в LinearLayout, который тоже лежит в LinearLayout. И у каждого из них есть свой background. Это значит, что когда будет рисоваться TextView, оно будет рисоваться по уже нарисованному LinearLayout, часть которого тоже была нарисовано по уже нарисованному. Это и называется overdraw. Возможно, само по себе это не так критично, но говорит о том, что у вас есть проблемы, например, с иерархией.
Для того, чтобы найти overdraw, нужно поставить галочку в настройках Android: Settings -> Developer Options ->Debug GPU Overdraw
И ваш телефон сразу преобразится!
Все сразу окрашивается в разные красивые цвета:
Синий цвет — все в порядке
Зеленый — 2x overdraw
Светлокрасный — 3x overdraw
Красный — 4x и более
Вот так, например, выгдядит Gmail:
А вот так знакомое многим приложение Vivino:
Видно, у кого есть проблемы, а у кого нет.
Первое, что можно сделать чтобы избавить от overdraw, это убрать лишние определения background. Например:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:background="#ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
....
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
android:text="Hello, habrahabr!" />
</LinearLayout>
</LinearLayout>
И, еще можно написать в Activity:
@Override
public void onCreate(Bundle savedInstanceState) {
getWindow().setBackgroundDrawable(null);
...
}
Иерархия
Построение дерева вьюх — весьма дорогой процесс. И общая рекомендация здесь очень проста — чем проще, тем лучше. Плоская разметка — идеально.
Чтобы найти проблемные места в вашем приложении, можно использовать утилиту monitor, которая входит в набор Android SDK. В нем есть вид "Hierarchy View", и это то, что нам нужно.
* о том как настроить и подробности читайте здесь: http://developer.android.com/training/improving-layouts/optimizing-layout.html
и здесь: http://developer.android.com/tools/debugging/debugging-ui.html
Эта утилита показывает всю иерархию вьюх в приложении, и показывает насколько каждая вью производительнее (относительно остальных). Выглядит это примерно так:
Что показывают цветные точки
monitor меряет скорость отрисовки вьюх для каждой фазы отрисовки Activity:
Левая — Draw
Средняя — Layout
Правая — Measure
Что касается цвета, то:
Зеленый означает, что вью рендерится быстрее по крайней мере половины соседних вьюх.
Yellow означает, что вью рендерится медленнее по крайней мере половины соседних вьюх.
Красный говорит о том, что
Как трактовать эти точки?
Если у вас все красное у «крайних» вью (те, что листы дерева), то на них точно стоит смотреть.
Если у вас ViewGroup с большим количество детей, и у них точка показывающая measure красная, то тут точно надо смотреть на иерархию.
В нашем примере, ее можно было бы сделать плоской достаточно легко:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/my_layout"
android:background="#ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
....
</LinearLayout>
<TextView
android:layout_below="@+id/my_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff0000"
android:text="Hello, habrahabr!" />
</RelativeLayout>
ViewStub
ViewStub, это вью, которая является простым контейнером и может содержать в себе только одного ребенка:
<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />
Но вся штука в том, что layout, который содержится в ViewStub, будет создан только, если ViewStub видима, или у нее напрямую вызван метод inflate().
Подробнее: http://developer.android.com/reference/android/view/ViewStub.html
Это можно и нужно использовать, если у вас часть разметки становится видимой только в каких-то случаях или по нажатию на какую-то кнопку. То есть, это что-то вроде ленивой инициализации.
Пишите быстрые приложения, коллеги.
Комментарии (10)
forceLain
17.04.2015 17:44+2Спасибо за статью, я всё никак не мог понять, по какому принципу монитор мне окрашивал эти точки. Ну т.е. понятно, что красный цвет это плохо, но как он определял, что такое-то время measure/layout/draw это уже много было загадкой :)
От себя бы добавил:
1. Если нужно заполнить место каким то холдером/пустым местом, есть специальная вьюшка Space, которая участвует в процессах measure/layout, но ничего не рисует, тем самым не отнимает ресурсов.
2. Бывают случаи, когда нужно нарисовать сложный, но не очень изменяемый layout и в таких случаях есть смысл подумать: «а не написать ли мне свой ViewGroup с легким и быстром measure/layout/draw процессом»
kaftanati
17.04.2015 18:28Большое спасибо за наводку на: «Debug GPU Overdraw/Отладка превышения GPU». Надеюсь на продолжение.
gurinderu
18.04.2015 11:37+1Не понимаю я утверждения, что ndk быстрее. Для чего быстрее? Для обсчета какой-нибудь трудоемкой задачи да, а для всего явно нет. Не забывайте что jni call медленный
stalkerg
21.04.2015 10:50Можно GUI и на NDK писать. Есть лёгкие варианты и есть монстрики типо Qt.
gurinderu
21.04.2015 12:20Конечно можно. Напрямую делая колы через opengl. Только вот зачем? Для обычного приложения потратить годы на разработку, по пути наделав кучу багов. Для игр это только нужно.
stalkerg
22.04.2015 12:32Ну есть смежные с играми области где это может быть нужно. Я скорее говорил о возможности, а не обязательности.
anton9088
18.04.2015 18:10Еще можно добавить остальные warning'и, которые кидает Lint в плане производительности
forceLain
Это не всегда так. К примеру, RelativeLayout'у приходится выполнять measure-процесс 2 раза и если он содержит детей со сложным measure-подсчетом, то он может оказаться медленнее, чем несколько других более «простых» ViewGroup.
forceLain
Кстати, та же история с множественным measure и у LinearLayout, если рисовать её детей с помощью параметра weight