Pourquoi AndroidTestCase.getContext (). GetApplicationContext () renvoie null?

MISE À JOUR 2/13/2012: Accepté une réponse, a expliqué que ce comportement est un bug et a noté qu'il semble avoir disparu sur les émulateurs mieux que v 1.6, ce qui en fait un problème pour la plupart d'entre nous. La solution de contournement consiste simplement à boucler / à dormir jusqu'à ce que getContext (). GetApplicationContext () retourne non nulle. FIN UPDATE

Selon android.app.Application javadoc, j'ai défini un singleton (appelé Database) que toutes mes activités ont accès pour l'état et les données persistantes, et Database.getDatabase (Context) obtient le contexte de l'application via Context.getApplicationContext (). Cette configuration fonctionne comme annoncée lorsque les activités se transmettent à getDatabase (Context), mais lorsque j'exécute un test d'unité à partir d'une AndroidTestCase, l'appel getApplicationContext () renvoie souvent nulle, mais plus le test est long, plus souvent il renvoie un non-nul valeur.

Le code suivant reproduit le null dans un AndroidTestCase – le singleton n'est pas nécessaire pour la démonstration.

Tout d'abord, pour enregistrer les messages d'instanciation d'application, dans l'application sous-test, j'ai défini MyApp et l'ai ajouté au manifeste.

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

Ensuite, j'ai défini un cas de test pour faire rapport sur AndroidTestCase.getContext () 4 fois, séparé par certains dort et un appel getSharedPreferences ():

 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."); } } } 

Et c'est un morceau du logCat résultant (1,6 émulateur sur SDK11 WinXP via Eclipse):

 INFO/TestRunner(465): started: test_exploreContext(test.foo.android.DatabaseTest) INFO/XPLORE1(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android INFO/XPLORE1(465): testContext.getAppCtx()=null INFO/XPLORE1(465): appContext=android.app.ApplicationContext@437801e8 pkg=com.foo.android INFO/XPLORE1(465): appContext.getAppCtx()=null INFO/XPLORE2(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android INFO/XPLORE2(465): testContext.getAppCtx()=null INFO/XPLORE2(465): appContext=android.app.ApplicationContext@43782820 pkg=com.foo.android INFO/XPLORE2(465): appContext.getAppCtx()=null INFO/MYAPP(465): this=com.foo.android.MyApplication@43783830 INFO/MYAPP(465): getAppCtx()=com.foo.android.MyApplication@43783830 INFO/XPLORE3(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android INFO/XPLORE3(465): testContext.getAppCtx()=com.foo.android.MyApplication@43783830 INFO/XPLORE3(465): appContext=android.app.ApplicationContext@43784768 pkg=com.foo.android INFO/XPLORE3(465): appContext.getAppCtx()=com.foo.android.MyApplication@43783830 INFO/XPLORE4(465): testCtx=android.app.ApplicationContext@43757368 pkg=com.foo.android INFO/XPLORE4(465): testContext.getAppCtx()=com.foo.android.MyApplication@43783830 INFO/XPLORE4(465): appContext=android.app.ApplicationContext@43785778 pkg=com.foo.android INFO/XPLORE4(465): appContext.getAppCtx()=com.foo.android.MyApplication@43783830 INFO/TestRunner(465): finished: test_exploreContext(test.foo.android.DatabaseTest) 

Notez que getApplicationContext () a renvoyé null pendant un certain temps, puis a commencé à renvoyer une instance de MyApp. Je n'ai pas pu obtenir exactement les mêmes résultats dans différentes séries de ce test (c'est ainsi que j'ai terminé à 4 itérations, dort, et cet appel à getSharedPreferences () pour essayer de faire fonctionner l'application).

Le morceau des messages LogCat ci-dessus semblait le plus pertinent, mais tout le LogCat pour cette seule version de ce seul test était intéressant. Android a commencé 4 AndroidRuntimes; Le morceau ci-dessus était du 4ème. Fait intéressant, le 3ème temps d'exécution affiche des messages indiquant qu'il a instancié une instance différente de MyApp dans le processus ID 447:

 INFO/TestRunner(447): started: test_exploreContext(test.foo.android.DatabaseTest) INFO/MYAPP(447): this=com.foo.android.MyApplication@437809b0 INFO/MYAPP(447): getAppCtx()=com.foo.android.MyApplication@437809b0 INFO/TestRunner(447): finished: test_exploreContext(test.foo.android.DatabaseTest) 

Je présume que les messages TestRunner (447) proviennent d'un compte-test parent qui rapporte ses enfants en cours 465. La question est toujours la suivante: pourquoi Android permet-il d'exécuter une AndroidTestCase avant que son contexte ne soit correctement connecté à une instance de l'application?

Solution de contournement : l'un de mes tests semblait éviter les nuls la plupart du temps si getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit(); D'abord, je vais avec ça.

BTW : si la réponse est "c'est un bug d'Android, pourquoi ne le déposez-vous pas, heck, pourquoi ne pas le réparer?" Alors je serais disposé à faire les deux. Je n'ai pas pris la mesure d'être un bug-filer ou un contributeur encore – peut-être c'est un bon moment.

2 Solutions collect form web for “Pourquoi AndroidTestCase.getContext (). GetApplicationContext () renvoie null?”

L'instrumentation s'exécute dans un thread distinct du thread principal de l'application, de sorte qu'il peut s'exécuter sans bloquer ou interrompre (ou être bloqué par) le thread principal. Si vous devez synchroniser avec le thread principal, utilisez par exemple: Instrumentation.waitForIdleSync ()

En particulier, l'objet Application ainsi que toutes les autres classes de niveau supérieur comme Activité sont initialisées par le thread principal. Le thread d'instrumentation est en cours d'exécution en même temps que ceux qui sont initialisés. Ceci si vous touchez l'un de ces objets et ne mettez pas en œuvre vos propres mesures de sécurité de thread, vous devriez probablement avoir un tel code sur le thread principal, par exemple: Instrumentation.runOnMainSync (java.lang.Runnable)

Comme mentionné dans la question et la réponse de Dianne (@hackbod), l'Instrumentation s'exécute sur un thread distinct. AndroidTestCase comporte un défaut d'implémentation (synchronisation manquante) ou il n'est pas documenté correctement. Malheureusement, il n'y a aucun moyen d'appeler Instrumentation.waitForIdleSync() partir de cette classe de cas de test particulière, car l'Instrumentation n'est pas accessible à partir de celle-ci.

Cette sous-classe peut être utilisée pour ajouter une synchronisation qui analyse getApplicationContext () jusqu'à ce qu'elle renvoie une valeur non nulle:

 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); } } } 

Les durées de sondage et de sommeil sont basées sur l'expérience et peuvent être réglées si nécessaire.

coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.