Android AsyncTask блокирует пользовательский интерфейс при изменении ориентации

У меня очень странная проблема. У меня есть AsyncTask, который, кажется, блокирует поток пользовательского интерфейса, когда я поворачиваю телефон.

Во-первых, позвольте мне заявить, что меня устраивает, что мой Activity уничтожается и воссоздается при ротации. Я сохраняю AsyncTask в onRetainNonConfigurationInstance(), отсоединяю от него действие в onDestroy() и снова присоединяю к нему в onCreate() с помощью getLastNonConfigurationInstance().

Все в порядке, пока AsyncTask не выполняет тяжелую работу. Например, для отладки моей проблемы у меня есть это, и оно работает точно так, как ожидалось, без блокировки пользовательского интерфейса:

@Override
protected Boolean doInBackground(Void... params) {
    boolean success = false;
    final DbAdapter dbAdapter = dbService.getAdapter();

    try {
        // pretend to do some complicated work
        Thread.sleep(20000);
        success = true;
    } catch (Exception e) {
        e.printStackTrace();
    }

    return success;
}

Однако, если я изменю его, чтобы выполнить какую-то реальную работу в фоновом режиме, тогда я получу очень большую задержку между вызовом onPause() для начала поворота и вызовом onResume() позже в новой ориентации. Эта задержка составляет десятки секунд, в течение которых пользовательский интерфейс находится в несогласованном состоянии (старый макет обрезан в новой ориентации), и кажется, что вращение блокируется на AsyncTask. Единственное изменение — это две строки внутри блока try:

@Override
protected Boolean doInBackground(Void... params) {
    boolean success = false;
    final DbAdapter dbAdapter = dbService.getAdapter();

    try {
        // actually do some complicated work
        XmlParser parser = new XmlParser(context, dbAdapter);
        success = parser.parse(source);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return success;
}

context — это глобальный контекст приложения, передаваемый конструктору AsyncTask, поэтому я не думаю, что случайно сохраняю ссылку на исходящий Activity.

Что еще может пойти не так?


person Graham Borland    schedule 09.03.2011    source источник


Ответы (1)


Возможно, ваша активность также пытается использовать dbAdapter, и у вас есть правильная синхронизация, чтобы предотвратить одновременное использование двумя потоками dbAdapter.

Возможно, ваша активность пытается загрузить данные из файловой системы в основной поток приложения, а ваши операции с базой данных вызывают слишком много конфликтов. На аппаратном уровне YAFFS2 фактически является однопоточным для операций ввода-вывода.

Вы можете использовать Traceview, чтобы попытаться получить больше информации о том, что занимает ваше время, и вы можете использовать StrictMode на Android 2.2 и выше, чтобы увидеть, где ваши действия могут пытаться выполнить файловый ввод-вывод в основном потоке приложения.

Кроме того, если ваш AsyncTask преобразует XML в записи базы данных, возможно, лучше использовать IntentService, а не AsyncTask.

person CommonsWare    schedule 09.03.2011
comment
Спасибо, что указали мне правильное направление. Это конфликт с базой данных; длительная операция базы данных заключена в транзакцию. Если я отключу транзакцию, она работает нормально (но занимает в 10 раз больше времени). Интересно, что спор не в видимой активности; моя активность — одна из нескольких в Tabhost, и это одна из других активностей (не видимая во время ротации), ожидающая разблокировки базы данных. - person Graham Borland; 09.03.2011
comment
Что касается вашего предложения IntentService... насколько я понимаю, сложнее передать результаты обратно в пользовательский интерфейс, например, чтобы закрыть диалоговое окно прогресса. Можете ли вы указать мне хорошее объяснение того, почему IntentService может быть лучше, чем AsyncTask? - person Graham Borland; 09.03.2011
comment
@Graham Borland: В некоторых случаях это может быть не лучше, чем IntentService. В своем вопросе вы не указали необходимость обновления пользовательского интерфейса. Имейте в виду, что даже если вы пропускаете AsyncTask, когда пользователь нажимает НАЗАД, фоновая работа может не быть завершена, потому что Android может завершить процесс до того, как вы закончите, поскольку он может подумать, что больше ничего не работает, чтобы оправдать сохранение процесса. . Это то, что дает вам IntentService: контейнер для длительных фоновых действий, которые могут пережить уход активности и дать Android знать, что что-то все еще существует. - person CommonsWare; 09.03.2011