Андроид
Как написать приложение, которое не
              тормозит

       Максим Ушаков, Google
Обзор

 Руки прочь от UI Thread!
 Немного об SQLite
 Память и GC:
   Нативная куча
   Строки/массивы/массивы объектов
   Страшные сказки на ночь
 Как разобраться с памятью?
 Что делать, если всё-таки виноват CPU?
Руки прочь от UI Thread!
"ANR" получает программа, которая

 за 5 секунд не ответила на действие
 пользователя
 или за 10 секунд не закончился
 BroadcastReceiver

Но даже раньше этого пользователи
жалуются, что программа "тормозит"
"ANR" обычно получает тот, кто

  Делает какую-то существенную работу,
  Читает или пишет файлы,
  Обращается к сети,
  или просто общается с базой данных

в основном потоке программы
(Main/UI Thread)
Не делайте этого
Что выполняется "медленно"

Очевидные вещи, вроде увеличения всех
чисел в базе данных на 1, взлома шифра
RSA, и т.д.
Но и менее очевидные:
  Запись на флеш: 5-200(!)мс
  Сеть: пинг = 100мс--10с--больше (GPRS)
  Соединение+HTTP+6k = 1-6с (3G)
  Garbage collector run -- ?мс
Как отложить работу на потом

 android.os.AsyncTask
 android.app.IntentService
 подробнее:
 http://developer.android.com/reference
Как показать это пользователю

 Пользователь нажал на кнопку, вы
 запустили какое-то действие
 Вы не знаете, сколько оно продлится
 Советы бывалых:
   Сразу же выключите кнопку (disable)
   Запустите действие и таймер
   Если действие не завершилось за
   200-500мс, включите progress bar или
   какую-нибудь ещё анимацию
А в моей программе всё ок?

Android 2.3 (Gingerbread): StrictMode

  Следит за вашей программой
  Определяет, где вы делаете опасные
  вызовы в основном потоке
  И наказывает вас!
А в моей программе всё ок?

Вставьте что-то вроде этого в начало
программы:

StrictMode.setThreadPolicy(
   new StrictMode.ThreadPolicy.Builder()
     .detectAll()
     .penaltyLog()
     .build());

И вы получите кучу сообщений в log со
стеком и рассказом, как вы неправы.
SQLite

 Транзакция обязательно пишет журнал
   новый файл, запись, удалить файл
   если места мало, скорость падает
   катастрофически (с 5мс до 60мс)
 Индексы
 EXPLAIN, EXPLAIN QUERY PLAN
 sqlite-wrapper.pl (by bradfitz)
   http://code.google.com/p/zippy-android
А нужен ли SQLite?

 Пишете лог? Лучше добавлять строчки в
 файл
 Только читаете? Нужна простая структура?
 Подумайте, не обойтись ли простой
 структурой в файле
 SQLite -- не Oracle, он всё делает
 прямолинейно
GC/использование памяти

for (Element el: elements) {
  Wrapper wrapper = new Wrapper(el);
  wrapper.doTask();
  // wrapper is deleted
}

Wrapper wrapper = new Wrapper();
for (Element el: elements) {
  wrapper.setElement(el);
  wrapper.doTask();
  // GC is happy!
}
Использование памяти

 16Мб на процесс (24Мб на N1/Desire)
 Это включает себя "нативную кучу", где
 хранятся Bitmap
 Если памяти остаётся мало, GC начинает
 включаться чаще
 Если пытаться занять больше, получите
 OOME
Нативная куча

Bitmap bitmap = ...(1024x768 pixels, 24bpp);

Проблема: с точки зрения GC объект bitmap
занимает ~15байт, поэтому, когда мало
памяти, его удалять нет особого смысла.

Однако: bitmap отъедает 2.3Мб от тех 24Мб,
которые выделены процессу
Нативная куча

Поэтому не полагайтесь на GC:

Bitmap bitmap = ...(1024x768, 24bpp);
... use it ...
... when you don't need it any more:
bitmap.recycle();
Сказки у камина (aka joys of no JIT)

class Item {
  private int x, y;
  public int getX() { return x; }
  public int getY() { return y; }
}
List<Item> array = new ArrayList<Item>(10^9);

Это
  создаёт огромное множество объектов Item
  на куче
  с оверхедом в два раза
Сказки у камина (aka joys of no JIT)
Сказки у камина (aka joys of no JIT)

Обращение к элементу происходит так:
array.get(55).getX()

1. Находим в объекте array виртуальную
   таблицу, соответствующую интерфейсу List
2. Отыскиваем ней метод get
3. Вызываем его
4. Он проверяет границы...
Сказки у камина (aka joys of no JIT)

array.get(55).getX()


5. Находит 55-ый элемент массива
6. В таблице виртуальных методов Item ищет
   getX()
7. Метод getX() прибавляет смещение к
   началу объекта, и...
8. Возвращает нам x!
Сказки у камина (aka joys of no JIT)

class ItemArray {
 private int[] xx;
 private int[] yy;

    public final int getX(int idx) {...}
    public final int getY(int idx) {...}
}

       Нет оверхеда по памяти
      Вызов гораздо быстрее (одно обращение к
      массиву с проверкой границ)
Сказки у камина (aka joys of no JIT)

class ItemArray {
 private int[] xx;
 private int[] yy;

    public final int getX(int idx) {...}
    public final int getY(int idx) {...}
}

       Компилятор может подставлять прямой
      вызов или даже inline из-за final
Сказки у камина (aka joys of no JIT)

  Как прочесть строку из файла в String?
  В файле живут char'ы
  Есть StringBuilder (прочли что-то --
  добавили)
  Дальше -- builder.toString()
  Но тут происходит копирование
  4Mb в файле (== 4Mchar) -->
  --> 8Mb в памяти (char==2bytes) -->
  --> 16Mb при копировании -->
  --> OOME!!!
Сказки у камина (aka joys of no JIT)

  Кроме этого, нельзя без копирования
  перевести из char[] в String и обратно (хотя
  String состоит всего лишь из char[]!)
  Взятие подстроки всегда копирует
  Парсинг любых текстовых форматов
  становится сущим мучением
  (Don't even think of using XML for your next
  project!)
  (Впрочем, если вы реализуете парсинг в
  нативном коде...)
Сказки у камина (aka joys of no JIT)

  В запущенных случаях хочется все строки
  выкинуть и заменить на один большой char
  [] со смещениями
  Немедленно все проблемы решаются --
  можно делать slice, доступ быстрый
  (никаких там виртуальных функций)
  Даже проверка границ в x[i] происходит в
  нативном коде, а не в интерпретируемом!
Использование памяти

 Профайлер памяти:
 http://www.eclipse.org/mat/
 В нужный момент сбросить кучу на диск:
 Debug.dumpHprofData
  ("/data/data/<package>/dump.hprof");
 Далее:
 adb pull /data/data/<package>/dump.hprof
 hprof_conv dump.hprof dump-conv.hprof
 Всё, можно загружать в Eclipse!
Если всё же виноват CPU

 Нет, вы этого так просто знать не можете!
 Сначала запустите профайлер
 Потом найдите виновника...
 ...и перепишите его на C++
Android NDK

http://developer.android.com/sdk/ndk/index.html

  Объявляете фукнции в java как native
  Обрабатываете java-файл:
  javah -jni org.some.package.MyClass
  Получаете .h-файл
  Пишете реализацию функций
Если функции общаются с
примитивными типами, больше
    знать ничего не надо
           (почти)
NDK

 Парсинг текстовых форматов
 И не текстовых тоже
 Тяжёлые вычисления
   Mercator java -> native -> table
 Всё, про что профайлер говорит, что оно
 занимает слишком много времени
 Поиск подстроки в длинной строке,
 например (std->КМП = 0.8, std->native=0.1)
Что почитать
Ссылки

   http://source.android.com/
 (очень полезное чтение, кладезь знаний)
 (и горестных мыслей)
 http://developer.android.com/guide/index.html
 (много рассказов, "как надо")
 http://java.sun.com/developer/onlineTraining/
 Programming/JDCBook/jni.html (JNI)
 http://android-developers.blogspot.com/
 (несколько важных статей про разные
 тонкости)
Спасибо!
теперь спрашивайте
backup slides
Страшные сказки

while(true) {
  Task task = queue.getTaskBlocking();
  task.execute();
}
Страшные сказки

Task task;
while(true) {
  task = queue.getTaskBlocking();
  task.execute();
}
Страшные сказки

while(true) {
  Task task = queue.getTaskBlocking();
  task.execute();
  task = null;
}
Страшные сказки

while(true) {
  Task task = queue.getTaskBlocking();
  task.execute();
}
Совсем Страшные сказки

class Magic {
  static private int n = 0;
  static public void doMagic(Object obj) {
    if (obj != null) n++;
  }
  static public int getMagic() { return n; }
}
Совсем Страшные сказки

class Magic {
  static private int n = 0;
  static public void doMagic(Object obj) {
    if (obj != null) n++;
  }
  static public int getMagic() { return n; }
}
while(true) {
  Task task = queue.getTaskBlocking();
  task.execute();
  task = null;
  Magic.doMagic(task);
}
Уфф...

мурашки по спине

Android: Как написать приложение, которое не тормозит

  • 1.
    Андроид Как написать приложение,которое не тормозит Максим Ушаков, Google
  • 2.
    Обзор Руки прочьот UI Thread! Немного об SQLite Память и GC: Нативная куча Строки/массивы/массивы объектов Страшные сказки на ночь Как разобраться с памятью? Что делать, если всё-таки виноват CPU?
  • 3.
  • 4.
    "ANR" получает программа,которая за 5 секунд не ответила на действие пользователя или за 10 секунд не закончился BroadcastReceiver Но даже раньше этого пользователи жалуются, что программа "тормозит"
  • 5.
    "ANR" обычно получаеттот, кто Делает какую-то существенную работу, Читает или пишет файлы, Обращается к сети, или просто общается с базой данных в основном потоке программы (Main/UI Thread)
  • 6.
  • 7.
    Что выполняется "медленно" Очевидныевещи, вроде увеличения всех чисел в базе данных на 1, взлома шифра RSA, и т.д. Но и менее очевидные: Запись на флеш: 5-200(!)мс Сеть: пинг = 100мс--10с--больше (GPRS) Соединение+HTTP+6k = 1-6с (3G) Garbage collector run -- ?мс
  • 8.
    Как отложить работуна потом android.os.AsyncTask android.app.IntentService подробнее: http://developer.android.com/reference
  • 9.
    Как показать этопользователю Пользователь нажал на кнопку, вы запустили какое-то действие Вы не знаете, сколько оно продлится Советы бывалых: Сразу же выключите кнопку (disable) Запустите действие и таймер Если действие не завершилось за 200-500мс, включите progress bar или какую-нибудь ещё анимацию
  • 10.
    А в моейпрограмме всё ок? Android 2.3 (Gingerbread): StrictMode Следит за вашей программой Определяет, где вы делаете опасные вызовы в основном потоке И наказывает вас!
  • 11.
    А в моейпрограмме всё ок? Вставьте что-то вроде этого в начало программы: StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .build()); И вы получите кучу сообщений в log со стеком и рассказом, как вы неправы.
  • 12.
    SQLite Транзакция обязательнопишет журнал новый файл, запись, удалить файл если места мало, скорость падает катастрофически (с 5мс до 60мс) Индексы EXPLAIN, EXPLAIN QUERY PLAN sqlite-wrapper.pl (by bradfitz) http://code.google.com/p/zippy-android
  • 13.
    А нужен лиSQLite? Пишете лог? Лучше добавлять строчки в файл Только читаете? Нужна простая структура? Подумайте, не обойтись ли простой структурой в файле SQLite -- не Oracle, он всё делает прямолинейно
  • 14.
    GC/использование памяти for (Elementel: elements) { Wrapper wrapper = new Wrapper(el); wrapper.doTask(); // wrapper is deleted } Wrapper wrapper = new Wrapper(); for (Element el: elements) { wrapper.setElement(el); wrapper.doTask(); // GC is happy! }
  • 15.
    Использование памяти 16Мбна процесс (24Мб на N1/Desire) Это включает себя "нативную кучу", где хранятся Bitmap Если памяти остаётся мало, GC начинает включаться чаще Если пытаться занять больше, получите OOME
  • 16.
    Нативная куча Bitmap bitmap= ...(1024x768 pixels, 24bpp); Проблема: с точки зрения GC объект bitmap занимает ~15байт, поэтому, когда мало памяти, его удалять нет особого смысла. Однако: bitmap отъедает 2.3Мб от тех 24Мб, которые выделены процессу
  • 17.
    Нативная куча Поэтому неполагайтесь на GC: Bitmap bitmap = ...(1024x768, 24bpp); ... use it ... ... when you don't need it any more: bitmap.recycle();
  • 18.
    Сказки у камина(aka joys of no JIT) class Item { private int x, y; public int getX() { return x; } public int getY() { return y; } } List<Item> array = new ArrayList<Item>(10^9); Это создаёт огромное множество объектов Item на куче с оверхедом в два раза
  • 19.
    Сказки у камина(aka joys of no JIT)
  • 20.
    Сказки у камина(aka joys of no JIT) Обращение к элементу происходит так: array.get(55).getX() 1. Находим в объекте array виртуальную таблицу, соответствующую интерфейсу List 2. Отыскиваем ней метод get 3. Вызываем его 4. Он проверяет границы...
  • 21.
    Сказки у камина(aka joys of no JIT) array.get(55).getX() 5. Находит 55-ый элемент массива 6. В таблице виртуальных методов Item ищет getX() 7. Метод getX() прибавляет смещение к началу объекта, и... 8. Возвращает нам x!
  • 22.
    Сказки у камина(aka joys of no JIT) class ItemArray { private int[] xx; private int[] yy; public final int getX(int idx) {...} public final int getY(int idx) {...} } Нет оверхеда по памяти Вызов гораздо быстрее (одно обращение к массиву с проверкой границ)
  • 23.
    Сказки у камина(aka joys of no JIT) class ItemArray { private int[] xx; private int[] yy; public final int getX(int idx) {...} public final int getY(int idx) {...} } Компилятор может подставлять прямой вызов или даже inline из-за final
  • 24.
    Сказки у камина(aka joys of no JIT) Как прочесть строку из файла в String? В файле живут char'ы Есть StringBuilder (прочли что-то -- добавили) Дальше -- builder.toString() Но тут происходит копирование 4Mb в файле (== 4Mchar) --> --> 8Mb в памяти (char==2bytes) --> --> 16Mb при копировании --> --> OOME!!!
  • 25.
    Сказки у камина(aka joys of no JIT) Кроме этого, нельзя без копирования перевести из char[] в String и обратно (хотя String состоит всего лишь из char[]!) Взятие подстроки всегда копирует Парсинг любых текстовых форматов становится сущим мучением (Don't even think of using XML for your next project!) (Впрочем, если вы реализуете парсинг в нативном коде...)
  • 26.
    Сказки у камина(aka joys of no JIT) В запущенных случаях хочется все строки выкинуть и заменить на один большой char [] со смещениями Немедленно все проблемы решаются -- можно делать slice, доступ быстрый (никаких там виртуальных функций) Даже проверка границ в x[i] происходит в нативном коде, а не в интерпретируемом!
  • 27.
    Использование памяти Профайлерпамяти: http://www.eclipse.org/mat/ В нужный момент сбросить кучу на диск: Debug.dumpHprofData ("/data/data/<package>/dump.hprof"); Далее: adb pull /data/data/<package>/dump.hprof hprof_conv dump.hprof dump-conv.hprof Всё, можно загружать в Eclipse!
  • 28.
    Если всё жевиноват CPU Нет, вы этого так просто знать не можете! Сначала запустите профайлер Потом найдите виновника... ...и перепишите его на C++
  • 29.
    Android NDK http://developer.android.com/sdk/ndk/index.html Объявляете фукнции в java как native Обрабатываете java-файл: javah -jni org.some.package.MyClass Получаете .h-файл Пишете реализацию функций
  • 30.
    Если функции общаютсяс примитивными типами, больше знать ничего не надо (почти)
  • 31.
    NDK Парсинг текстовыхформатов И не текстовых тоже Тяжёлые вычисления Mercator java -> native -> table Всё, про что профайлер говорит, что оно занимает слишком много времени Поиск подстроки в длинной строке, например (std->КМП = 0.8, std->native=0.1)
  • 32.
  • 33.
    Ссылки http://source.android.com/ (очень полезное чтение, кладезь знаний) (и горестных мыслей) http://developer.android.com/guide/index.html (много рассказов, "как надо") http://java.sun.com/developer/onlineTraining/ Programming/JDCBook/jni.html (JNI) http://android-developers.blogspot.com/ (несколько важных статей про разные тонкости)
  • 34.
  • 35.
  • 36.
    Страшные сказки while(true) { Task task = queue.getTaskBlocking(); task.execute(); }
  • 37.
    Страшные сказки Task task; while(true){ task = queue.getTaskBlocking(); task.execute(); }
  • 38.
    Страшные сказки while(true) { Task task = queue.getTaskBlocking(); task.execute(); task = null; }
  • 39.
    Страшные сказки while(true) { Task task = queue.getTaskBlocking(); task.execute(); }
  • 40.
    Совсем Страшные сказки classMagic { static private int n = 0; static public void doMagic(Object obj) { if (obj != null) n++; } static public int getMagic() { return n; } }
  • 41.
    Совсем Страшные сказки classMagic { static private int n = 0; static public void doMagic(Object obj) { if (obj != null) n++; } static public int getMagic() { return n; } } while(true) { Task task = queue.getTaskBlocking(); task.execute(); task = null; Magic.doMagic(task); }
  • 42.