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