Android AsyncTask контекстное поведение - PullRequest
33 голосов
/ 23 января 2010

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

Возьмите простой пример, Activity с одним 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 работает, измените ориентацию вашего телефона.

Ответы [ 4 ]

23 голосов
/ 30 мая 2010

AsyncTask не предназначен для повторного использования после того, как действие было разорвано и перезапущено. Внутренний объект-обработчик становится устаревшим, как вы заявили. В примере с Полками от Romain Guy он просто отменяет все запущенные AsyncTask, а затем перезапускает новые после изменения ориентации.

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

3 голосов
/ 11 июля 2012

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

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

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

2 голосов
/ 07 декабря 2011

Чтобы избежать этого, вы можете использовать ответ, приведенный здесь: https://stackoverflow.com/a/2124731/327011

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

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

2 голосов
/ 24 января 2010

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

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

android:configChanges="orientation|keyboardHidden" 

И переопределить onConfigurationChanged в вашем классе активности:

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