Метод в экземпляре класса сохраняет старые ссылки 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