当前位置:  开发笔记 > Android > 正文

片段后栈和isRemoving()

如何解决《片段后栈和isRemoving()》经验,为你挑选了1个好方法。

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()但我不确定这是正确的解决方案).



1> George Mulli..:

原来不是很正确的答案

我不确定它是否是一个错误或设计,但片段只是设置为在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.

推荐阅读
低调pasta_730
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有