Pull to refresh

О производительности Android-приложений

Reading time 5 min
Views 37K

Введение


Первое, что я хочу сказать: статья не претендует на сильно глубокий уровень, скорее я хочу рассказать о том, что производительность это не только «быстрее с 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

Эта утилита показывает всю иерархию вьюх в приложении, и показывает насколько каждая вью производительнее (относительно остальных). Выглядит это примерно так:

image

Что показывают цветные точки

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

Это можно и нужно использовать, если у вас часть разметки становится видимой только в каких-то случаях или по нажатию на какую-то кнопку. То есть, это что-то вроде ленивой инициализации.

Пишите быстрые приложения, коллеги.
Tags:
Hubs:
+34
Comments 10
Comments Comments 10

Articles