asd asdsa f
Статьи

Как ускорить приложение для Андроид. Борьба со сборщиком мусора.

1 35

Хотите чтобы ваше Android-приложение летало? Чтобы у пользователей просто мандраж наступал от того какое оно быстрое? Вы заработаете много денег, если вашим пользователям будет по кайфу использовать ваше приложение.

Один из самых основных ответов на вопрос как улучшить производительность Android-приложения таков: дружите со сборщиком мусора. Нет, не с тем хмурым мужиком, который копошится в мусорных баках. Речь пойдет о garbage collector-е (GC), компоненте JVM, который любезно уничтожает ненужные объекты в Java. Рассмотрим, для чего он нужен.

Как только GC находит объект, на который больше никто не ссылается (с которым никто не хочет больше играть), то безжалостно его уничтожает. Не будем касаться алгоритмов поиска «подвешенных» объектов, т.к. с точки зрения производительности разница между ними некритичная.  Так в чем тут проблема?

На вашем домашнем компьютере работа сборщика мусора будет совсем незаметна, поскольку даже на средней современной машинке ресурсов хватает. На мобильных устройствах объем ресурсов сейчас тоже растет стремительно, но их по прежнему недостаточно, чтобы скрыть нелегкую работу сборщика. Это особенно критично для приложений, интенсивно работающих с ресурсами устройства, такими как графика, видео, аудио. Тут мы приближаемся непосредственно к проблеме.

Успешные разработчики игр знают эту проблему очень хорошо. Когда в цикл игрового процесса вклинивается GC и начитает вычищать память, приложение «виснет». Чем это грозит, надеюсь, объяснять не надо? Достаточно просто сказать, что конкуренция на рынке мобильных приложений огромная. Если ваша игра тормозит и виснет, то на смартфоне вашего клиента она долго не задержится. Решение данной проблемы лежит на поверхности.

Прежде чем переходить к основному содержимому, пройдемся по общим пунктам «устава борца против сборщика мусора». Мы не хотим, чтобы срабатывал GC, поэтому нужно соблюдать следующие правила:

1) Создавайте объекты как можно реже. Грубой ошибкой будет создавать объекты внутри, например, цикла визуализации (на каждом шаге которого вы рисуете кадр игры).

2) Очень осторожно используйте классы стандартной библиотеки Java. Лучше не использовать библиотечные реализации интерфейсов Map и Set. Они любят создавать новые объекты, что даст богатую пищу для GC. По этой же причине не следует пользоваться итераторами.  Если нужна функциональность Map, то практически всегда можно обойтись гораздо более эффективным SparseArray (а также SparseIntArray и SparseBooleanArray) из фреймфорка.

3) Для конкатенации строк всегда используйте StringBuilder. Оператор + для склейки строковых литералов создает новые объекты String.

4) Не следует применять обертки над примитивными типами. Используйте int, а не Integer.

Наверняка, советов должно быть больше, но основную идею вы уже поняли.

Наконец, настало время размять суставы пальцев и написать немного кода! Сейчас мы рассмотрим один универсальный механизм борьбы со сборщиком мусора, который вы сможете использовать повторно в ваших проектах.

Чтобы предотвратить запуск сборки мусора, необходимо обеспечить сохранение «сильной» ссылки на объект. Допустим, вы интенсивно используете объекты одного и того же типа. Такими объектами могут быть, например, события взаимодействия с UI.  Такого рода объекты сознаются очень часто. Их количество неизбежно увеличивается сто и будет приводить к частой сборке мусора.

Решение проблемы заключается в том, чтобы не оставлять объект на произвол судьбы, а возвращать его в некий пул. Этот пул должен хранить сильные ссылки на объекты. Каждый раз, когда требуется новый экземпляр класса, сначала проверяется наличие готовых объектов в пуле. Если свободный экземпляр есть, то он возвращается клиентскому коду. Если пул пустой, то создается новый объект. Когда клиент больше не нуждается в объекте, но клиент должен вернуть этот объект в пул. Тогда объект снова становится доступным для использования.

Вот пример реализации пула объектов:

public class ObjectPool<T> {

	public interface ObjectFactory<T> {
		public T newInstance();
	}

	private final int mMaxSize;
	private final List<T> mObjects;
	private final ObjectFactory<T> mObjectFactory;

	public ObjectPool(ObjectFactory<T> factory, int poolSize) {
		mObjectFactory = factory;
		mMaxSize = poolSize;
		mObjects = new ArrayList<T>(poolSize);
	}

	/**
	 * С помощью зтого метода можно получить экземпляр объекта.
	 * Метод инкапсулирует создание или получение свободного экземпляра из пула объектов 
	 */
	public T getObject() {
		T obj = null;

		if(mObjects.size() == 0) {
			obj = mObjectFactory.newInstance();
		}
		else {
			obj = mObjects.remove(mObjects.size() - 1);
		}

		return obj;
	}

	/**
	 * Вызываем этот метод, чтобы вернуть объект в пул.
	 * Если пул уже полон, то объект не будет сохранен.  
	 */
	public void setFree(T obj) {
		if(mObjects.size() < mMaxSize) {
			mObjects.add(obj);
		}
	}

}

Как видите, пул потоков может (и должен) быть ограничен по величине в целях экономии памяти.

Клиенты должны реализовать метод newInstance() публичного интерфейса ObjectFactory. Этот метод отвечает за создание новых экземпляров объектов.

Метод getObject проверяет наличие свободных объектов. В зависимости от этого он либо возвращается существующий объект, либо создает новый при помощи фабричного метода  newInstance().

Чтобы освободить объект, клиент должен вызвать метод setFree, передавая туда освобождаемый объект. Если пул объектов уже полон, то объект не будет добавлен и, таким образом, становится доступен для уничтожения.

Так выглядит типичный пример использования класса  ObjectPool:

		
ObjectPool<Event> pool = new ObjectPool<Event>(new ObjectPool.ObjectFactory<Event>() {
        @Override
        public Event newInstance() {
	   return new Event();
        }, 10);

	Event evt = pool.getObject();

	//работаем с объектом evt...

	evt.setFree(evt);

Таким образом, мы приручили сборщик мусора путем сохранения ссылок на освободившиеся объекты.

Стоит отметить, что такой подход требует дополнительно внимания при работе с объектами из пула. Поскольку эти объекты используются повторно, то нужно следить, чтобы их поля также были инициализированы правильно.

Тема оптимизации скорости работы приложений для Андроида не ограничивается только сборщиком мусора. К этому вопросу мы еще не раз вернемся.

Поделитесь своим опытом оптимизации приложений для Андроид в комментариях!

 

About the author / 

admin

  • Pingback: Timothy()