Метод в экземпляре класса сохраняет старые ссылки Activity после воссоздания Activity с фрагментами (при изменении ориентации устройства)

Сегодня я боролся с проблемой, которой хотел бы поделиться с вами, и узнать ваше мнение о том, как лучше всего решить эту проблему в моем приложении.

Контекст: я хочу иметь макет с ActionBar и фрагментами. Для фрагментов я использую код по умолчанию в Android Studio, используя ViewPager с SectionsPagerAdapter, который заполняет PlaceholderFragments. Разные фрагменты имеют один XML и отличаются некоторыми TextViews, которые я изменяю в переопределении onViewCreated() класса PlaceholderFragments на основе позиции вкладки. Кроме того, я хочу, чтобы фрагменты имели немного другой макет в ландшафтном режиме, поэтому я добавил фрагмент_активность.xml в каталог res/layout-land/. В этом упражнении я создал класс, и в этом классе у меня есть метод updateControlButtons():

public class SomeActivity extends ActionBarActivity implements ActionBar.TabListener
(...)
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_hangboard);

    // Set up the action bar.
    final ActionBar actionBar = getSupportActionBar();
    Log.i("onCreate", actionBar.toString());
    (...)
  }

    private class SomeClass {
       public void updateControlButtons() {
       (...)

          runOnUiThread(new Runnable() {
                public void run() {
                    ActionBar actionBar = getSupportActionBar();
                    actionBar.hide();
                    Log.i("updateControlButtons", actionBar.toString());
                    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // KeepScreenOn off
                    (...)
                }
          }
       (...)
       }
    (.....)
  }

Чтобы обработать изменения ориентации устройства, мне нужно повторно инициализировать несколько TextView для текущей вкладки, а также для текущего состояния происходящего на экране (которое основано на полях, хранящихся в SomeClass). Для этого я переопределил метод onViewCreated() следующим образом:

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        //default code:
        super.onViewCreated(view, savedInstanceState); //this does not really do something

        int position = getArguments().getInt(ARG_SECTION_NUMBER); 
        Log.i("TEST onViewCreated position", Integer.toString(position));

        switch (position) {
            // no 0
            case 1: //BEGINNER
                someTextView.setText("0:05");
                break;
            (...)
        }

        // Reload the View IDs into the SomeClass if it is the current view created and SomeClass exists!
        if (SomeClass != null && SomeClass.timerTab == position) { 
             updateCurrentViewIDs(); 
        } // timerTab contains the Tab number on which the user started a timer, these values have to be restored

С updateCurrentViewIDs метод Activity:

private static void updateCurrentViewIDs() {
    // for device reorientation

    Log.i("updateCurrentViewIDs", "View re-initialised");
    View mView = mViewPager.findViewWithTag(SomeClass.timerTab); // the View that was playing

    SomeClass.mView = mView;

    // findIDs for current tab
    SomeClass.tvTimer = (TextView)mView.findViewById(R.id.tvTimer);
    SomeClass.tvTimerRemaining = (TextView)mView.findViewById(R.id.tvTimerRemaining);
    SomeClass.tvTimerTotal = (TextView)mView.findViewById(R.id.tvTimerTotal);
    (..)

    //update controls
    SomeClass.updateControlButtons();

Правильно: идентификаторы TextView корректно обновляются до нового представления, которое было инициализировано воссозданием фрагмента (из-за воссоздания действия), и может использоваться SomeClass, который все еще работает.

Проблема: Когда я не обновляю идентификатор чего-то, что даже не было сохранено внутри SomeClass, SomeClass по-прежнему использует идентификатор старой (и удаленной) ссылки во фрагменте! Например, SomeClass.updateControlButtons(), вызываемый новым Fragment (в onViewCreated()), по-прежнему получает старый идентификатор ActionBar в SomeClass.updateControlButtons()! Это можно увидеть в logcat:

I/onCreate﹕ android.support.v7.internal.app.WindowDecorActionBar@420f7570
I/TEST onViewCreated position﹕ 1
(..)
I/SomeClass.start()﹕ called
I/updateControlButtons﹕ android.support.v7.internal.app.WindowDecorActionBar@420f7570
(..)
(NOW DEVICE IS ORIENTED TO LANDSCAPE)
I/onCreate﹕ android.support.v7.internal.app.WindowDecorActionBar@421592d0
I/TEST onViewCreated position﹕ 1
I/updateCurrentViewIDs﹕ View re-initialised
I/updateControlButtons﹕ android.support.v7.internal.app.WindowDecorActionBar@420f7570

Конечно, когда экземпляр SomeClass закрывается, а новый экземпляр создается, ссылка на ActionBar снова становится правильной.

Решение: Конечно, я мог бы просто сохранить ActionBar в методе onCreate глобально и поместить его как поле в SomeClass внутри метода updateCurrentViewIDs(). Тогда я мог бы заменить getSupportActionBar на это обновленное поле. Я также должен был бы сделать это для Окна, чтобы иметь правильный вызов:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

Кстати, использование вызова setRetainInstance(true) не работает для меня, так как я действительно хочу изменить макет в ландшафтном режиме.

Вопрос: мне не нравится этот способ, мне кажется, что я должен слишком много обновлять после простого изменения ориентации устройства. Что-то, что, по моему мнению, даже не должно храниться в SomeClass?! Разве это не может быть решено более простым или изящным способом?

1 ответ

Решение

Я полагаю, что нашел ответ, углубившись в Воссоздание Действия.

Также, согласно документации по изменениям времени выполнения:

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

Поэтому сейчас я расскажу, как использовать Bundle для сохранения и восстановления информации при воссоздании активности.

Более того, мне, вероятно, нужно приостановить мой текущий экземпляр SomeClass и создать новый после восстановления активности. Это, однако, довольно сложно, так как у меня есть цикл for, выполняемый внутри SomeClass, так что пока нет никаких подсказок, как это решить... Тем не менее, я думаю, что это немного странно, что вызов getSupportActionBar() внутри метода внутри SomeClass ссылается до последней (и уничтоженной) Деятельности, а не только что созданной Деятельности, но, по-видимому, так она компилируется?

Обновление Наконец-то я решил отслеживать текущие ActionBar и Window, сохраняя их в статических переменных в классе Activity. Я обновляю эти переменные при воссоздании, добавляя эти строки в метод onCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {
    (..)
    storedActionBar = actionBar; //PETER: added to be able to access the new ActionBar upon Activiy Recreate (e.g. orientation change)
    storedWindow = getWindow(); // for the KEEP_SCREEN_ON flag