ОБНОВЛЕНИЕ 2/13/2012: Принял ответ, объяснил, что это поведение является ошибкой, и отметил, что он, по-видимому, исчез на эмуляторах лучше, чем v 1.6, что делает его не проблемой для большинства из нас. Обходным путем является просто цикл/спящий режим до получения getContext(). GetApplicationContext() возвращает ненулевое значение. END UPDATE
Как и в android.app.Application javadoc, я определил singleton (называемый Database), что весь доступ к моим действиям для состояния и постоянных данных, а Database.getDatabase(Context) получает контекст приложения через Context.getApplicationContext(). Эта настройка работает так, как рекламируется, когда действия передаются в getDatabase (Context), но когда я запускаю unit test из AndroidTestCase, вызов getApplicationContext() часто возвращает null, хотя чем дольше тест, тем чаще он возвращает не- нулевое значение.
Следующий код воспроизводит нулевое значение в AndroidTestCase - для демонстрации не требуется синглтон.
Во-первых, для регистрации сообщений о создании приложения в приложении под тестом я определил MyApp и добавил его в манифест.
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.i("MYAPP", "this=" + this);
Log.i("MYAPP", "getAppCtx()=" + getApplicationContext());
}
}
Далее я определил тестовый пример, чтобы сообщать об AndroidTestCase.getContext() 4 раза, разделенные некоторыми снами и вызовом 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.");
}
}
}
И это кусок результирующего эмулятора logCat (1.6 на SDK11 WinXP через 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)
Обратите внимание, что getApplicationContext() вернул null некоторое время, а затем начал возвращать экземпляр MyApp. Я не смог получить точные результаты в разных прогонах этого теста (что, как я закончил с 4 итерациями, спит и этот вызов getSharedPreferences(), чтобы попытаться запустить приложение).
Час сообщений LogCat выше выглядел наиболее актуальным, но весь LogCat для этого одиночного запуска этого единственного теста был интересен. Android начал 4 AndroidRuntimes; кусок выше был с 4-го. Интересно, что 3-я среда выполнения отображала сообщения, указывающие, что она создала экземпляр MyApp другого экземпляра в ID 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)
Я предполагаю, что сообщения TestRunner (447) из родительского тестового потока сообщают о его дочерних элементах в процессе 465. Тем не менее, возникает вопрос: почему Android разрешает запуск AndroidTestCase до того, как его контекст будет правильно подключен к экземпляру приложения
Временное решение. Один из моих тестов, как правило, избегал нулей в большинстве случаев, если я сначала позвонил getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit();
, поэтому я собираюсь с этим.
BTW. Если ответ "это ошибка Android, почему бы вам не записать его, черт возьми, почему бы вам не исправить это?" то я был бы готов сделать то и другое. Я еще не принял на себя ответственность за исправление ошибок или внесения изменений - возможно, это подходящее время.