片段后栈和isRemoving()

时间:2016-01-07 07:01:22

标签: android android-fragments

当活动刚刚将片段添加到后台堆栈时,我遇到了来自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}

第一个(不在后面的堆栈中)和第二个(放在后面的堆栈中)之间的差异是:

  1. mState = 2(ACTIVITY_CREATED)vs. mState = 1(CREATED
  2. mBackStackNesting = 0 vs. mBackStackNesting = 1
  3. mAdded = true vs. mAdded = false
  4. mRemoving = false vs. mRemoving = true(显然)
  5. 第二次(第一次破坏)和第三次(第二次破坏时间)之间的差异是:

    1. mRemoving = true vs. mRemoving = false
    2. mSavedFragmentState = null vs mSavedFragmentState = Bundle [...]
    3. 有Child FragmentManager vs.没有Child FragmentManager
    4. 但是,我不知道如何解释这些结果。

      我开始认为isRemoving不是我需要的(我实际需要的是等同于Activity.isFinishing的东西,但对于片段。我需要知道“这个片段将从不再次重复使用“,所以我可以取消后台任务。现在我正在使用isRemoving() && !getActivity().isChangingConfigurations(),但我不确定这是正确的解决方案。)

5 个答案:

答案 0 :(得分:7)

原始不正确答案

我不确定它是否是错误或设计但是片段只能在支持v4库v23.1.1的FragmentManager.removeFragment方法中删除。

这可能会有所不同,具体取决于您是否使用支持库以及版本,但对于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的{​​{1}}类中有一个方法,其中包含以下注释:

  

一旦删除此片段,片段管理器调用        因此,如果申请决定,我们不会有任何遗留状态        重用实例。这只清除了框架的状态        内部管理,而不是应用程序设置的东西。

在旋转期间为每个片段调用此方法一次,并将其中的一个重置为mRemoving为false。

答案 1 :(得分:4)

当使用调用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**

添加注释的全长logcat

# 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

正如您所看到的,当片段被替换一次时,只需要移动一次,logcat的其余部分仅在此之后显示旋转。

似乎配置更改管理片段更改与使用replace不同。

//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

我对你的实际问题有点不清楚你在片段消失时试图停止使用的内容。每当更换片段或更改配置时,您可以处理该片段以及与其相关的任何内容,如果这是您的愿望。如果您看到这些结果,您会看到其他片段仍然在后台,因此您决定使用它们是您的选择。

答案 2 :(得分:3)

isRemoving()返回mRemoving,其中代码注释表示:

  

如果设置此片段正在从其活动中删除。

这主要在FragmentManager.removeFragment()

中设定

另请注意:

  • 此行为可能会在实现中发生变化,尤其是框架与AppCompat
  • 片段事务是异步的,重现实验时isRemoving()返回的值可能会发生变化

我不知道你想用这些信息做什么。如果您想知道片段是否处于活动状态,可以使用:

isAdded() && !isRemoving() && !isDetached()

编辑:您现在想知道如何知道片段的实例应该停止异步工作(因为片段被删除)。我这样做是为了:

getActivity().isFinishing() || isRemoving() || isDetached()

答案 3 :(得分:1)

我可以说的唯一想法是:isRemoving()方法返回内部参数mRemoving,这意味着“删除正在进行中”。通常,这意味着有一个管理器,例如,在另一个线程中释放内存。这就是为什么你不时会收到不同的价值观。这显然不是回调。只是一个简单的状态。

答案 4 :(得分:1)

可能的流程:

申请开始。 在创建片段0时,mRemoving被初始化为false。 在使用片段1替换片段0时,mRemoving将片段0设置为removeFragment()

配置更改。 在配置更改期间,当我们第一次替换片段0时,该字段(片段0中的mRemoving)被读取为真,因为它是由removeFragment()设置的。

但是当发生配置更改时,与我们的手动replace()事务相反,FragmentManager可能会以不同方式处理流。 而且,正如我们所知,添加片段时mRemoving初始化为false,并且考虑removeFragment()没有被调用,mRemoving第二次为假。