суббота, 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 и т.д. создают в памяти копию. Память не утекает, т.к. сборщик мусора удалит исходную строку. Но выполнение сборки во время игрового процесса недопустимо. 


воскресенье, 30 сентября 2012 г.

Очистка мира-box2d andengine



Andengine.
Удаление объектов world-box2d должно производиться в такте andengine.
Вот код.
сначала удаляются все тела, потом соединения


public class MyPhysicsWorld extends FixedStepPhysicsWorld {

...

public void clean() {
  boolean bFlagDestroy=true
  clearForces();
  clearPhysicsConnectors();
  Iterator<Body> allMyBodies = getBodies();
  while (allMyBodies.hasNext()) {
     bFlagDestroy=true;
     final Body currentBody = allMyBodies.next();
     destroyBody(currentBody);
  }
  Iterator<Joint> allMyJoints = getJoints();
  while (allMyJoints.hasNext()) {
     final Joint currentJoint = allMyJoints.next();
     destroyJoint(currentJoint);
  }
  reset();
}

...

 @Override
 public void onUpdate(float pSecondsElapsed) {
   if (mClean) {
      mClean=false;
      clean1();
   }
   super.onUpdate(pSecondsElapsed);
 }


}

Удаление спрайтов со сцены andengine


AndEngine, GLES 1.  Удаление спрайтов со сцены
Удаление спрайтов со сцены должно производиться в такте движка. Почему имеено в такте ?  Потому что  вызов Scene.attachChild(entity) добавляет entity  в
SmartList<IEntity>  класса Entity, от которого наследован класс  Scene.  Во время каждого такта сцена пробегает по списку и вызывает onUpdate для каждого элемента списка.  (Кстати, элемент может отказаться от получения данного уведомления путем вызова setIgnoreUpdate(true)).
Если мы удалим спрайт со сцены вне такта, то сцена об этом знать не будет и при следующем пробеге вывалится exception index out of bounds, т.к. список стал короче.

Вот как правильно производить удаление со сцены:

public class GameScene extends Scene {

public GameScene() {

}

public void setClean()
{
     mClean=true;
}

... 

public void clean() 
{
  this.clearTouchAreas();
  while (getChildCount() > 0) {
      Entity layer = (Entity) getChild(0);
      while (layer.getChildCount() > 0) {
           Entity obj = (Entity) layer.getChild(0);
           while (obj.getChildCount() > 0) {
              Entity sg_child = (Entity) obj.getChild(0);
              obj.detachChild(sg_child);
              sg_child.setIgnoreUpdate(true);
              sg_child = null;
           }
           layer.detachChild(obj);
           obj.setIgnoreUpdate(true);
           obj = null;
         }
         detachChild(layer);
         layer.setIgnoreUpdate(true);
         layer = null;
    }
}



@Override
protected void onManagedUpdate(float pSecondsElapsed) {
   if (mClean)
   {
      mClean=false;
      clean();
   }
   super.onManagedUpdate(pSecondsElapsed);
}

}