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中的错误还是我理解错了?
更新:我在onDestroy中添加了对Fragment.dump()的调用,得到了以下结果:
在将片段放入后栈之前:
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}} mHost=android.support.v4.app.FragmentActivity$HostCallbacks@387c69e8 mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801} 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}} mHost=android.support.v4.app.FragmentActivity$HostCallbacks@2db8e006 mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801} 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}} mHost=android.support.v4.app.FragmentActivity$HostCallbacks@c0f9245 mSavedFragmentState=Bundle[{android:view_state={2131492979=android.view.AbsSavedState$1@6adf801}}] mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
第一个(不在后面的堆栈中)和第二个(放在后面的堆栈中)之间的差异是:
mState = 2(ACTIVITY_CREATED
)与mState = 1(CREATED
)
mBackStackNesting = 0 vs. mBackStackNesting = 1
mAdded = true vs. mAdded = false
mRemoving = false vs. mRemoving = true(显然)
第二次(第一次破坏)和第三次(第二次破坏时间)之间的差异是:
mRemoving = true vs. mRemoving = false
mSavedFragmentState = null vs mSavedFragmentState = Bundle [...]
有Child FragmentManager vs.没有Child FragmentManager
但是,我不知道如何解释这些结果.
我开始认为isRemoving
不是我需要的东西(我实际需要的是等同Activity.isFinishing
于片段的东西.我需要知道"这个片段永远不会再被重用",所以我可以取消后台任务.现在我'使用,isRemoving() && !getActivity().isChangingConfigurations()
但我不确定这是正确的解决方案).
我不确定它是否是一个错误或设计,但片段只是设置为在FragmentManager.removeFragment
支持v4库v23.1.1 的方法中删除.
这可能会有所不同,具体取决于您是否使用支持库以及版本,但对于您在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)
这样,当设备的方向改变时,将重复使用相同的片段,并且可以保留任何正在进行的后台操作.
当retain instance为true时,onDestroy()
在方向更改期间不会调用片段的方法,因此您可以将取消逻辑放在那里以了解片段是否正常消失.
看到这个答案已被接受,我觉得我应该从原来的答案中解决一些不准确的问题.我说"只有当一个片段被放置在后面的堆栈上时,才会调用此方法"这不完全正确.替换片段也会调用该方法,并将isRemoving设置为true,作为一个示例.
现在通过分析您的日志来回答您关于为什么修复在旋转中看起来不一致的问题.我的其他评论以##开头
# 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
如果再次旋转设备,则所有片段将从isRemoving()返回false.
有趣的是,即使使用相同的片段实例,您仍然可能获得相同的输出.在Fragment
类中有一个方法,initState
它有以下注释:
删除此片段后,由片段管理器调用,以便在应用程序决定重新使用该实例时,我们没有任何剩余状态.这只清除了框架内部管理的状态,而不是应用程序设置的状态.
在旋转期间为每个片段调用此方法一次,并将其中的一个重置为mRemoving为false.