Почему AndroidTestCase.getContext (). GetApplicationContext () возвращает ноль? - PullRequest
20 голосов
/ 29 июня 2011

ОБНОВЛЕНИЕ 13.02.2012: Приняли ответ, объяснили, что это поведение является ошибкой, и отметили, что оно исчезло в эмуляторах лучше, чем v 1.6, что делает его без проблем для большинства из нас. Обходной путь заключается в том, чтобы просто зациклить / уснуть, пока getContext (). GetApplicationContext () вернет ненулевое значение. КОНЕЦ ОБНОВЛЕНИЯ

В соответствии с javadoc android.app.Application, я определил синглтон (называемый базой данных), к которому все мои действия обращаются к состоянию и постоянным данным, а Database.getDatabase (Context) получает контекст приложения через Context.getApplicationContext (). Эта настройка работает так, как объявлено, когда действия передаются в getDatabase (Context), но когда я запускаю модульный тест из AndroidTestCase, вызов getApplicationContext () часто возвращает ноль, хотя чем длиннее тест, тем чаще он возвращает ненулевой значение.

Следующий код воспроизводит нулевое значение в 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): 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)

Обратите внимание, что getApplicationContext () некоторое время возвращал значение null, а затем начал возвращать экземпляр MyApp. Я не смог получить одинаковые результаты при разных запусках этого теста (вот как я закончил на 4 итерациях, в спящем режиме и с помощью вызова getSharedPreferences (), чтобы попытаться вывести приложение из строя).

Часть сообщений LogCat, представленных выше, казалась наиболее актуальной, но весь LogCat для этого отдельного прогона этого одиночного теста был интересен. Android запустил 4 AndroidRuntimes; кусок выше был с 4-го. Интересно, что 3-я среда выполнения отображала сообщения, указывающие, что она создала другой экземпляр MyApp в идентификаторе процесса 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)

Я предполагаю, что сообщения TestRunner (447) поступают от родительского тестового потока, который сообщает о своих дочерних процессах 465. Тем не менее, возникает вопрос: почему Android запускает AndroidTestCase до того, как его контекст должным образом подключен к экземпляру приложения

Обходной путь : Один из моих тестов, по-видимому, большую часть времени избегал нулей, если я сначала позвонил getContext().getSharedPreferences("anyname", Context.MODE_PRIVATE).edit().clear().commit();, поэтому я продолжаю.

КСТАТИ : Если ответ «это ошибка Android, почему бы вам не отправить ее; черт, почему бы вам не исправить это?» тогда я был бы готов сделать оба. Я еще не сделал шаг к тому, чтобы стать баг-файлером или участником - возможно, сейчас хорошее время.

Ответы [ 2 ]

12 голосов
/ 29 ноября 2011

Инструментарий работает в отдельном потоке от основного потока приложения, поэтому он может выполняться без блокировки или прерывания (или блокировки) основного потока. Если вам нужно синхронизироваться с основным потоком, используйте, например: Instrumentation.waitForIdleSync ()

В частности, объект Application, а также все другие классы верхнего уровня, такие как Activity, инициализируются основным потоком. Ваш поток инструментария работает одновременно с инициализацией. Это если вы касаетесь какого-либо из этих объектов и не реализуете свои собственные меры безопасности потока, вам, вероятно, следует запустить такой код в главном потоке, например, через: *

4 голосов
/ 27 ноября 2013

Как уже упоминалось в вопросе и ответе Дайан (@hackbod), Инструментарий работает в отдельном потоке. AndroidTestCase либо имеет дефект реализации (отсутствует синхронизация), либо неправильно задокументирован. К сожалению, нет способа вызвать Instrumentation.waitForIdleSync() из этого конкретного класса тестового примера, потому что инструментарий недоступен из него.

Этот подкласс может использоваться для добавления синхронизации, которая опрашивает getApplicationContext (), пока не вернет ненулевое значение:

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

Длительность опроса и сна основана на опыте и может быть настроена при необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...