Контекстное поведение Android AsyncTask

Я работаю с AsyncTasks в Android, и я имею дело с проблемой.

Возьмем простой пример: активность с одной AsyncTask. Задача на заднем плане не делает ничего впечатляющего, она просто спит в течение 8 секунд.

В конце AsyncTask в методе onPostExecute() я просто устанавливаю статус видимости кнопки на View.VISIBLE, только чтобы проверить мои результаты.

Теперь это отлично работает, пока пользователь не решит изменить ориентацию своего телефона, пока работает AsyncTask (в течение 8 секундного окна сна).

Я понимаю жизненный цикл Android-активности, и я знаю, что активность уничтожается и воссоздается.

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

Я бы ожидал, что этот старый контекст (так как пользователь вызвал изменение ориентации) либо стал нулевым, либо AsyncTask, чтобы выбросить NPE для ссылки на кнопку, которую он пытается сделать видимой.

Вместо этого NPE не выбрасывается, AsyncTask считает, что ссылка на кнопку не равна нулю, и устанавливает ее на видимую. Результат? На экране ничего не происходит!

Обновление: Я решил это, сохранив WeakReference для активности и переключения при изменении конфигурации. Это громоздко.

Здесь код:

public class Main extends Activity {

    private Button mButton = null;
    private Button mTestButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mButton = (Button) findViewById(R.id.btnStart);
        mButton.setOnClickListener(new OnClickListener () {
            @Override
            public void onClick(View v) {
                new taskDoSomething().execute(0l);
            }
        });
        mTestButton = (Button) findViewById(R.id.btnTest);   
    }

    private class TaskDoSomething extends AsyncTask<Long, Integer, Integer> 
    {
        @Override
        protected Integer doInBackground(Long... params) {
            Log.i("LOGGER", "Starting...");
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 0;
        }

        @Override
        protected void onPostExecute(Integer result) {
            Log.i("LOGGER", "...Done");
            mTestButton.setVisibility(View.VISIBLE);
        }
    }
}

Попробуйте выполнить его, и пока работа AsyncTask изменит ориентацию вашего телефона.

Ответ 1

AsyncTask не предназначен для повторного использования после того, как активность была снесена и перезапущена. Внутренний объект Handler становится устаревшим, как вы заявили. В примере с полками Ромен Гай он просто отменяет любую текущую запущенную AsyncTask, а затем перезапускает новые изменения после ориентации.

Можно передать свою Thread на новую активность, но она добавляет много сантехники. В общем, нет общего способа сделать это, но вы можете прочитать о моем методе здесь: http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html

Ответ 2

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

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

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

Ответ 3

Это тот тип вещей, который заставляет меня всегда мешать моей деятельности уничтожаться/воссоздаваться при изменении ориентации.

Для этого добавьте это в свой тег <Activity> в файл манифеста:

android:configChanges="orientation|keyboardHidden" 

И переопределите onConfigurationChanged в классе Activity:

@Override
public void onConfigurationChanged(final Configuration newConfig)
{
    // Ignore orientation change to keep activity from restarting
    super.onConfigurationChanged(newConfig);
}

Ответ 4

Чтобы избежать этого, вы можете использовать answer givin здесь: fooobar.com/info/86607/...

Но если вам нужно уничтожить действие (разные макеты для портрета и пейзажа), вы можете сделать AsyncTask открытым классом (читайте здесь, почему он не должен быть закрытым Android: AsyncTask рекомендации: частный класс или открытый класс?), а затем создать метод setActivity, чтобы установить ссылку на текущую активность всякий раз, когда она уничтожена/создана.

Здесь вы можете увидеть пример: Android AsyncTask в внешнем классе