суббота, 13 октября 2012 г.

Поиск утечек памяти в приложениях Android



Поиск источников, инициализирующих сборщика мусора.

В моей игре Truck adventure во время игрового процесса в LogCat-е постоянно проскакивала строка о том что работет сборщик мусора.  В эти моменты наблюдались тормоза, что портило впечатление от игрового процесса. Я несколько дней бороздил в бескрайних просторах интернета, в надежде найти помощь в решении этой проблемы. Установил MAT, настроил чтобы после HPROF дампа он автоматически запускался. С помощью MAT  мне удалось найти несколько проблемных мест в своем приложении. В интернете много чего про него написано.
MAT надо использовать, если у Вас в приложении происходит постоянная утечка памяти, т.е. размер кучи растет со временем. Это грозит OutOfMmory exception! Недавно я пару дней оптимизировал свое приложение с помощью MAT и нашел несколько утечек памяти. Мне удалось их устранить и теперь во время игры размер кучи не меняется )
Лень сейчас об этом подробно писать, но как будет время обязательно напишу пост как я искал утечки с помощью MAT.

Но MAT мне не помог в поиске куска кода, из за которого постоянно работал GC, возможно не хватает опыта работы с ним.

Тогда я  поочередно комментировал блоки кода, которые работали в главном цикле. Т.к. в AndEngine весь игровой процесс необходимо реализовывать в onUpdate (в такте движка), я просто комментил один за одним блоки.
К примеру: Машина в моей игре, это экземпляр класса TheCar. Он созается после инициализации сцены, кнопок и загрузки всех элементов уровня.
1. Выключил "машинку", т.е. она не создавалась и не добавлялась на игровую сцену. Жор памяти продолжался.
2. Выключил MainHUD(на котором нарисованы элементы управления) , жор памяти не уменьшился.
Значит не машинка, не основной HUD не влияют на постоянный запуск сборщика мусора.
3. Остался 3-й блок, который учавствует в игровом процесс - это физический мир.
В игре я использую Box2d.  Во время игры, при возникновении контакта происходит вызов переопределенного обработчика события  beginContact.   В нем происходит проверка, что с чем  контактирует и соответствующие, дальнейшие вызовы.

вот как он выглядит:


@Override
public void beginContact(final Contact contact)
{
     Body checkedBody    = contact.getFixtureA().getBody();
     Body contactPartner = contact.getFixtureB().getBody();
     ...
}


Проблема была в нем. Когда я писал проверку контактов, то написал одну вспомогательную ф-цию checkContact , которая принимала произвольное число аргументов. Мне было удобно ее использовать.

К примеру:
произошло событие,  вызвался метод
я получил указатели на checkedBody и contactPartner и с помощью этой ф-ции мог выполнить проверку:
if (checkContact(checkedBody, contactPartner, "danger", "carwheel","carbasket","carcabin")
{
     ... здесь должна развалиться машинка ...
}
т.е.  произошел контакт объекта типа danger с  колесом, или кузовом, или кабиной машины

Обработчик вызывался на каждом такте. Соотвтественно dalvik постоянно создавала в памяти большие массивы строк из за этого и происходил жор!

Вот кусок кода, как не надо писать ф-ции в приложениях для ОС Android:
Красным выделены проблемные места!

private boolean checkContact(Body checkedBody, Body contactPartner, String cb, String ...strings )
{
boolean bFlag=false;
for (int i=0;i<strings.length;i++)
{
final String cn_cp = contactPartner.getUserData().getClass().getName().toLowerCase();
final String cn_cb = checkedBody.getUserData().getClass().getName().toLowerCase();
if (cn_cp.contains(strings[i].toLowerCase())&&cn_cb.contains(cb)) 
{
bFlag=true;
break;
}
}
return bFlag;
}


Т.е. видно, что на вход может передаваться произвольное количество строк... В этом и проблема!
Вообще все параметры в методах класса очень желательно объявлять final.
А вот код проверки, который работает на данный момент и весьма успешно!

...

final String cbName = checkedBody.getUserData().getClass().getName();
final String cpName = contactPartner.getUserData().getClass().getName();
if (
cbName.contains("Bonus")&&
(
     cpName.contains("Carwheel")||cpName.contains("Carcabin")||
     cpName.contains("Carframe")||cpName.contains("Carbasket")||
             cpName.contains("Caraxle")
) &&!GameManager.mTheCar.mIsDestroyed
)
{

        ... произошел контакт машинки и бонуса! ...
return;
}
...

И кстати сказать, - не используйте в своих играх, в главном цикле, ф-ции работы со строками, которые в результате создают копию строки. Даже uppercase, lowercase, concat и т.д. создают в памяти копию. Память не утекает, т.к. сборщик мусора удалит исходную строку. Но выполнение сборки во время игрового процесса недопустимо. 


Комментариев нет:

Отправить комментарий