Фрагмент обратной стопки и isRemoving()
Я столкнулся с непоследовательными значениями возврата от Fragment.isRemoving()
, когда активность только что добавила фрагмент в задний стек. первый время, когда фрагмент временно уничтожен из-за изменения конфигурации, isRemoving()
возвращает значение true. Если фрагмент временно разрушен во второй раз, isRemoving()
возвращает false!
Мой код:
public class MainActivityFragment extends Fragment {
private static final String TAG = "MainActivityFragment";
private static final String LEVEL = "MainActivityFragment.LEVEL";
public MainActivityFragment() {
}
public static MainActivityFragment newInstance(int n) {
MainActivityFragment f = new MainActivityFragment();
f.setArguments(new Bundle());
f.getArguments().putInt(LEVEL, n);
return f;
}
private int getLevel() {
return (getArguments() == null) ? 0 : getArguments().getInt(LEVEL);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
Button button = (Button) rootView.findViewById(R.id.button);
button.setText(String.valueOf(getLevel()));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, MainActivityFragment.newInstance(getLevel() + 1))
.addToBackStack(null)
.commit();
}
});
return rootView;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, String.valueOf(getLevel()) + ": onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, String.valueOf(getLevel()) + ": onDestroy");
Log.i(TAG, String.valueOf(getLevel()) + ": isChangingConfigurations() == " + getActivity().isChangingConfigurations());
Log.i(TAG, String.valueOf(getLevel()) + ": isRemoving() == " + isRemoving());
}
Журнал (строки, начинающиеся С#, являются моими комментариями):
# Start Activity
I/MainActivityFragment: 0: onCreate
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true # ???????
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Ok, correct
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true # WHY????
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate
Является ли это ошибкой в Android или я понимаю это неправильно?
Обновление: Я добавил вызов в Fragment.dump() в onDestroy, и я получил следующие результаты:
Прежде чем фрагмент будет помещен в задний стек:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=2 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0
mAdded=true mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{336d670b in HostCallbacks{387c69e8}}
[email protected]69e8
mSavedViewState={[email protected]}
Child FragmentManager{2b6916a6 in null}}:
FragmentManager misc state:
mHost=null
mContainer=null
mCurState=0 mStateSaved=true mDestroyed=true
После того, как фрагмент помещается в задний стек и уничтожается в первый раз:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=true mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{34638ae1 in HostCallbacks{2db8e006}}
[email protected]e006
mSavedViewState={[email protected]}
Child FragmentManager{169d66c7 in null}}:
FragmentManager misc state:
mHost=null
mContainer=null
mCurState=0 mStateSaved=true mDestroyed=true
Разрушен второй раз:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{23beb2bc in HostCallbacks{c0f9245}}
[email protected]245
mSavedFragmentState=Bundle[{android:view_state={[email protected]}}]
mSavedViewState={[email protected]}
Различия между первым (а не задним стеком еще) и вторым (положить в задний стек):
- mState = 2 (
ACTIVITY_CREATED
) vs. mState = 1 (CREATED
)
- mBackStackNesting = 0 против mBackStackNesting = 1
- mAdded = true vs. mAdded = false
- mRemoving = false vs. mRemoving = true (очевидно)
Различия между вторым (сначала уничтоженным) и третьим (вторым + временным отключением):
- mRemoving = true vs. mRemoving = false
- mSavedFragmentState = null vs mSavedFragmentState = Bundle [...]
- У Child FragmentManager нет дочернего элемента FragmentManager
Однако я не знаю, как интерпретировать эти результаты.
Я начинаю думать, что isRemoving
не то, что мне нужно (мне действительно нужно что-то эквивалентное Activity.isFinishing
, но для фрагментов. Мне нужно знать, что "этот фрагмент больше никогда не будет повторно использован", поэтому я может отменить фоновые задачи. Сейчас я использую isRemoving() && !getActivity().isChangingConfigurations()
, но я не уверен, что это правильное решение).
Ответы
Ответ 1
Оригинальный не совсем правильный ответ
Я не уверен, является ли это ошибкой или по дизайну, но фрагмент только когда-либо установлен для удаления в методе FragmentManager.removeFragment
библиотеки v4.1.1 поддержки v4.
Это может сильно отличаться в зависимости от того, используете ли вы библиотеку поддержки и какую версию, но для кода, который у вас есть в репозитории GitHub, это причина.
Этот метод вызывается только когда удаляется фрагмент, который был помещен в задний стек.
Вот полный метод для справки:
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
if (mAdded != null) {
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
fragment.mRemoving = true;
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
transition, transitionStyle, false);
}
}
Возможный ответ на вопрос "Как узнать этот фрагмент никогда больше не будет использоваться"
Чтобы ответить на вопрос о том, как узнать, что вы можете отменить фоновые задачи во фрагменте, обычно эти фрагменты используют setRetainInstance(true)
Таким образом, когда ориентация устройства изменяется, тот же Фрагмент будет повторно использован, и любые текущие фоновые операции могут быть сохранены.
При сохранении экземпляра true метод фрагмента onDestroy()
не будет вызываться во время изменений ориентации, чтобы вы могли поместить свою логику отмены там, чтобы узнать, идет ли фрагмент навсегда.
Лучший ответ на то, как isRemoving работает на основе анализа исходного кода
Увидев, что этот ответ принят, я чувствую, что должен исправить пару неточностей из моего первоначального ответа. Я сказал: "Этот метод когда-либо вызывается только при удалении фрагмента, который был помещен в задний стек", что не совсем правильно. Замена фрагмента также вызывает метод и правильно устанавливает isRemoving в true как один пример.
Теперь, чтобы ответить на ваш вопрос о том, почему isRemoving кажется непоследовательным в поворотах, анализируя ваш журнал. Мои дополнительные комментарии начинаются С##
# Start Activity
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
## FragmentManager.removeFragment is called on fragment 0 setting mRemoving to true
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true ## To emphasize, this is true because as soon as you replaced fragment 0 it was set to true in the FragmentManager.removeFragment method.
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false ## fragment 1 is never actually removed so mRemoving is false.
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
## after rotating the device the first time your same fragments are not reused but new instances are created. This resets all the internal state of the fragments so mRemoving is false for all fragments.
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
## fragment 1 now has mRemoving set to true in FragmentManager.removeFragment
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false ## still false from prior rotation
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true ## true because mRemoving was set to true in FragmentManager.removeFragment.
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate
Если вы снова повернете устройство, все фрагменты вернут false из isRemoving().
Интересно, что даже если бы использовались те же экземпляры фрагментов, вы все равно могли бы получить тот же результат. Существует метод в классе Fragment
под названием initState
, который имеет следующий комментарий:
Вызывается менеджером фрагментов после удаления этого фрагмента, так что у нас нет никакого состояния слева, если приложение решает для повторного использования экземпляра. Это только очищает состояние, что структура внутренне управляет, а не вещами, которые устанавливает приложение.
Этот метод вызывается один раз для каждого фрагмента во время вращения, и одна из вещей, которые он делает, - это reset mRemoving to false.
Ответ 2
isRemoving
вызывается, когда фрагмент заменяется другим фрагментом, используя вызов .replace
, а не изменения конфигурации. Я добавил журнал:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, String.valueOf(getLevel()) + ": isAdded() == " +
isAdded());
Log.i(TAG, String.valueOf(getLevel()) + ": onCreate");
}
Вкратце:
# Start Activity
**PORTRAIT**
I/MainActivityFragment: 0: isAdded() == true // **0.portrait Added**
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
**REPLACE FRAGMENT**
I/MainActivityFragment: 1: isAdded() == true // **1.portrait Added**
# Rotate the device
**LANDSCAPE**
I/MainActivityFragment: 0: isRemoving() == true // replaced by 1 **0.portrait Removed**
I/MainActivityFragment: 1: isRemoving() == false // Not replaced in portrait
I/MainActivityFragment: 1: isAdded() == true // **1.landscape Added**
# Rotate the device a second time
**PORTRAIT**
I/MainActivityFragment: 1: isRemoving() == false // Not being replaced in landscape
I/MainActivityFragment: 1: isAdded() == true // Display portrait again **1.portrait add**
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
**REPLACE FRAGMENT**
I/MainActivityFragment: 2: isAdded() == true **2.portrait Added**
# Rotate the device
**LANDSCAPE**
I/MainActivityFragment: 1: isRemoving() == true
// Is being replaced from previous replace fragment
I/MainActivityFragment: 2: isAdded() == true // Adding to landscape **1.landscape Added**
Лог-код полной длины с добавленными аннотациями
# Start Activity
**PORTRAIT**
I/MainActivityFragment: 0: isAdded() == true // **0.portrait Added**
I/MainActivityFragment: 0: onCreate
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
**REPLACE FRAGMENT**
I/MainActivityFragment: 1: isAdded() == true // **1.portrait Added**
I/MainActivityFragment: 1: onCreate
# Rotate the device
**LANDSCAPE**
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true // replaced by 1 **0.portrait Removed**
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false // Not replaced in portrait
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false // **nothing to do**
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true // **1.landscape Added**
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
**PORTRAIT**
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false // Not being replaced in landscape
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true // Display **1.portrait add**
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
**REPLACE FRAGMENT**
I/MainActivityFragment: 2: isAdded() == true **2.portrait Added**
I/MainActivityFragment: 2: onCreate
# Rotate the device
**LANDSCAPE**
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragme/nt: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true // Is being replaced
I/MainActivityFragment: 1: isDetached() == false
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 2: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == false
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: isAdded() == true // Adding to landscape **1.landscape Added**
I/MainActivityFragment: 2: onCreate
Как вы можете видеть, когда фрагмент заменяется один раз, isremoving требуется только один раз, остальная часть logcat показывает вращения только после этого.
Кажется, изменение конфигурации управляет изменением фрагмента по-разному с помощью замены.
//Start
I/MainActivityFragment: 0: isAdded() == true
I/MainActivityFragment: 0: onCreate
//Replace fragment
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
I/MainActivity: Activity: onSaveInstanceState
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false
I/MainActivityFragment: 0: isDetached() == false
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 1: isDetached() == false
I/MainActivity: Activity: onDestroy
I/MainActivityFragment: 0: isAdded() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: isAdded() == true
I/MainActivityFragment: 1: onCreate
Я немного неясен в вашем фактическом вопросе о том, что вы пытаетесь прекратить использовать, когда фрагмент исчез. Всякий раз, когда фрагмент заменяется или изменяется в конфигурации, вы можете избавиться от этого фрагмента и всего, что связано с ним, если это ваше желание. Если вы увидите эти результаты, вы увидите, что остальные фрагменты по-прежнему находятся в фоновом режиме, поэтому вы решили сделать с ними выбор.
Ответ 3
isRemoving()
возвращает mRemoving
, который из комментария кода означает:
Если установлено, этот фрагмент удаляется из его действия.
В основном это указано в FragmentManager.removeFragment()
Также обратите внимание:
- Такое поведение может меняться в соответствии с реализацией, в частности framework vs AppCompat
- Фрагментные транзакции асинхронны, возможно, что значение, возвращаемое
isRemoving()
, изменяется при воспроизведении вашего эксперимента
Я не знаю, что вы хотите сделать с этой информацией. Если вы хотите узнать, активен ли фрагмент, вы можете использовать:
isAdded() && !isRemoving() && !isDetached()
Изменить: теперь вы спрашиваете, как узнать, что экземпляр фрагмента должен остановить асинхронную работу (поскольку фрагмент удаляется). Я бы сделал это с помощью:
getActivity().isFinishing() || isRemoving() || isDetached()
Ответ 4
Единственная идея, которую я могу сказать: метод isRemoving()
возвращает внутренний параметр mRemoving
, что означает "выполняется удаление". Обычно это означает, что есть менеджер, который, например, выпускает память в другом потоке. Поэтому время от времени вы будете получать разные значения. Это obvoiusly не обратный вызов. Простое состояние.
Ответ 5
Возможный поток:
Запускается приложение.
При создании фрагмента 0 mRemoving
инициализируется значением false.
При замене фрагмента 0 на фрагмент 1 значение mRemoving
установлено равным true для фрагмента 0 на removeFragment()
.
Изменения конфигурации.
Во время изменения конфигурации это поле (mRemoving
в фрагменте 0) считается истинным, поскольку оно было установлено removeFragment()
, когда мы впервые заменили фрагмент 0.
Но когда происходит изменение конфигурации, FragmentManager, возможно, обрабатывает поток по-другому, в отличие от нашей ручной транзакции replace()
.
И, как мы знаем, при добавлении фрагментов mRemoving
инициализируется значение false, а рассмотрение removeFragment()
не вызывается, mRemoving
является ложным во второй раз.