2011-06-29 22 views
20

UPDATE 2/13/2012: Zaakceptowano odpowiedź, wyjaśniłem, że to zachowanie jest błędem, i zauważyłem, że wydaje się, że zniknęło na emulatorach lepiej niż wersja 1.6, co sprawia, że ​​nie jest to problem dla większości z nas. Obejście problemu polega po prostu na zapętleniu/uśpieniu aż do getContext(). Funkcja getApplicationContext() zwraca wartość inną niż null. END UPDATEDlaczego metoda AndroidTestCase.getContext(). GetApplicationContext() zwraca wartość null?

Zgodnie z Android.app.Application javadoc, zdefiniowałem singleton (nazywany Database), że wszystkie moje aktywności uzyskują dostęp do stanów i trwałych danych, a Database.getDatabase (Kontekst) pobiera kontekst aplikacji przez kontekst. getApplicationContext(). Ta konfiguracja działa tak, jak jest reklamowana, gdy działania przekazują się do getDatabase (kontekst), ale kiedy uruchamiam test jednostki z AndroidTestCase, wywołanie getApplicationContext() często zwraca wartość null, chociaż im dłuższy test, tym częściej zwraca wartość inną niż null wartość.

Poniższy kod odtwarza wartość null w obrębie AndroidTestCase - singleton nie jest konieczny do demonstracji.

Najpierw, aby rejestrować wiadomości z aplikacjami, w testowanej aplikacji zdefiniowałem MyApp i dodałem ją do manifestu.

public class MyApplication extends Application { 
    @Override 
    public void onCreate() { 
     super.onCreate(); 
     Log.i("MYAPP", "this=" + this); 
     Log.i("MYAPP", "getAppCtx()=" + getApplicationContext()); 
    } 
} 

Następnie zdefiniowałem przypadek testowy zgłosić na AndroidTestCase.getContext (4-krotnie), oddzielone przez niektórych śpi a getSharedPreferences() połączenia:

public class DatabaseTest extends AndroidTestCase { 
    public void test_exploreContext() { 
     exploreContexts("XPLORE1"); 
     getContext().getSharedPreferences("foo", Context.MODE_PRIVATE); 
     exploreContexts("XPLORE2"); 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     exploreContexts("XPLORE3"); 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     exploreContexts("XPLORE4"); 
    } 
    public void exploreContexts(String tag) { 
     Context testContext = getContext(); 
     Log.i(tag, "testCtx=" + testContext + 
       " pkg=" + testContext.getApplicationInfo().packageName); 
     Log.i(tag, "testContext.getAppCtx()=" + testContext.getApplicationContext()); 
     try { 
      Context appContext = testContext.createPackageContext("com.foo.android", 0); 
      ApplicationInfo appInfo = appContext.getApplicationInfo(); 
      Log.i(tag, "appContext=" + appContext + 
        " pkg=" + appContext.getApplicationInfo().packageName); 
      Log.i(tag, "appContext.getAppCtx()=" + appContext.getApplicationContext()); 
     } catch (NameNotFoundException e) { 
      Log.i(tag, "Can't get app context."); 
     } 
    } 
} 

I to jest kawał Otrzymany logcat (1,6 emulatora SDK11 WinXP poprzez Eclipse)

INFO/TestRunner(465): started: test_exploreContext(test.foo.android.DatabaseTest) 
INFO/XPLORE1(465): [email protected] pkg=com.foo.android 
INFO/XPLORE1(465): testContext.getAppCtx()=null 
INFO/XPLORE1(465): [email protected] pkg=com.foo.android 
INFO/XPLORE1(465): appContext.getAppCtx()=null 
INFO/XPLORE2(465): [email protected] pkg=com.foo.android 
INFO/XPLORE2(465): testContext.getAppCtx()=null 
INFO/XPLORE2(465): [email protected] pkg=com.foo.android 
INFO/XPLORE2(465): appContext.getAppCtx()=null 
INFO/MYAPP(465): [email protected] 
INFO/MYAPP(465): getAppCtx()[email protected] 
INFO/XPLORE3(465): [email protected] pkg=com.foo.android 
INFO/XPLORE3(465): testContext.getAppCtx()[email protected] 
INFO/XPLORE3(465): [email protected] pkg=com.foo.android 
INFO/XPLORE3(465): appContext.getAppCtx()[email protected] 
INFO/XPLORE4(465): [email protected] pkg=com.foo.android 
INFO/XPLORE4(465): testContext.getAppCtx()[email protected] 
INFO/XPLORE4(465): [email protected] pkg=com.foo.android 
INFO/XPLORE4(465): appContext.getAppCtx()[email protected] 
INFO/TestRunner(465): finished: test_exploreContext(test.foo.android.DatabaseTest) 

Należy zauważyć, że getApplicationContext() zwrócone zerowy na chwilę, po czym zaczął powrocie wystąpienie MojaApl. Nie byłem w stanie uzyskać dokładnie takich samych rezultatów w różnych przebiegach tego testu (tak skończyłem w 4 iteracjach, śpi i to wywołanie getSharedPreferences(), aby spróbować uruchomić aplikację).

Fragment wiadomości LogCat powyżej wydawał się najbardziej odpowiedni, ale cały LogCat dla tego pojedynczego przebiegu tego pojedynczego testu był interesujący. Android uruchomił 4 AndroidRuntimes; ten kawałek powyżej był z czwartego. Co ciekawe, 3rd Runtime wyświetlane komunikaty wskazujące, że instancja inną instancję MojaApl w ID procesu 447:

INFO/TestRunner(447): started: test_exploreContext(test.foo.android.DatabaseTest) 
INFO/MYAPP(447): [email protected] 
INFO/MYAPP(447): getAppCtx()[email protected] 
INFO/TestRunner(447): finished: test_exploreContext(test.foo.android.DatabaseTest) 

Przypuszczam, że (447) komunikaty TestRunner pochodzą z raportów wątku testowym rodzic na swoich dzieci w procesie 465 Pozostaje jednak pytanie: dlaczego Android uruchamia AndroidTestCase, zanim jego kontekst zostanie poprawnie podłączony do instancji aplikacji?

Obejście: Jeden z moich testów wydawało się uniknąć null większość czasu, gdy zadzwoniłem getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit(); pierwszy, więc idę z tym.

BTW: Jeśli odpowiedź brzmi: "to błąd Androida, dlaczego go nie zarchiwizujesz, do cholery, dlaczego go nie naprawisz?" wtedy byłbym gotów zrobić jedno i drugie. Nie zrobiłem jeszcze kroku, by być filantem lub płatnikiem błędów - może to dobry czas.

+0

Zauważyłem też dziwne zachowanie w AndroidTestCase. Zazwyczaj każdy test jest uruchamiany wiele razy, ale tylko raz raportuje do Eclipse. Na przykład czasami otrzymuję wyjątki 'Nullpointer' w' onDestroy', podczas gdy wszystkie pola instancji zostały poprawnie zainicjowane w 'onCreate'. Możesz to przetestować poprzez wstawienie punktu przerwania w 'onDestroy' i zobaczenie, że wykonanie przerwało się tam co najmniej 4 razy, podczas gdy test był uruchamiany tylko raz. – siamii

+1

Mam ten sam problem na emulatorze Androida 4.0.4, w którym czasami znajdował się ApplicationContext, a czasami nie. Twoje obejście problemu, dopóki się nie pojawi, wydaje się działać. – Ralf

+0

Pojawia się także dla mnie podczas testów na emulatorze HAX z SDK 10. W przeciwnym razie wygląda na to, że błędu nie można już odtworzyć. – Snicolas

Odpowiedz

12

Oprzyrządowanie działa w oddzielnym wątku od głównego wątku aplikacji, dzięki czemu może być wykonywane bez blokowania lub przerywania (lub blokowania) głównego wątku.Jeśli chcesz zsynchronizować główny wątek, użyj na przykład: Instrumentation.waitForIdleSync()

W szczególności obiekt aplikacji, jak również wszystkie inne klasy najwyższego poziomu, takie jak aktywność, są inicjowane przez główny wątek. Twój wątek z instrumentacją działa w tym samym czasie, co inicjatory. Jeśli dotykasz któregoś z tych obiektów i nie wdrażasz własnych środków bezpieczeństwa wątków, prawdopodobnie powinieneś mieć taki kod uruchomiony na głównym wątku, takim jak: Instrumentation.runOnMainSync(java.lang.Runnable)

+0

Aha! Dam mu szansę (a następnie zaakceptuję twoją odpowiedź). +1 na razie. Dzięki! – cdhabecker

+0

Myślę, że prawidłowo zidentyfikowałeś przyczynę tego zachowania, więc akceptuję tę odpowiedź. Zachowanie jest nadal wadą; klasy szkieletowe mają ukończyć konfigurację przed uruchomieniem metod przypadków testowych. Na przykład, AndroidTestCase.getContext() javadoc nie wyświetla żadnych wcześniejszych wymagań; w rzeczywistości getContext() zwraca wartość, jest to getContext(). getApplicationContext(), która nie działa (przez jakiś czas). Idea synchronizacji instrumentu jest przyjemna, ale nie dotyczy AndroidTestCase. Dzięki i tak. WRESZCIE - Mogę odtworzyć to na emulatorze 1.6, ale nie w wersji 2.1, 2.3.3 lub 3.0. Więc nazwijmy to dniem! :-) – cdhabecker

4

Jak wspomniano w pytaniu i odpowiedzi Dianne (@ hackbod), Instrumentation działa na osobnym wątku. AndroidTestCase ma wadę implementacji (brak synchronizacji) lub nie jest poprawnie udokumentowany. Niestety, nie ma sposobu, aby zadzwonić pod numer Instrumentation.waitForIdleSync() z tej konkretnej klasy przypadków testowych, ponieważ instrumentacja nie jest z niego dostępna.

Podklasa ta nie może być używana do dodawania synchronizacji, który sonduje getApplicationContext() dopóki nie zwróci niezerową wartość: czas trwania

public class MyAndroidTestCase extends AndroidTestCase { 

    @Override 
    public void setContext(Context context) { 
     super.setContext(context); 

     long endTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(2); 

     while (null == context.getApplicationContext()) { 

      if (SystemClock.elapsedRealtime() >= endTime) { 
       fail(); 
      } 

      SystemClock.sleep(16); 
     } 
    } 
} 

odpytywanie i snu są oparte na doświadczeniu i mogą być konfigurowane w razie potrzeby.

+1

Ten problem wydaje się dotyczyć urządzeń poniżej API 16 Jelly Bean. –

Powiązane problemy