2015-12-17 3 views
0

У меня возникла интересная проблема, и я не знаю, как ее исправить. Рассмотрим следующий код:Как получить текущий savedInstanceState в обратном вызове AsyncTask?

public class MainActivity extends AppCompatActivity { 

    private Bundle savedState; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     savedState = savedInstanceState; 
     Log.d("ON CREATE", "savedState is null: "+(savedState==null)); 
     new CustomTask().execute(); 
    } 

    public class CustomTask extends AsyncTask<Void, Void, Void> { 

     protected Void doInBackground(Void... voids) { 
      try { 
       Thread.sleep(5000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      return null; 
     } 

     protected void onPostExecute(Void v) { 
      Log.d("POST EXECUTE", "savedState is null: "+(savedState==null)); 
     } 
    } 
} 

Этот код сохраняет ссылку на savedInstanceState затем запускает AsyncTask, который пытается получить доступ к этой переменной через 5 секунд. Если пользователь изменяет ориентацию устройства до того, как AsyncTask заканчивает свою работу, следующий вывод производится:

ON CREATE: savedState is null: true //initial run 
ON CREATE: savedState is null: false //re-created after orientation change 
POST EXECUTE: savedState is null: true //first instance 
POST EXECUTE: savedState is null: false //second instance 

Оба пожара onPostExecute() методов после изменения ориентации, но они, казалось бы, доступ к тому же savedState переменному, который, как я ожидал, будет не нулевым в обоих случаях, поскольку к нему обращались после изменения ориентации.

По-видимому, первая AsyncTask, которая была запущена до изменения ориентации, все еще ссылается на переменную savedState до изменения. Итак, мои вопросы:

Зачем это происходит? После восстановления состояния приложения я бы ожидал, что AsyncTask будет просто получать доступ ко всем членам класса в их текущих состояниях, что означает, что savedState будет не нулевым.

И как я могу получить доступ к текущей переменной savedInstanceState из обратного вызова AsyncTask, который был запущен до того, как эта переменная была изменена?

ответ

0

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

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

public class CustomTask extends AsyncTask<Void, Void, Void> { 

    //This could also be a reference to a callback interface 
    //implemented in an Activity 
    private Activity activity; 

    public CustomTask(Activity activity) { 
     this.activity = activity; 
    }  

    protected Void doInBackground(Void... voids) { 
     try { 
      Thread.sleep(5000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     return null; 
    } 

    protected void onPostExecute(Void v) { 
     Log.d("POST EXECUTE", "savedState is null: "+(savedState==null)); 
    } 

    //newly added method 
    public void setActivity(Activity activity) { 
     this.activity = activity; 
    } 

    //newly added method 
    public void detachFromActivity() { 
     activity = null; 
    } 
} 

Следующий шаг, чтобы сохранить ссылку на бегущего AsyncTask в качестве элемента данных о деятельности. Это всего лишь вопрос создания частной переменной private CustomTask customTask; в Управлении.

Далее, и это важная часть, вам необходимо изменить onRetainCustomNonConfigurationInstance() в своей деятельности, и в этом методе, отделить AsyncTask от своей старой деятельности (которая разрушается при вращении экрана) и сохранить ссылку на задачу поэтому мы можем работать с ним в новом экземпляре Activity, который будет воссоздан, когда экран закончит вращение. Так что этот метод будет выглядеть следующим образом:

@Override 
public Object onRetainCustomNonConfigurationInstance() { 
    if(customTask != null) { 
     customTask.detachFromActivity(); 
    } 
    return customTask; 
} 

Теперь, после того, как поворот экрана завершен и активность будет создана заново, мы должны получить ссылку на нашу задачу, которая была передана через. Поэтому мы вызываем getLastCustomNonConfigurationInstance() и отбрасываем объект, который возвращается к нашему типу класса AsyncTask. Мы можем сделать нулевую проверку здесь, чтобы узнать, есть ли у нас задача, которая прошла. Если он есть, мы устанавливаем его слушателя в нашу ссылку CURRENT Activity, чтобы обратные вызовы попадали в нужный экземпляр Activity (и, следовательно, избегали NullPointerExceptions, IllegalStateExceptions и других гадостей). Этот бит должен выглядеть следующим образом:

customTask = (CustomTask) getLastCustomNonConfigurationInstance(); 
if(customTask != null) { 
    customTask.setListener(this); 
} 

Теперь наш ход AsyncTask имеет правильную ссылку на текущий экземпляр нашей деятельности, и это будет правильно поставить обратные вызовы к уточненным деятельностям.

Для получения более подробной информации о целях и ограничениях этого решения, см Андроида документацию здесь: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()

1

После изменения ориентации есть два экземпляра MainActivity. Старый из них пережил срывающие события жизненного цикла (onStop(), onDestroy() и т. Д.), Но не был собран мусором, потому что поток внутреннего класса CustomTask все еще работает и содержит ссылку на него. Этот экземпляр CustomTask видит нуль savedState. Он ничего не знает о новом экземпляре MainActivity, созданном после перезапуска, и его нулевом savedState.

Действительно ли вы хотите, чтобы исходный экземпляр CustomTask продолжал работать после перезапуска? Может быть, вы должны отменить его, когда действие будет уничтожено. Если вам действительно нужно, чтобы он продолжал работать и имел доступ к данным состояния, которые вы теперь объявляете в своей деятельности, вам нужно будет перенести данные состояния из этой операции в другое место, например, одноэлементный объект, подкласс Application или постоянное хранилище ,

Использование retained fragment может быть еще одним вариантом сохранения состояния и обработки фона при перезагрузке.

+0

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

Смежные вопросы