Sunday, October 3, 2010

Kapryśny Android 2

Może nie tyle kapryśny android co kapryśny proces inicjalizacji całego frameworka. Okazało się, że przez kilka/kilkanaście pierwszych przebiegów runSlice (głównej metody Corea) Renderer rzuca wyjątkiem, tj. uchwyt do ekranu (lub płótna - Canvas - jak kto woli) jest nullem. Problem uchował się do dzisiaj, gdyż podczas rysowania obiektów dodawałem zabezpieczenie tj. "if canvas != null then render()". Kolejne zmiany w kodzie ujawniły robaka :P Poniżej o co tak naprawdę chodzi :)

Jak już wspomniałem w jednym ze wcześniejszym wpisów cały proces inicjalizacji kasuroid przeniosłem z view do głównego activity. Przypomnę, że kasuroid w trakcie startowania uruchamia wątek odpowiedzialny za aktualizację i rysowanie gry. Renderer aby spełniać swe zadanie musi wiedzieć gdzie rysować scenę. Ekran (Canvas) tworzony jest podczas inicjalizowania view głównego activity. Problem pojawia się w momencie, kiedy którykolwiek z elementów kasuroid zaczyna odwoływać się do ekranu gdy ten jeszcze nie został utworzony.

Oczywiście najprostszym sposobem jest wstawienie wspomnianego zabezpieczenia. Jednak jest to tylko ukrycie błędu w kodzie. Innym (lepszym) rozwiązaniem jest rozpoczęcie rysowania dopiero gdy ekran został poprawnie utworzony. Z pomocą przychodzą callbacki SurfaceHolder, które wołane są odpowiednio podczas tworzenia, zmiany oraz niszczenia ekranu. Teraz wystarczy we wspomnianych callbackach powiadomić Core o stanie ekranu. Aby to umożliwić rozszerzyłem interfejs Corea o metody:
  • screenCreated - wołana podczas tworzenia ekranu
  • screenChanged - wołana gdy ekran zmienia swoje parametry (np. podczas obrotu urządzenia)
  • screenDestroyed - wołana gdy ekran został właśnie zniszczony (np. podczas zamykania aplikacji)
Metody te ustawiają jedynie (przynajmniej na razie) zmienną mScreenExists na true (screenCreated, screenChanged) lub na false (screenDestroyed)

A tak wyglądają callbacki:
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
    Core.getInstance().screenChanged(holder, width, height);
    Debug.inf(getClass().getName(), "surfaceChanged");
}

@Override
public void surfaceCreated(SurfaceHolder holder) 
{
    Core.getInstance().screenCreated(holder);
    Debug.inf(getClass().getName(), "surfaceCreated");
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) 
{
    Core.getInstance().screenDestroyed(holder);
    Debug.inf(getClass().getName(), "surfaceDestroyed");
}
W zależności od ustawienia zmiennej mScreenExists w Core, runSlice będzie wykonany lub nie. W zasadzie mam pomysł jak inaczej mógłbym to rozwiązać, ale na razie zostawiam tak jak jest (na refaktoryzację przyjdzie jeszcze czas).

No comments:

Post a Comment